<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package prober

import (
	"math/rand"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/component-base/metrics"
	"k8s.io/klog/v2"
	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/prober/results"
)

// worker handles the periodic probing of its assigned container. Each worker has a go-routine
// associated with it which runs the probe loop until the container permanently terminates, or the
// stop channel is closed. The worker uses the probe Manager's statusManager to get up-to-date
// container IDs.
type worker struct {
	// Channel for stopping the probe.
	stopCh chan struct{}

	// Channel for triggering the probe manually.
	manualTriggerCh chan struct{}

	// The pod containing this probe (read-only)
	pod *v1.Pod

	// The container to probe (read-only)
	container v1.Container

	// Describes the probe configuration (read-only)
	spec *v1.Probe

	// The type of the worker.
	probeType probeType

	// The probe value during the initial delay.
	initialValue results.Result

	// Where to store this workers results.
	resultsManager results.Manager
	probeManager   *manager

	// The last known container ID for this worker.
	containerID kubecontainer.ContainerID
	// The last probe result for this worker.
	lastResult results.Result
	// How many times in a row the probe has returned the same result.
	resultRun int

	// If set, skip probing.
	onHold bool

	// proberResultsMetricLabels holds the labels attached to this worker
	// for the ProberResults metric by result.
	proberResultsSuccessfulMetricLabels metrics.Labels
	proberResultsFailedMetricLabels     metrics.Labels
	proberResultsUnknownMetricLabels    metrics.Labels
}

// Creates and starts a new probe worker.
func newWorker(
	m *manager,
	probeType probeType,
	pod *v1.Pod,
	container v1.Container) *worker {

	w := &worker{
		stopCh:          make(chan struct{}, 1), // Buffer so stop() can be non-blocking.
		manualTriggerCh: make(chan struct{}, 1), // Buffer so prober_manager can do non-blocking calls to doProbe.
		pod:             pod,
		container:       container,
		probeType:       probeType,
		probeManager:    m,
	}

	switch probeType {
	case readiness:
		w.spec = container.ReadinessProbe
		w.resultsManager = m.readinessManager
		w.initialValue = results.Failure
	case liveness:
		w.spec = container.LivenessProbe
		w.resultsManager = m.livenessManager
		w.initialValue = results.Success
	case startup:
		w.spec = container.StartupProbe
		w.resultsManager = m.startupManager
		w.initialValue = results.Unknown
	}

	basicMetricLabels := metrics.Labels{
		"probe_type": w.probeType.String(),
		"container":  w.container.Name,
		"pod":        w.pod.Name,
		"namespace":  w.pod.Namespace,
		"pod_uid":    string(w.pod.UID),
	}

	w.proberResultsSuccessfulMetricLabels = deepCopyPrometheusLabels(basicMetricLabels)
	w.proberResultsSuccessfulMetricLabels["result"] = probeResultSuccessful

	w.proberResultsFailedMetricLabels = deepCopyPrometheusLabels(basicMetricLabels)
	w.proberResultsFailedMetricLabels["result"] = probeResultFailed

	w.proberResultsUnknownMetricLabels = deepCopyPrometheusLabels(basicMetricLabels)
	w.proberResultsUnknownMetricLabels["result"] = probeResultUnknown

	return w
}

// run periodically probes the container.
func (w *worker) run() {
	probeTickerPeriod := time.Duration(w.spec.PeriodSeconds) * time.Second

	// If kubelet restarted the probes could be started in rapid succession.
	// Let the worker wait for a random portion of tickerPeriod before probing.
	// Do it only if the kubelet has started recently.
	if probeTickerPeriod > time.Since(w.probeManager.start) {
		time.Sleep(time.Duration(rand.Float64() * float64(probeTickerPeriod)))
	}

	probeTicker := time.NewTicker(probeTickerPeriod)

	defer func() {
		// Clean up.
		probeTicker.Stop()
		if !w.containerID.IsEmpty() {
			w.resultsManager.Remove(w.containerID)
		}

		w.probeManager.removeWorker(w.pod.UID, w.container.Name, w.probeType)
		ProberResults.Delete(w.proberResultsSuccessfulMetricLabels)
		ProberResults.Delete(w.proberResultsFailedMetricLabels)
		ProberResults.Delete(w.proberResultsUnknownMetricLabels)
	}()

probeLoop:
	for w.doProbe() {
		// Wait for next probe tick.
		select {
		case <-w.stopCh:
			break probeLoop
		case <-probeTicker.C:
		case <-w.manualTriggerCh:
			// continue
		}
	}
}

// stop stops the probe worker. The worker handles cleanup and removes itself from its manager.
// It is safe to call stop multiple times.
func (w *worker) stop() {
	select {
	case w.stopCh <- struct{}{}:
	default: // Non-blocking.
	}
}

// doProbe probes the container once and records the result.
// Returns whether the worker should continue.
func (w *worker) doProbe() (keepGoing bool) {
	defer func() { recover() }() // Actually eat panics (HandleCrash takes care of logging)
	defer runtime.HandleCrash(func(_ interface{}) { keepGoing = true })

	status, ok := w.probeManager.statusManager.GetPodStatus(w.pod.UID)
	if !ok {
		// Either the pod has not been created yet, or it was already deleted.
		klog.V(3).InfoS("No status for pod", "pod", klog.KObj(w.pod))
		return true
	}

	// Worker should terminate if pod is terminated.
	if status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded {
		klog.V(3).InfoS("Pod is terminated, exiting probe worker",
			"pod", klog.KObj(w.pod), "phase", status.Phase)
		return false
	}

	c, ok := podutil.GetContainerStatus(status.ContainerStatuses, w.container.Name)
	if !ok || len(c.ContainerID) == 0 {
		// Either the container has not been created yet, or it was deleted.
		klog.V(3).InfoS("Probe target container not found",
			"pod", klog.KObj(w.pod), "containerName", w.container.Name)
		return true // Wait for more information.
	}

	if w.containerID.String() != c.ContainerID {
		if !w.containerID.IsEmpty() {
			w.resultsManager.Remove(w.containerID)
		}
		w.containerID = kubecontainer.ParseContainerID(c.ContainerID)
		w.resultsManager.Set(w.containerID, w.initialValue, w.pod)
		// We've got a new container; resume probing.
		w.onHold = false
	}

	if w.onHold {
		// Worker is on hold until there is a new container.
		return true
	}

	if c.State.Running == nil {
		klog.V(3).InfoS("Non-running container probed",
			"pod", klog.KObj(w.pod), "containerName", w.container.Name)
		if !w.containerID.IsEmpty() {
			w.resultsManager.Set(w.containerID, results.Failure, w.pod)
		}
		// Abort if the container will not be restarted.
		return c.State.Terminated == nil ||
			w.pod.Spec.RestartPolicy != v1.RestartPolicyNever
	}

	// Graceful shutdown of the pod.
	if w.pod.ObjectMeta.DeletionTimestamp != nil && (w.probeType == liveness || w.probeType == startup) {
		klog.V(3).InfoS("Pod deletion requested, setting probe result to success",
			"probeType", w.probeType, "pod", klog.KObj(w.pod), "containerName", w.container.Name)
		if w.probeType == startup {
			klog.InfoS("Pod deletion requested before container has fully started",
				"pod", klog.KObj(w.pod), "containerName", w.container.Name)
		}
		// Set a last result to ensure quiet shutdown.
		w.resultsManager.Set(w.containerID, results.Success, w.pod)
		// Stop probing at this point.
		return false
	}

	// Probe disabled for InitialDelaySeconds.
	if int32(time.Since(c.State.Running.StartedAt.Time).Seconds()) < w.spec.InitialDelaySeconds {
		return true
	}

	if c.Started != nil && *c.Started {
		// Stop probing for startup once container has started.
		if w.probeType == startup {
			return false
		}
	} else {
		// Disable other probes until container has started.
		if w.probeType != startup {
			return true
		}
	}

	// TODO: in order for exec probes to correctly handle downward API env, we must be able to reconstruct
	// the full container environment here, OR we must make a call to the CRI in order to get those environment
	// values from the running container.
	result, err := w.probeManager.prober.probe(w.probeType, w.pod, status, w.container, w.containerID)
	if err != nil {
		// Prober error, throw away the result.
		return true
	}

	switch result {
	case results.Success:
		ProberResults.With(w.proberResultsSuccessfulMetricLabels).Inc()
	case results.Failure:
		ProberResults.With(w.proberResultsFailedMetricLabels).Inc()
	default:
		ProberResults.With(w.proberResultsUnknownMetricLabels).Inc()
	}

	if w.lastResult == result {
		w.resultRun++
	} else {
		w.lastResult = result
		w.resultRun = 1
	}

	if (result == results.Failure && w.resultRun < int(w.spec.FailureThreshold)) ||
		(result == results.Success && w.resultRun < int(w.spec.SuccessThreshold)) {
		// Success or failure is below threshold - leave the probe state unchanged.
		return true
	}

	w.resultsManager.Set(w.containerID, result, w.pod)

	if (w.probeType == liveness || w.probeType == startup) && result == results.Failure {
		// The container fails a liveness/startup check, it will need to be restarted.
		// Stop probing until we see a new container ID. This is to reduce the
		// chance of hitting #21751, where running `docker exec` when a
		// container is being stopped may lead to corrupted container state.
		w.onHold = true
		w.resultRun = 0
	}

	return true
}

func deepCopyPrometheusLabels(m metrics.Labels) metrics.Labels {
	ret := make(metrics.Labels, len(m))
	for k, v := range m {
		ret[k] = v
	}
	return ret
}
<commit_msg>Fix startupProbe behaviour changed<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package prober

import (
	"math/rand"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/component-base/metrics"
	"k8s.io/klog/v2"
	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/prober/results"
)

// worker handles the periodic probing of its assigned container. Each worker has a go-routine
// associated with it which runs the probe loop until the container permanently terminates, or the
// stop channel is closed. The worker uses the probe Manager's statusManager to get up-to-date
// container IDs.
type worker struct {
	// Channel for stopping the probe.
	stopCh chan struct{}

	// Channel for triggering the probe manually.
	manualTriggerCh chan struct{}

	// The pod containing this probe (read-only)
	pod *v1.Pod

	// The container to probe (read-only)
	container v1.Container

	// Describes the probe configuration (read-only)
	spec *v1.Probe

	// The type of the worker.
	probeType probeType

	// The probe value during the initial delay.
	initialValue results.Result

	// Where to store this workers results.
	resultsManager results.Manager
	probeManager   *manager

	// The last known container ID for this worker.
	containerID kubecontainer.ContainerID
	// The last probe result for this worker.
	lastResult results.Result
	// How many times in a row the probe has returned the same result.
	resultRun int

	// If set, skip probing.
	onHold bool

	// proberResultsMetricLabels holds the labels attached to this worker
	// for the ProberResults metric by result.
	proberResultsSuccessfulMetricLabels metrics.Labels
	proberResultsFailedMetricLabels     metrics.Labels
	proberResultsUnknownMetricLabels    metrics.Labels
}

// Creates and starts a new probe worker.
func newWorker(
	m *manager,
	probeType probeType,
	pod *v1.Pod,
	container v1.Container) *worker {

	w := &worker{
		stopCh:          make(chan struct{}, 1), // Buffer so stop() can be non-blocking.
		manualTriggerCh: make(chan struct{}, 1), // Buffer so prober_manager can do non-blocking calls to doProbe.
		pod:             pod,
		container:       container,
		probeType:       probeType,
		probeManager:    m,
	}

	switch probeType {
	case readiness:
		w.spec = container.ReadinessProbe
		w.resultsManager = m.readinessManager
		w.initialValue = results.Failure
	case liveness:
		w.spec = container.LivenessProbe
		w.resultsManager = m.livenessManager
		w.initialValue = results.Success
	case startup:
		w.spec = container.StartupProbe
		w.resultsManager = m.startupManager
		w.initialValue = results.Unknown
	}

	basicMetricLabels := metrics.Labels{
		"probe_type": w.probeType.String(),
		"container":  w.container.Name,
		"pod":        w.pod.Name,
		"namespace":  w.pod.Namespace,
		"pod_uid":    string(w.pod.UID),
	}

	w.proberResultsSuccessfulMetricLabels = deepCopyPrometheusLabels(basicMetricLabels)
	w.proberResultsSuccessfulMetricLabels["result"] = probeResultSuccessful

	w.proberResultsFailedMetricLabels = deepCopyPrometheusLabels(basicMetricLabels)
	w.proberResultsFailedMetricLabels["result"] = probeResultFailed

	w.proberResultsUnknownMetricLabels = deepCopyPrometheusLabels(basicMetricLabels)
	w.proberResultsUnknownMetricLabels["result"] = probeResultUnknown

	return w
}

// run periodically probes the container.
func (w *worker) run() {
	probeTickerPeriod := time.Duration(w.spec.PeriodSeconds) * time.Second

	// If kubelet restarted the probes could be started in rapid succession.
	// Let the worker wait for a random portion of tickerPeriod before probing.
	// Do it only if the kubelet has started recently.
	if probeTickerPeriod > time.Since(w.probeManager.start) {
		time.Sleep(time.Duration(rand.Float64() * float64(probeTickerPeriod)))
	}

	probeTicker := time.NewTicker(probeTickerPeriod)

	defer func() {
		// Clean up.
		probeTicker.Stop()
		if !w.containerID.IsEmpty() {
			w.resultsManager.Remove(w.containerID)
		}

		w.probeManager.removeWorker(w.pod.UID, w.container.Name, w.probeType)
		ProberResults.Delete(w.proberResultsSuccessfulMetricLabels)
		ProberResults.Delete(w.proberResultsFailedMetricLabels)
		ProberResults.Delete(w.proberResultsUnknownMetricLabels)
	}()

probeLoop:
	for w.doProbe() {
		// Wait for next probe tick.
		select {
		case <-w.stopCh:
			break probeLoop
		case <-probeTicker.C:
		case <-w.manualTriggerCh:
			// continue
		}
	}
}

// stop stops the probe worker. The worker handles cleanup and removes itself from its manager.
// It is safe to call stop multiple times.
func (w *worker) stop() {
	select {
	case w.stopCh <- struct{}{}:
	default: // Non-blocking.
	}
}

// doProbe probes the container once and records the result.
// Returns whether the worker should continue.
func (w *worker) doProbe() (keepGoing bool) {
	defer func() { recover() }() // Actually eat panics (HandleCrash takes care of logging)
	defer runtime.HandleCrash(func(_ interface{}) { keepGoing = true })

	status, ok := w.probeManager.statusManager.GetPodStatus(w.pod.UID)
	if !ok {
		// Either the pod has not been created yet, or it was already deleted.
		klog.V(3).InfoS("No status for pod", "pod", klog.KObj(w.pod))
		return true
	}

	// Worker should terminate if pod is terminated.
	if status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded {
		klog.V(3).InfoS("Pod is terminated, exiting probe worker",
			"pod", klog.KObj(w.pod), "phase", status.Phase)
		return false
	}

	c, ok := podutil.GetContainerStatus(status.ContainerStatuses, w.container.Name)
	if !ok || len(c.ContainerID) == 0 {
		// Either the container has not been created yet, or it was deleted.
		klog.V(3).InfoS("Probe target container not found",
			"pod", klog.KObj(w.pod), "containerName", w.container.Name)
		return true // Wait for more information.
	}

	if w.containerID.String() != c.ContainerID {
		if !w.containerID.IsEmpty() {
			w.resultsManager.Remove(w.containerID)
		}
		w.containerID = kubecontainer.ParseContainerID(c.ContainerID)
		w.resultsManager.Set(w.containerID, w.initialValue, w.pod)
		// We've got a new container; resume probing.
		w.onHold = false
	}

	if w.onHold {
		// Worker is on hold until there is a new container.
		return true
	}

	if c.State.Running == nil {
		klog.V(3).InfoS("Non-running container probed",
			"pod", klog.KObj(w.pod), "containerName", w.container.Name)
		if !w.containerID.IsEmpty() {
			w.resultsManager.Set(w.containerID, results.Failure, w.pod)
		}
		// Abort if the container will not be restarted.
		return c.State.Terminated == nil ||
			w.pod.Spec.RestartPolicy != v1.RestartPolicyNever
	}

	// Graceful shutdown of the pod.
	if w.pod.ObjectMeta.DeletionTimestamp != nil && (w.probeType == liveness || w.probeType == startup) {
		klog.V(3).InfoS("Pod deletion requested, setting probe result to success",
			"probeType", w.probeType, "pod", klog.KObj(w.pod), "containerName", w.container.Name)
		if w.probeType == startup {
			klog.InfoS("Pod deletion requested before container has fully started",
				"pod", klog.KObj(w.pod), "containerName", w.container.Name)
		}
		// Set a last result to ensure quiet shutdown.
		w.resultsManager.Set(w.containerID, results.Success, w.pod)
		// Stop probing at this point.
		return false
	}

	// Probe disabled for InitialDelaySeconds.
	if int32(time.Since(c.State.Running.StartedAt.Time).Seconds()) < w.spec.InitialDelaySeconds {
		return true
	}

	if c.Started != nil && *c.Started {
		// Stop probing for startup once container has started.
		// we keep it running to make sure it will work for restarted container.
		if w.probeType == startup {
			return true
		}
	} else {
		// Disable other probes until container has started.
		if w.probeType != startup {
			return true
		}
	}

	// TODO: in order for exec probes to correctly handle downward API env, we must be able to reconstruct
	// the full container environment here, OR we must make a call to the CRI in order to get those environment
	// values from the running container.
	result, err := w.probeManager.prober.probe(w.probeType, w.pod, status, w.container, w.containerID)
	if err != nil {
		// Prober error, throw away the result.
		return true
	}

	switch result {
	case results.Success:
		ProberResults.With(w.proberResultsSuccessfulMetricLabels).Inc()
	case results.Failure:
		ProberResults.With(w.proberResultsFailedMetricLabels).Inc()
	default:
		ProberResults.With(w.proberResultsUnknownMetricLabels).Inc()
	}

	if w.lastResult == result {
		w.resultRun++
	} else {
		w.lastResult = result
		w.resultRun = 1
	}

	if (result == results.Failure && w.resultRun < int(w.spec.FailureThreshold)) ||
		(result == results.Success && w.resultRun < int(w.spec.SuccessThreshold)) {
		// Success or failure is below threshold - leave the probe state unchanged.
		return true
	}

	w.resultsManager.Set(w.containerID, result, w.pod)

	if (w.probeType == liveness || w.probeType == startup) && result == results.Failure {
		// The container fails a liveness/startup check, it will need to be restarted.
		// Stop probing until we see a new container ID. This is to reduce the
		// chance of hitting #21751, where running `docker exec` when a
		// container is being stopped may lead to corrupted container state.
		w.onHold = true
		w.resultRun = 0
	}

	return true
}

func deepCopyPrometheusLabels(m metrics.Labels) metrics.Labels {
	ret := make(metrics.Labels, len(m))
	for k, v := range m {
		ret[k] = v
	}
	return ret
}
<|endoftext|>


<commit_before>// +build linux

package native

import "github.com/opencontainers/runc/libcontainer/configs"

var defaultSeccompProfile = &configs.Seccomp{
	DefaultAction: configs.Allow,
	Syscalls: []*configs.Syscall{
		{
			// Quota and Accounting syscalls which could let containers
			// disable their own resource limits or process accounting
			Name:   "acct",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from using the kernel keyring,
			// which is not namespaced
			Name:   "add_key",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Similar to clock_settime and settimeofday
			// Time/Date is not namespaced
			Name:   "adjtimex",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Time/Date is not namespaced
			Name:   "clock_settime",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny cloning new namespaces
			Name:   "clone",
			Action: configs.Errno,
			Args: []*configs.Arg{
				{
					// flags from sched.h
					// CLONE_NEWUTS		0x04000000
					// CLONE_NEWIPC		0x08000000
					// CLONE_NEWUSER	0x10000000
					// CLONE_NEWPID		0x20000000
					// CLONE_NEWNET		0x40000000
					Index: 0,
					Value: uint64(0x04000000),
					Op:    configs.GreaterThanOrEqualTo,
				},
				{
					// flags from sched.h
					// CLONE_NEWNS		0x00020000
					Index: 0,
					Value: uint64(0x00020000),
					Op:    configs.EqualTo,
				},
			},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "create_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "delete_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny retrieval of exported kernel and module symbols
			Name:   "get_kernel_syms",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "get_mempolicy",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny getting the list of robust futexes
			Name:   "get_robust_list",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "init_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from modifying kernel I/O privilege levels.
			// Already restricted as containers drop CAP_SYS_RAWIO by default.
			Name:   "ioperm",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from modifying kernel I/O privilege levels.
			// Already restricted as containers drop CAP_SYS_RAWIO by default.
			Name:   "iopl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Sister syscall of kexec_load that does the same thing,
			// slightly different arguments
			Name:   "kexec_file_load",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny loading a new kernel for later execution
			Name:   "kexec_load",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from using the kernel keyring,
			// which is not namespaced
			Name:   "keyctl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Tracing/profiling syscalls,
			// which could leak a lot of information on the host
			Name:   "lookup_dcookie",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "mbind",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "migrate_pages",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Old syscall only used in 16-bit code,
			// and a potential information leak
			Name:   "modify_ldt",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny mount
			Name:   "mount",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "move_pages",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny interaction with the kernel nfs daemon
			Name:   "nfsservctl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Cause of an old container breakout,
			// might as well restrict it to be on the safe side
			Name:   "open_by_handle_at",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Tracing/profiling syscalls,
			// which could leak a lot of information on the host
			Name:   "perf_event_open",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent container from enabling BSD emulation.
			// Not inherently dangerous, but poorly tested,
			// potential for a lot of kernel vulns in this.
			Name:   "personality",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny pivot_root
			Name:   "pivot_root",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Already blocked by dropping CAP_PTRACE
			Name:   "ptrace",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "query_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Quota and Accounting syscalls which could let containers
			// disable their own resource limits or process accounting
			Name:   "quotactl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Probably a bad idea to let containers reboot the host
			Name:   "reboot",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Probably a bad idea to let containers restart
			Name:   "restart_syscall",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from using the kernel keyring,
			// which is not namespaced
			Name:   "request_key",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// meta, deny seccomp
			Name:   "seccomp",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "set_mempolicy",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// deny associating a thread with a namespace
			Name:   "setns",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny setting the list of robust futexes
			Name:   "set_robust_list",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Time/Date is not namespaced
			Name:   "settimeofday",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny start/stop swapping to file/device
			Name:   "swapon",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny start/stop swapping to file/device
			Name:   "swapoff",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny read/write system parameters
			Name:   "_sysctl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny umount
			Name:   "umount2",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Same as clone
			Name:   "unshare",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Older syscall related to shared libraries, unused for a long time
			Name:   "uselib",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
	},
}
<commit_msg>Allow use of robust list syscalls<commit_after>// +build linux

package native

import "github.com/opencontainers/runc/libcontainer/configs"

var defaultSeccompProfile = &configs.Seccomp{
	DefaultAction: configs.Allow,
	Syscalls: []*configs.Syscall{
		{
			// Quota and Accounting syscalls which could let containers
			// disable their own resource limits or process accounting
			Name:   "acct",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from using the kernel keyring,
			// which is not namespaced
			Name:   "add_key",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Similar to clock_settime and settimeofday
			// Time/Date is not namespaced
			Name:   "adjtimex",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Time/Date is not namespaced
			Name:   "clock_settime",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny cloning new namespaces
			Name:   "clone",
			Action: configs.Errno,
			Args: []*configs.Arg{
				{
					// flags from sched.h
					// CLONE_NEWUTS		0x04000000
					// CLONE_NEWIPC		0x08000000
					// CLONE_NEWUSER	0x10000000
					// CLONE_NEWPID		0x20000000
					// CLONE_NEWNET		0x40000000
					Index: 0,
					Value: uint64(0x04000000),
					Op:    configs.GreaterThanOrEqualTo,
				},
				{
					// flags from sched.h
					// CLONE_NEWNS		0x00020000
					Index: 0,
					Value: uint64(0x00020000),
					Op:    configs.EqualTo,
				},
			},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "create_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "delete_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny retrieval of exported kernel and module symbols
			Name:   "get_kernel_syms",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "get_mempolicy",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "init_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from modifying kernel I/O privilege levels.
			// Already restricted as containers drop CAP_SYS_RAWIO by default.
			Name:   "ioperm",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from modifying kernel I/O privilege levels.
			// Already restricted as containers drop CAP_SYS_RAWIO by default.
			Name:   "iopl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Sister syscall of kexec_load that does the same thing,
			// slightly different arguments
			Name:   "kexec_file_load",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny loading a new kernel for later execution
			Name:   "kexec_load",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from using the kernel keyring,
			// which is not namespaced
			Name:   "keyctl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Tracing/profiling syscalls,
			// which could leak a lot of information on the host
			Name:   "lookup_dcookie",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "mbind",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "migrate_pages",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Old syscall only used in 16-bit code,
			// and a potential information leak
			Name:   "modify_ldt",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny mount
			Name:   "mount",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "move_pages",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny interaction with the kernel nfs daemon
			Name:   "nfsservctl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Cause of an old container breakout,
			// might as well restrict it to be on the safe side
			Name:   "open_by_handle_at",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Tracing/profiling syscalls,
			// which could leak a lot of information on the host
			Name:   "perf_event_open",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent container from enabling BSD emulation.
			// Not inherently dangerous, but poorly tested,
			// potential for a lot of kernel vulns in this.
			Name:   "personality",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny pivot_root
			Name:   "pivot_root",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Already blocked by dropping CAP_PTRACE
			Name:   "ptrace",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny manipulation and functions on kernel modules.
			Name:   "query_module",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Quota and Accounting syscalls which could let containers
			// disable their own resource limits or process accounting
			Name:   "quotactl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Probably a bad idea to let containers reboot the host
			Name:   "reboot",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Probably a bad idea to let containers restart
			Name:   "restart_syscall",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Prevent containers from using the kernel keyring,
			// which is not namespaced
			Name:   "request_key",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// meta, deny seccomp
			Name:   "seccomp",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Terrifying syscalls that modify kernel memory and NUMA settings.
			// They're gated by CAP_SYS_NICE,
			// which we do not retain by default in containers.
			Name:   "set_mempolicy",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// deny associating a thread with a namespace
			Name:   "setns",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Time/Date is not namespaced
			Name:   "settimeofday",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny start/stop swapping to file/device
			Name:   "swapon",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny start/stop swapping to file/device
			Name:   "swapoff",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny read/write system parameters
			Name:   "_sysctl",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Deny umount
			Name:   "umount2",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Same as clone
			Name:   "unshare",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
		{
			// Older syscall related to shared libraries, unused for a long time
			Name:   "uselib",
			Action: configs.Errno,
			Args:   []*configs.Arg{},
		},
	},
}
<|endoftext|>


<commit_before>package cache

import (
	"container/heap"
	"time"
)

// A heap that expires the first added string first.
//
// This also allows for a string to be refreshed. That is, be put
// at the back of the line.
//
// This cache implementation does not enforce a size limit. It only
// orders items for eviction. The user must evict them to reach a desired
// Len() (size).
type LIFOCache struct {
	// The heap of strings.
	Keys []string

	// The heap of satellite data.
	Items []interface{}

	// The heap of insertion values.
	AddedTime []int64

	// The map of strings to their indexes in the arrays.
	Indexes map[string]int

	// When a string is removed, the eviction handler is called and given the
	// evicted string and associated data.
	//
	// This allows users of this class to have it drive eviction of other resources.
	//
	// The eviction handler is not called when a string is refreshed / re-added.
	EvictionHandlers []func(s string, data interface{})

	// A function that returns the "time" an element is added.
	//
	// This is used to sort the elements for removal where the
	// smallest int64 value is considered first.
	//
	// These integers need not be time, but it is convenient to think of them
	// that way.
	TimeFunction func() int64
}

// Construct a new LIFOCache that uses the system clock in seconds
// to order the strings added.
func NewLIFOCache() *LIFOCache {
	h := LIFOCache{
		Keys:             []string{},
		Items:            []interface{}{},
		Indexes:          make(map[string]int),
		AddedTime:        []int64{},
		EvictionHandlers: []func(string, interface{}){},
		TimeFunction: func() int64 {
			return time.Now().Unix()
		},
	}

	return &h
}

func (c *LIFOCache) Len() int {
	return len(c.Items)
}

func (c *LIFOCache) Less(i, j int) bool {
	return c.AddedTime[i] < c.AddedTime[j]
}

func (c *LIFOCache) Swap(i, j int) {
	// Swap list items.
	c.AddedTime[i], c.AddedTime[j] = c.AddedTime[j], c.AddedTime[i]
	c.Items[i], c.Items[j] = c.Items[j], c.Items[i]
	c.Keys[i], c.Keys[j] = c.Keys[j], c.Keys[i]
	c.EvictionHandlers[i], c.EvictionHandlers[j] = c.EvictionHandlers[j], c.EvictionHandlers[i]

	// Update key to index mapping.
	c.Indexes[c.Keys[i]] = i
	c.Indexes[c.Keys[j]] = j

}

// Push a string key into this cache.
//
// *Do not call this directly.* This is an internal API.
func (c *LIFOCache) Push(x interface{}) {
	switch s := x.(type) {
	default:
		panic("This can only handle strings.")
	case string:
		if _, ok := c.Indexes[s]; ok {
			// Pushing, in this case, is moving the string to the end and
			// updateing the time.
			i := c.Indexes[s]
			c.AddedTime[i] = c.TimeFunction()
			c.Swap(i, len(c.Items)-1)
		} else {
			// Add string.
			c.Indexes[s] = len(c.Items)
			c.AddedTime = append(c.AddedTime, c.TimeFunction())
			c.Keys = append(c.Keys, s)

			if len(c.AddedTime) != len(c.EvictionHandlers) {
				panic("Use the Put function to add elements to this cache.")
			}

			if len(c.AddedTime) != len(c.Items) {
				panic("Use the Put function to add elements to this cache.")
			}
		}
	}
}

// Return the last user-added object and call the Eviction function.
//
// *Do not call this directly.* This is an internal API.
func (c *LIFOCache) Pop() interface{} {
	// The new length. Also as an index value.
	l := len(c.Items) - 1

	// Record the last item.
	k := c.Keys[l]
	i := c.Items[l]
	e := c.EvictionHandlers[l]

	// Slice the two arrays.
	c.Keys = c.Keys[0:l]
	c.Items = c.Items[0:l]
	c.AddedTime = c.AddedTime[0:l]
	c.EvictionHandlers = c.EvictionHandlers[0:l]

	// Remove the key mapping.
	delete(c.Indexes, k)

	e(k, i)

	// Return the last item.
	return i
}

// Add a key to this cache with a given eviction function.
//
// If the key already exists, the object is updated, the AddedTime is updated
// and a 2-tuple with the previous object and true is returned.
//
// If the key does not already exist, the object is added under that key
// and (nil, false) is returned.
func (c *LIFOCache) PutWithHandler(key string, item interface{}, evictionhandler func(string, interface{})) (interface{}, bool) {

	if _, ok := c.Indexes[key]; ok {
		// Update the time and re-heap.
		i := c.Indexes[key]
		o := c.Items[i]
		c.AddedTime[i] = c.TimeFunction()
		heap.Fix(c, i)

		return o, true
	} else {
		// Push our satellite data first, before the heap data.
		c.EvictionHandlers = append(c.EvictionHandlers, evictionhandler)
		c.Items = append(c.Items, item)
		heap.Push(c, key)

		return nil, false
	}
}

func (c *LIFOCache) Put(key string, item interface{}) (interface{}, bool) {
	return c.PutWithHandler(key, item, func(string, interface{}) {})
}

func (c *LIFOCache) Get(key string) (interface{}, bool) {
	if i, ok := c.Indexes[key]; ok {
		return c.Items[i], true
	} else {
		return nil, false
	}
}

// Evict the next item, returning the key and value.
//
// If the cache is empty "" and nil are returned.
func (c *LIFOCache) EvictNext() (string, interface{}) {
	if len(c.Keys) > 0 {
		k := c.Keys[0]
		i := heap.Pop(c)
		return k, i
	} else {
		return "", nil
	}
}

// Evict items that are older than the given tm.
// That is the object's added time is less-than tm.
func (c *LIFOCache) EvictOlderThan(tm int64) {
	for len(c.AddedTime) > 0 && c.AddedTime[0] < tm {
		c.EvictNext()
	}
}

// Set the added time of an item and re-heap it.
func (c *LIFOCache) SetAddedTime(key string, tm int64) {
	if idx, ok := c.Indexes[key]; ok {
		c.AddedTime[idx] = tm
		heap.Fix(c, idx)
	}
}

// Remove the given key from the cache.
func (c *LIFOCache) Remove(key string) (interface{}, bool) {
	if i, ok := c.Indexes[key]; ok {
		obj := c.Items[i]

		lasti := len(c.Items) - 1

		// Put i at the end of the arrays.
		c.Swap(i, lasti)

		// Remove the last element.
		c.Keys = c.Keys[0:lasti]
		c.Items = c.Items[0:lasti]
		c.EvictionHandlers = c.EvictionHandlers[0:lasti]
		c.AddedTime = c.AddedTime[0:lasti]

		delete(c.Indexes, key)

		// Fix i.
		heap.Fix(c, i)

		// Return it.
		return obj, true
	} else {
		return nil, false
	}
}

// Return the next key to be returned by a call to EvictNext().
func (c *LIFOCache) MinKey() string {
	return c.Keys[0]
}

// Return the time of the next key and item to be returned by a call to EvictNext().
func (c *LIFOCache) MinTime() int64 {
	return c.AddedTime[0]
}

// Return next item to be returned by a call to EvictNext().
func (c *LIFOCache) MinItem() interface{} {
	return c.Items[0]
}
<commit_msg>pkg/sdsai/cache/lifocache.go: Subtle length and ordering error because of mixing heap implementation and custom code.<commit_after>package cache

import (
	"container/heap"
	"time"
)

// A heap that expires the first added string first.
//
// This also allows for a string to be refreshed. That is, be put
// at the back of the line.
//
// This cache implementation does not enforce a size limit. It only
// orders items for eviction. The user must evict them to reach a desired
// Len() (size).
type LIFOCache struct {
	// The heap of strings.
	Keys []string

	// The heap of satellite data.
	Items []interface{}

	// The heap of insertion values.
	AddedTime []int64

	// The map of strings to their indexes in the arrays.
	Indexes map[string]int

	// When a string is removed, the eviction handler is called and given the
	// evicted string and associated data.
	//
	// This allows users of this class to have it drive eviction of other resources.
	//
	// The eviction handler is not called when a string is refreshed / re-added.
	EvictionHandlers []func(s string, data interface{})

	// A function that returns the "time" an element is added.
	//
	// This is used to sort the elements for removal where the
	// smallest int64 value is considered first.
	//
	// These integers need not be time, but it is convenient to think of them
	// that way.
	TimeFunction func() int64
}

// Construct a new LIFOCache that uses the system clock in seconds
// to order the strings added.
func NewLIFOCache() *LIFOCache {
	h := LIFOCache{
		Keys:             []string{},
		Items:            []interface{}{},
		Indexes:          make(map[string]int),
		AddedTime:        []int64{},
		EvictionHandlers: []func(string, interface{}){},
		TimeFunction: func() int64 {
			return time.Now().Unix()
		},
	}

	return &h
}

func (c *LIFOCache) Len() int {
	return len(c.Items)
}

func (c *LIFOCache) Less(i, j int) bool {
	return c.AddedTime[i] < c.AddedTime[j]
}

func (c *LIFOCache) Swap(i, j int) {
	// Swap list items.
	c.AddedTime[i], c.AddedTime[j] = c.AddedTime[j], c.AddedTime[i]
	c.Items[i], c.Items[j] = c.Items[j], c.Items[i]
	c.Keys[i], c.Keys[j] = c.Keys[j], c.Keys[i]
	c.EvictionHandlers[i], c.EvictionHandlers[j] = c.EvictionHandlers[j], c.EvictionHandlers[i]

	// Update key to index mapping.
	c.Indexes[c.Keys[i]] = i
	c.Indexes[c.Keys[j]] = j

}

// Push a string key into this cache.
//
// *Do not call this directly.* This is an internal API.
func (c *LIFOCache) Push(x interface{}) {
	switch s := x.(type) {
	default:
		panic("This can only handle strings.")
	case string:
		if _, ok := c.Indexes[s]; ok {
			// Pushing, in this case, is moving the string to the end and
			// updateing the time.
			i := c.Indexes[s]
			c.AddedTime[i] = c.TimeFunction()
			c.Swap(i, len(c.Items)-1)
		} else {
			// Add string.
			c.Indexes[s] = len(c.Keys)
			c.AddedTime = append(c.AddedTime, c.TimeFunction())
			c.Keys = append(c.Keys, s)

			if len(c.AddedTime) != len(c.EvictionHandlers) {
				panic("Use the Put function to add elements to this cache.")
			}

			if len(c.AddedTime) != len(c.Items) {
				panic("Use the Put function to add elements to this cache.")
			}
		}
	}
}

// Return the last user-added object and call the Eviction function.
//
// *Do not call this directly.* This is an internal API.
func (c *LIFOCache) Pop() interface{} {
	// The new length. Also as an index value.
	l := len(c.Items) - 1

	// Record the last item.
	k := c.Keys[l]
	i := c.Items[l]
	e := c.EvictionHandlers[l]

	// Slice the two arrays.
	c.Keys = c.Keys[0:l]
	c.Items = c.Items[0:l]
	c.AddedTime = c.AddedTime[0:l]
	c.EvictionHandlers = c.EvictionHandlers[0:l]

	// Remove the key mapping.
	delete(c.Indexes, k)

	e(k, i)

	// Return the last item.
	return i
}

// Add a key to this cache with a given eviction function.
//
// If the key already exists, the object is updated, the AddedTime is updated
// and a 2-tuple with the previous object and true is returned.
//
// If the key does not already exist, the object is added under that key
// and (nil, false) is returned.
func (c *LIFOCache) PutWithHandler(key string, item interface{}, evictionhandler func(string, interface{})) (interface{}, bool) {

	if _, ok := c.Indexes[key]; ok {
		// Update the time and re-heap.
		i := c.Indexes[key]
		o := c.Items[i]
		c.AddedTime[i] = c.TimeFunction()
		heap.Fix(c, i)

		return o, true
	} else {
		// Push our satellite data first, before the heap data.
		c.EvictionHandlers = append(c.EvictionHandlers, evictionhandler)
		c.Items = append(c.Items, item)
		heap.Push(c, key)

		return nil, false
	}
}

func (c *LIFOCache) Put(key string, item interface{}) (interface{}, bool) {
	return c.PutWithHandler(key, item, func(string, interface{}) {})
}

func (c *LIFOCache) Get(key string) (interface{}, bool) {
	if i, ok := c.Indexes[key]; ok {
		return c.Items[i], true
	} else {
		return nil, false
	}
}

// Evict the next item, returning the key and value.
//
// If the cache is empty "" and nil are returned.
func (c *LIFOCache) EvictNext() (string, interface{}) {
	if len(c.Keys) > 0 {
		k := c.Keys[0]
		i := heap.Pop(c)
		return k, i
	} else {
		return "", nil
	}
}

// Evict items that are older than the given tm.
// That is the object's added time is less-than tm.
func (c *LIFOCache) EvictOlderThan(tm int64) {
	for len(c.AddedTime) > 0 && c.AddedTime[0] < tm {
		c.EvictNext()
	}
}

// Set the added time of an item and re-heap it.
func (c *LIFOCache) SetAddedTime(key string, tm int64) {
	if idx, ok := c.Indexes[key]; ok {
		c.AddedTime[idx] = tm
		heap.Fix(c, idx)
	}
}

// Remove the given key from the cache.
func (c *LIFOCache) Remove(key string) (interface{}, bool) {
	if i, ok := c.Indexes[key]; ok {
		obj := c.Items[i]

		lasti := len(c.Items) - 1

		// Put i at the end of the arrays.
		c.Swap(i, lasti)

		// Remove the last element.
		c.Keys = c.Keys[0:lasti]
		c.Items = c.Items[0:lasti]
		c.EvictionHandlers = c.EvictionHandlers[0:lasti]
		c.AddedTime = c.AddedTime[0:lasti]

		delete(c.Indexes, key)

		// Fix i.
		heap.Fix(c, i)

		// Return it.
		return obj, true
	} else {
		return nil, false
	}
}

// Return the next key to be returned by a call to EvictNext().
func (c *LIFOCache) MinKey() string {
	return c.Keys[0]
}

// Return the time of the next key and item to be returned by a call to EvictNext().
func (c *LIFOCache) MinTime() int64 {
	return c.AddedTime[0]
}

// Return next item to be returned by a call to EvictNext().
func (c *LIFOCache) MinItem() interface{} {
	return c.Items[0]
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package runtime

import (
	"net/url"
	"strconv"
	"sync"

	"github.com/pkg/errors"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

const (
	// FeatureGateFlag is a name of a command line flag, which turns on specific tests for FeatureGates
	FeatureGateFlag = "feature-gates"

	// FeatureExample is an example feature gate flag, used for testing and demonstrative purposes
	FeatureExample Feature = "Example"

	// FeaturePlayerTracking is a feature flag to enable/disable player tracking features.
	FeaturePlayerTracking Feature = "PlayerTracking"

	// FeatureSDKWatchSendOnExecute is a feature flag to enable/disable immediate game server return after SDK.WatchGameServer is called
	FeatureSDKWatchSendOnExecute Feature = "SDKWatchSendOnExecute"

	// FeatureRollingUpdateOnReady is a feature flag to enable/disable rolling update fix of scale down, when ReadyReplicas
	// count is taken into account
	FeatureRollingUpdateOnReady Feature = "RollingUpdateOnReady"

	// NodeExternalDNS is a feature flag to enable/disable node ExternalDNS and InternalDNS use as GameServer address
	NodeExternalDNS Feature = "NodeExternalDNS"
)

var (
	// featureDefaults is a map of all Feature Gates that are
	// operational in Agones, and what their default configuration is.
	// alpha features are disabled.
	featureDefaults = map[Feature]bool{
		FeatureExample:               true,
		FeaturePlayerTracking:        false,
		FeatureSDKWatchSendOnExecute: true,
		FeatureRollingUpdateOnReady:  true,
		NodeExternalDNS:              false,
	}

	// featureGates is the storage of what features are enabled
	// or disabled.
	featureGates map[Feature]bool

	// featureMutex ensures that updates to featureGates don't happen at the same time as reads.
	// this is mostly to protect tests which can change gates in parallel.
	featureMutex = sync.RWMutex{}

	// FeatureTestMutex is a mutex to be shared between tests to ensure that a test that involves changing featureGates
	// cannot accidentally run at the same time as another test that also changing feature flags.
	FeatureTestMutex sync.Mutex
)

// Feature is a type for defining feature gates.
type Feature string

// FeaturesBindFlags does the Viper arguments configuration. Call before running pflag.Parse()
func FeaturesBindFlags() {
	viper.SetDefault(FeatureGateFlag, "")
	pflag.String(FeatureGateFlag, viper.GetString(FeatureGateFlag), "Flag to pass in the url query list of feature flags to enable or disable")
}

// FeaturesBindEnv binds the environment variables, based on the flags provided.
// call after viper.SetEnvKeyReplacer(...) if it is being set.
func FeaturesBindEnv() error {
	return viper.BindEnv(FeatureGateFlag)
}

// ParseFeaturesFromEnv will parse the feature flags from the Viper args
// configured by FeaturesBindFlags() and FeaturesBindEnv()
func ParseFeaturesFromEnv() error {
	return ParseFeatures(viper.GetString(FeatureGateFlag))
}

// ParseFeatures parses the url encoded query string of features and stores the value
// for later retrieval
func ParseFeatures(queryString string) error {
	featureMutex.Lock()
	defer featureMutex.Unlock()

	features := map[Feature]bool{}
	// copy the defaults into this map
	for k, v := range featureDefaults {
		features[k] = v
	}

	values, err := url.ParseQuery(queryString)
	if err != nil {
		return errors.Wrap(err, "error parsing query string for feature gates")
	}

	for k := range values {
		f := Feature(k)

		if _, ok := featureDefaults[f]; !ok {
			return errors.Errorf("Feature Gate %q is not a valid Feature Gate", f)
		}

		b, err := strconv.ParseBool(values.Get(k))
		if err != nil {
			return errors.Wrapf(err, "error parsing bool value from flag %s ", k)
		}
		features[f] = b
	}

	featureGates = features
	return nil
}

// EnableAllFeatures turns on all feature flags.
// This is useful for libraries/processes/tests that want to
// enable all Alpha/Beta features without having to track all
// the current feature flags.
func EnableAllFeatures() {
	featureMutex.Lock()
	defer featureMutex.Unlock()

	features := map[Feature]bool{}
	// copy the defaults into this map
	for k := range featureDefaults {
		features[k] = true
	}

	featureGates = features
}

// FeatureEnabled returns if a Feature is enabled or not
func FeatureEnabled(feature Feature) bool {
	featureMutex.RLock()
	defer featureMutex.RUnlock()
	return featureGates[feature]
}

// EncodeFeatures returns the feature set as a URL encoded query string
func EncodeFeatures() string {
	values := url.Values{}
	featureMutex.RLock()
	defer featureMutex.RUnlock()

	for k, v := range featureGates {
		values.Add(string(k), strconv.FormatBool(v))
	}
	return values.Encode()
}
<commit_msg>Feature gates for advanced Allocation filtering (#2143)<commit_after>// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package runtime

import (
	"net/url"
	"strconv"
	"sync"

	"github.com/pkg/errors"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

const (
	// FeatureGateFlag is a name of a command line flag, which turns on specific tests for FeatureGates
	FeatureGateFlag = "feature-gates"

	// FeatureExample is an example feature gate flag, used for testing and demonstrative purposes
	FeatureExample Feature = "Example"

	// FeaturePlayerTracking is a feature flag to enable/disable player tracking features.
	FeaturePlayerTracking Feature = "PlayerTracking"

	// FeatureSDKWatchSendOnExecute is a feature flag to enable/disable immediate game server return after SDK.WatchGameServer is called
	FeatureSDKWatchSendOnExecute Feature = "SDKWatchSendOnExecute"

	// FeatureRollingUpdateOnReady is a feature flag to enable/disable rolling update fix of scale down, when ReadyReplicas
	// count is taken into account
	FeatureRollingUpdateOnReady Feature = "RollingUpdateOnReady"

	// NodeExternalDNS is a feature flag to enable/disable node ExternalDNS and InternalDNS use as GameServer address
	NodeExternalDNS Feature = "NodeExternalDNS"

	// FeatureStateAllocationFilter is a feature flag that enables state filtering on Allocation.
	FeatureStateAllocationFilter Feature = "StateAllocationFilter"

	// FeaturePlayerAllocationFilter is a feature flag that enables the ability for Allocations to filter based on
	// player capacity.
	FeaturePlayerAllocationFilter Feature = "PlayerAllocationFilter"
)

var (
	// featureDefaults is a map of all Feature Gates that are
	// operational in Agones, and what their default configuration is.
	// alpha features are disabled.
	featureDefaults = map[Feature]bool{
		FeatureExample:                true,
		FeaturePlayerTracking:         false,
		FeatureSDKWatchSendOnExecute:  true,
		FeatureRollingUpdateOnReady:   true,
		NodeExternalDNS:               false,
		FeatureStateAllocationFilter:  false,
		FeaturePlayerAllocationFilter: false,
	}

	// featureGates is the storage of what features are enabled
	// or disabled.
	featureGates map[Feature]bool

	// featureMutex ensures that updates to featureGates don't happen at the same time as reads.
	// this is mostly to protect tests which can change gates in parallel.
	featureMutex = sync.RWMutex{}

	// FeatureTestMutex is a mutex to be shared between tests to ensure that a test that involves changing featureGates
	// cannot accidentally run at the same time as another test that also changing feature flags.
	FeatureTestMutex sync.Mutex
)

// Feature is a type for defining feature gates.
type Feature string

// FeaturesBindFlags does the Viper arguments configuration. Call before running pflag.Parse()
func FeaturesBindFlags() {
	viper.SetDefault(FeatureGateFlag, "")
	pflag.String(FeatureGateFlag, viper.GetString(FeatureGateFlag), "Flag to pass in the url query list of feature flags to enable or disable")
}

// FeaturesBindEnv binds the environment variables, based on the flags provided.
// call after viper.SetEnvKeyReplacer(...) if it is being set.
func FeaturesBindEnv() error {
	return viper.BindEnv(FeatureGateFlag)
}

// ParseFeaturesFromEnv will parse the feature flags from the Viper args
// configured by FeaturesBindFlags() and FeaturesBindEnv()
func ParseFeaturesFromEnv() error {
	return ParseFeatures(viper.GetString(FeatureGateFlag))
}

// ParseFeatures parses the url encoded query string of features and stores the value
// for later retrieval
func ParseFeatures(queryString string) error {
	featureMutex.Lock()
	defer featureMutex.Unlock()

	features := map[Feature]bool{}
	// copy the defaults into this map
	for k, v := range featureDefaults {
		features[k] = v
	}

	values, err := url.ParseQuery(queryString)
	if err != nil {
		return errors.Wrap(err, "error parsing query string for feature gates")
	}

	for k := range values {
		f := Feature(k)

		if _, ok := featureDefaults[f]; !ok {
			return errors.Errorf("Feature Gate %q is not a valid Feature Gate", f)
		}

		b, err := strconv.ParseBool(values.Get(k))
		if err != nil {
			return errors.Wrapf(err, "error parsing bool value from flag %s ", k)
		}
		features[f] = b
	}

	featureGates = features
	return nil
}

// EnableAllFeatures turns on all feature flags.
// This is useful for libraries/processes/tests that want to
// enable all Alpha/Beta features without having to track all
// the current feature flags.
func EnableAllFeatures() {
	featureMutex.Lock()
	defer featureMutex.Unlock()

	features := map[Feature]bool{}
	// copy the defaults into this map
	for k := range featureDefaults {
		features[k] = true
	}

	featureGates = features
}

// FeatureEnabled returns if a Feature is enabled or not
func FeatureEnabled(feature Feature) bool {
	featureMutex.RLock()
	defer featureMutex.RUnlock()
	return featureGates[feature]
}

// EncodeFeatures returns the feature set as a URL encoded query string
func EncodeFeatures() string {
	values := url.Values{}
	featureMutex.RLock()
	defer featureMutex.RUnlock()

	for k, v := range featureGates {
		values.Add(string(k), strconv.FormatBool(v))
	}
	return values.Encode()
}
<|endoftext|>


<commit_before>// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Repository contents API methods.
// http://developer.github.com/v3/repos/contents/

package github

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

// RepositoryContent represents a file or directory in a github repository.
type RepositoryContent struct {
	Type     *string `json:"type,omitempty"`
	Encoding *string `json:"encoding,omitempty"`
	Size     *int    `json:"size,omitempty"`
	Name     *string `json:"name,omitempty"`
	Path     *string `json:"path,omitempty"`
	Content  *string `json:"content,omitempty"`
	SHA      *string `json:"sha,omitempty"`
	URL      *string `json:"url,omitempty"`
	GitURL   *string `json:"git_url,omitempty"`
	HTMLURL  *string `json:"html_url,omitempty"`
}

// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentResponse struct {
	Content *RepositoryContent `json:"content,omitempty"`
	Commit  `json:"commit,omitempty"`
}

// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentFileOptions struct {
	Message   *string       `json:"message,omitempty"`
	Content   []byte        `json:"content,omitempty"`
	SHA       *string       `json:"sha,omitempty"`
	Branch    *string       `json:"branch,omitempty"`
	Author    *CommitAuthor `json:"author,omitempty"`
	Committer *CommitAuthor `json:"committer,omitempty"`
}

// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA,
// branch, or tag
type RepositoryContentGetOptions struct {
	Ref string `url:"ref,omitempty"`
}

func (r RepositoryContent) String() string {
	return Stringify(r)
}

// Decode decodes the file content if it is base64 encoded.
func (r *RepositoryContent) Decode() ([]byte, error) {
	if *r.Encoding != "base64" {
		return nil, errors.New("cannot decode non-base64")
	}
	o, err := base64.StdEncoding.DecodeString(*r.Content)
	if err != nil {
		return nil, err
	}
	return o, nil
}

// GetReadme gets the Readme file for the repository.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-the-readme
func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	readme := new(RepositoryContent)
	resp, err := s.client.Do(req, readme)
	if err != nil {
		return nil, resp, err
	}
	return readme, resp, err
}

// GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it
// easy to distinguish between both result types and to mimic the API as much
// as possible, both result types will be returned but only one will contain a
// value and the other will be nil.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-contents
func (s *RepositoriesService) GetContents(owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent,
	directoryContent []*RepositoryContent, resp *Response, err error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	u, err = addOptions(u, opt)
	if err != nil {
		return nil, nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, nil, err
	}
	var rawJSON json.RawMessage
	resp, err = s.client.Do(req, &rawJSON)
	if err != nil {
		return nil, nil, resp, err
	}
	fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
	if fileUnmarshalError == nil {
		return fileContent, nil, resp, fileUnmarshalError
	}
	directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
	if directoryUnmarshalError == nil {
		return nil, directoryContent, resp, directoryUnmarshalError
	}
	return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError)
}

// CreateFile creates a new file in a repository at the given path and returns
// the commit and file metadata.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#create-a-file
func (s *RepositoriesService) CreateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	createResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, createResponse)
	if err != nil {
		return nil, resp, err
	}
	return createResponse, resp, err
}

// UpdateFile updates a file in a repository at the given path and returns the
// commit and file metadata. Requires the blob SHA of the file being updated.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#update-a-file
func (s *RepositoriesService) UpdateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	updateResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, updateResponse)
	if err != nil {
		return nil, resp, err
	}
	return updateResponse, resp, err
}

// DeleteFile deletes a file from a repository and returns the commit.
// Requires the blob SHA of the file to be deleted.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#delete-a-file
func (s *RepositoriesService) DeleteFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("DELETE", u, opt)
	if err != nil {
		return nil, nil, err
	}
	deleteResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, deleteResponse)
	if err != nil {
		return nil, resp, err
	}
	return deleteResponse, resp, err
}

// archiveFormat is used to define the archive type when calling GetArchiveLink.
type archiveFormat string

const (
	// Tarball specifies an archive in gzipped tar format.
	Tarball archiveFormat = "tarball"

	// Zipball specifies an archive in zip format.
	Zipball archiveFormat = "zipball"
)

// GetArchiveLink returns an URL to download a tarball or zipball archive for a
// repository. The archiveFormat can be specified by either the github.Tarball
// or github.Zipball constant.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-archive-link
func (s *RepositoriesService) GetArchiveLink(owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
	if opt != nil && opt.Ref != "" {
		u += fmt.Sprintf("/%s", opt.Ref)
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	var resp *http.Response
	// Use http.DefaultTransport if no custom Transport is configured
	if s.client.client.Transport == nil {
		resp, err = http.DefaultTransport.RoundTrip(req)
	} else {
		resp, err = s.client.client.Transport.RoundTrip(req)
	}
	if err != nil || resp.StatusCode != http.StatusFound {
		return nil, newResponse(resp), err
	}
	parsedURL, err := url.Parse(resp.Header.Get("Location"))
	return parsedURL, newResponse(resp), err
}
<commit_msg>Add DownloadURL field to RepositoryContent struct<commit_after>// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Repository contents API methods.
// http://developer.github.com/v3/repos/contents/

package github

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

// RepositoryContent represents a file or directory in a github repository.
type RepositoryContent struct {
	Type        *string `json:"type,omitempty"`
	Encoding    *string `json:"encoding,omitempty"`
	Size        *int    `json:"size,omitempty"`
	Name        *string `json:"name,omitempty"`
	Path        *string `json:"path,omitempty"`
	Content     *string `json:"content,omitempty"`
	SHA         *string `json:"sha,omitempty"`
	URL         *string `json:"url,omitempty"`
	GitURL      *string `json:"git_url,omitempty"`
	HTMLURL     *string `json:"html_url,omitempty"`
	DownloadURL *string `json:"download_url,omitempty"`
}

// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentResponse struct {
	Content *RepositoryContent `json:"content,omitempty"`
	Commit  `json:"commit,omitempty"`
}

// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentFileOptions struct {
	Message   *string       `json:"message,omitempty"`
	Content   []byte        `json:"content,omitempty"`
	SHA       *string       `json:"sha,omitempty"`
	Branch    *string       `json:"branch,omitempty"`
	Author    *CommitAuthor `json:"author,omitempty"`
	Committer *CommitAuthor `json:"committer,omitempty"`
}

// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA,
// branch, or tag
type RepositoryContentGetOptions struct {
	Ref string `url:"ref,omitempty"`
}

func (r RepositoryContent) String() string {
	return Stringify(r)
}

// Decode decodes the file content if it is base64 encoded.
func (r *RepositoryContent) Decode() ([]byte, error) {
	if *r.Encoding != "base64" {
		return nil, errors.New("cannot decode non-base64")
	}
	o, err := base64.StdEncoding.DecodeString(*r.Content)
	if err != nil {
		return nil, err
	}
	return o, nil
}

// GetReadme gets the Readme file for the repository.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-the-readme
func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	readme := new(RepositoryContent)
	resp, err := s.client.Do(req, readme)
	if err != nil {
		return nil, resp, err
	}
	return readme, resp, err
}

// GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it
// easy to distinguish between both result types and to mimic the API as much
// as possible, both result types will be returned but only one will contain a
// value and the other will be nil.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-contents
func (s *RepositoriesService) GetContents(owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent,
	directoryContent []*RepositoryContent, resp *Response, err error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	u, err = addOptions(u, opt)
	if err != nil {
		return nil, nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, nil, err
	}
	var rawJSON json.RawMessage
	resp, err = s.client.Do(req, &rawJSON)
	if err != nil {
		return nil, nil, resp, err
	}
	fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
	if fileUnmarshalError == nil {
		return fileContent, nil, resp, fileUnmarshalError
	}
	directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
	if directoryUnmarshalError == nil {
		return nil, directoryContent, resp, directoryUnmarshalError
	}
	return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError)
}

// CreateFile creates a new file in a repository at the given path and returns
// the commit and file metadata.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#create-a-file
func (s *RepositoriesService) CreateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	createResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, createResponse)
	if err != nil {
		return nil, resp, err
	}
	return createResponse, resp, err
}

// UpdateFile updates a file in a repository at the given path and returns the
// commit and file metadata. Requires the blob SHA of the file being updated.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#update-a-file
func (s *RepositoriesService) UpdateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	updateResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, updateResponse)
	if err != nil {
		return nil, resp, err
	}
	return updateResponse, resp, err
}

// DeleteFile deletes a file from a repository and returns the commit.
// Requires the blob SHA of the file to be deleted.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#delete-a-file
func (s *RepositoriesService) DeleteFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("DELETE", u, opt)
	if err != nil {
		return nil, nil, err
	}
	deleteResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, deleteResponse)
	if err != nil {
		return nil, resp, err
	}
	return deleteResponse, resp, err
}

// archiveFormat is used to define the archive type when calling GetArchiveLink.
type archiveFormat string

const (
	// Tarball specifies an archive in gzipped tar format.
	Tarball archiveFormat = "tarball"

	// Zipball specifies an archive in zip format.
	Zipball archiveFormat = "zipball"
)

// GetArchiveLink returns an URL to download a tarball or zipball archive for a
// repository. The archiveFormat can be specified by either the github.Tarball
// or github.Zipball constant.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-archive-link
func (s *RepositoriesService) GetArchiveLink(owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
	if opt != nil && opt.Ref != "" {
		u += fmt.Sprintf("/%s", opt.Ref)
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	var resp *http.Response
	// Use http.DefaultTransport if no custom Transport is configured
	if s.client.client.Transport == nil {
		resp, err = http.DefaultTransport.RoundTrip(req)
	} else {
		resp, err = s.client.client.Transport.RoundTrip(req)
	}
	if err != nil || resp.StatusCode != http.StatusFound {
		return nil, newResponse(resp), err
	}
	parsedURL, err := url.Parse(resp.Header.Get("Location"))
	return parsedURL, newResponse(resp), err
}
<|endoftext|>


<commit_before>// Copyright 2014 tsuru-admin authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"github.com/tsuru/tsuru/cmd"
	tTesting "github.com/tsuru/tsuru/testing"
	"launchpad.net/gocheck"
	"os"
	"testing"
)

type S struct {
	recover []string
}

var manager *cmd.Manager

func (s *S) SetUpSuite(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	manager = cmd.NewManager("glb", version, header, &stdout, &stderr, os.Stdin, nil)
	s.recover = tTesting.SetTargetFile(c)
}

func (s *S) TearDownSuite(c *gocheck.C) {
	tTesting.RollbackTargetFile(s.recover)
}

var _ = gocheck.Suite(&S{})

func Test(t *testing.T) { gocheck.TestingT(t) }

type AdminCommandableProvisioner struct {
	tTesting.FakeProvisioner
}

func (p *AdminCommandableProvisioner) AdminCommands() []cmd.Command {
	return []cmd.Command{&FakeAdminCommand{}}
}

type FakeAdminCommand struct{}

func (c *FakeAdminCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:  "fake-admin",
		Usage: "fake usage",
		Desc:  "fake desc",
	}
}

func (c *FakeAdminCommand) Run(*cmd.Context, *cmd.Client) error {
	return nil
}
<commit_msg>suite_test: fix build<commit_after>// Copyright 2014 tsuru-admin authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"github.com/tsuru/tsuru/cmd"
	tTesting "github.com/tsuru/tsuru/testing"
	"launchpad.net/gocheck"
	"os"
	"testing"
)

type S struct {
	recover []string
}

var manager *cmd.Manager

func (s *S) SetUpSuite(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	manager = cmd.NewManager("glb", version, header, &stdout, &stderr, os.Stdin, nil)
	s.recover = tTesting.SetTargetFile(c, []byte("http://localhost"))
}

func (s *S) TearDownSuite(c *gocheck.C) {
	tTesting.RollbackFile(s.recover)
}

var _ = gocheck.Suite(&S{})

func Test(t *testing.T) { gocheck.TestingT(t) }

type AdminCommandableProvisioner struct {
	tTesting.FakeProvisioner
}

func (p *AdminCommandableProvisioner) AdminCommands() []cmd.Command {
	return []cmd.Command{&FakeAdminCommand{}}
}

type FakeAdminCommand struct{}

func (c *FakeAdminCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:  "fake-admin",
		Usage: "fake usage",
		Desc:  "fake desc",
	}
}

func (c *FakeAdminCommand) Run(*cmd.Context, *cmd.Client) error {
	return nil
}
<|endoftext|>


<commit_before>package command

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

// Options defines optional arguments for ShellRunner.RunWith().
type Options struct {

	// Dir contains the directory in which to execute the command.
	// Runs in the current directory if this option is not provided.
	Dir string

	// Env allows to override the environment variables to use in the subshell, in the format provided by os.Environ()
	// Uses the environment variables of this process if this option is not provided.
	Env []string

	// Essential indicates whether this is an essential command.
	// Essential commands are critically important for Git Town to function., if they fail Git Town ends right there.
	Essential bool

	// Input contains the user input to enter into the running command.
	// Input is written to the subprocess one element at a time,
	// with a delay defined by command.InputDelay in between.
	Input []string // input into the subprocess
}

// InputDelay defines how long to wait before writing the next input string into the subprocess.
const InputDelay = 50 * time.Millisecond

// MustRun executes an essential subshell command given in argv notation.
// Essential subshell commands are essential for the functioning of Git Town.
// If they fail, Git Town ends right there.
func MustRun(cmd string, args ...string) *Result {
	result, _ := RunWith(Options{Essential: true}, cmd, args...)
	return result
}

// MustRunInDir executes an essential subshell command given in argv notation.
// Essential subshell commands are essential for the functioning of Git Town.
// If they fail, Git Town ends right there.
func MustRunInDir(dir string, cmd string, args ...string) *Result {
	result, _ := RunWith(Options{Dir: dir, Essential: true}, cmd, args...)
	return result
}

// Run executes the command given in argv notation.
func Run(cmd string, args ...string) (*Result, error) {
	return RunWith(Options{}, cmd, args...)
}

// RunInDir executes the given command in the given directory.
func RunInDir(dir string, cmd string, args ...string) (*Result, error) {
	return RunWith(Options{Dir: dir}, cmd, args...)
}

// RunWith runs the command with the given RunOptions.
func RunWith(opts Options, cmd string, args ...string) (*Result, error) {
	logRun(cmd, args...)
	subProcess := exec.Command(cmd, args...) // #nosec
	if opts.Dir != "" {
		subProcess.Dir = opts.Dir
	}
	if opts.Env != nil {
		subProcess.Env = opts.Env
	}
	var output bytes.Buffer
	subProcess.Stdout = &output
	subProcess.Stderr = &output
	result := Result{command: cmd, args: args}
	input, err := subProcess.StdinPipe()
	if err != nil {
		return &result, err
	}
	err = subProcess.Start()
	if err != nil {
		return &result, fmt.Errorf("can't start subprocess '%s %s': %w", cmd, strings.Join(args, " "), err)
	}
	for _, userInput := range opts.Input {
		// Here we simply wait for some time until the subProcess needs the input.
		// Capturing the output and scanning for the actual content needed
		// would introduce substantial amounts of multi-threaded complexity
		// for not enough gains.
		// https://github.com/Originate/go-execplus could help make this more robust.
		time.Sleep(InputDelay)
		_, err := input.Write([]byte(userInput))
		if err != nil {
			result.output = output.String()
			return &result, fmt.Errorf("can't write %q to subprocess '%s %s': %w", userInput, cmd, strings.Join(args, " "), err)
		}
	}
	err = subProcess.Wait()
	if opts.Essential && err != nil {
		fmt.Printf("\n\nError running '%s %s' in %q: %s", cmd, strings.Join(args, " "), subProcess.Dir, err)
		os.Exit(1)
	}
	result.output = output.String()
	return &result, err
}
<commit_msg>Improve comments (#1287)<commit_after>package command

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

// Options defines optional arguments for ShellRunner.RunWith().
type Options struct {

	// Dir contains the directory in which to execute the command.
	// If empty, runs in the current directory.
	Dir string

	// Env allows to override the environment variables to use in the subshell, in the format provided by os.Environ()
	// If empty, uses the environment variables of this process.
	Env []string

	// Essential indicates whether this is an essential command.
	// Essential commands are critically important for Git Town to function. If they fail Git Town ends right there.
	Essential bool

	// Input contains the user input to enter into the running command.
	// It is written to the subprocess one element at a time, with a delay defined by command.InputDelay in between.
	Input []string // input into the subprocess
}

// InputDelay defines how long to wait before writing the next input string into the subprocess.
const InputDelay = 50 * time.Millisecond

// MustRun executes an essential subshell command given in argv notation.
// Essential subshell commands are essential for the functioning of Git Town.
// If they fail, Git Town ends right there.
func MustRun(cmd string, args ...string) *Result {
	result, _ := RunWith(Options{Essential: true}, cmd, args...)
	return result
}

// MustRunInDir executes an essential subshell command given in argv notation.
// Essential subshell commands are essential for the functioning of Git Town.
// If they fail, Git Town ends right there.
func MustRunInDir(dir string, cmd string, args ...string) *Result {
	result, _ := RunWith(Options{Dir: dir, Essential: true}, cmd, args...)
	return result
}

// Run executes the command given in argv notation.
func Run(cmd string, args ...string) (*Result, error) {
	return RunWith(Options{}, cmd, args...)
}

// RunInDir executes the given command in the given directory.
func RunInDir(dir string, cmd string, args ...string) (*Result, error) {
	return RunWith(Options{Dir: dir}, cmd, args...)
}

// RunWith runs the command with the given RunOptions.
func RunWith(opts Options, cmd string, args ...string) (*Result, error) {
	logRun(cmd, args...)
	subProcess := exec.Command(cmd, args...) // #nosec
	if opts.Dir != "" {
		subProcess.Dir = opts.Dir
	}
	if opts.Env != nil {
		subProcess.Env = opts.Env
	}
	var output bytes.Buffer
	subProcess.Stdout = &output
	subProcess.Stderr = &output
	result := Result{command: cmd, args: args}
	input, err := subProcess.StdinPipe()
	if err != nil {
		return &result, err
	}
	err = subProcess.Start()
	if err != nil {
		return &result, fmt.Errorf("can't start subprocess '%s %s': %w", cmd, strings.Join(args, " "), err)
	}
	for _, userInput := range opts.Input {
		// Here we simply wait for some time until the subProcess needs the input.
		// Capturing the output and scanning for the actual content needed
		// would introduce substantial amounts of multi-threaded complexity
		// for not enough gains.
		// https://github.com/Originate/go-execplus could help make this more robust.
		time.Sleep(InputDelay)
		_, err := input.Write([]byte(userInput))
		if err != nil {
			result.output = output.String()
			return &result, fmt.Errorf("can't write %q to subprocess '%s %s': %w", userInput, cmd, strings.Join(args, " "), err)
		}
	}
	err = subProcess.Wait()
	if opts.Essential && err != nil {
		fmt.Printf("\n\nError running '%s %s' in %q: %s", cmd, strings.Join(args, " "), subProcess.Dir, err)
		os.Exit(1)
	}
	result.output = output.String()
	return &result, err
}
<|endoftext|>


<commit_before>// Package editor provides a graphical, editable text area widget.
package editor // import "sigint.ca/graphics/editor"

import (
	"image"
	"time"

	"sigint.ca/clip"
	"sigint.ca/graphics/editor/internal/hist"
	"sigint.ca/graphics/editor/internal/text"

	"golang.org/x/image/font"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"
)

// An Editor is a graphical, editable text area widget, intended to be
// compatible with golang.org/x/exp/shiny, or any other graphical
// window package capable of drawing a widget via an image.RGBA.
// See sigint.ca/cmd/edit for an example program using this type.
type Editor struct {
	// textual state
	buf *text.Buffer
	dot text.Selection // the current selection

	// images and drawing data
	img      *image.RGBA
	dirty    bool
	scrollPt image.Point // the part of the image in view

	// configurable
	bgcol      *image.Uniform
	selcol     *image.Uniform
	cursor     image.Image // the cursor to draw when nothing is selected
	font       fontface
	lineHeight int
	margin     image.Point

	// history
	history     *hist.History        // represents the Editor's history
	savePoint   *hist.Transformation // records the last time the Editor was saved, for use by Saved and SetSaved
	uncommitted *hist.Transformation // recent input which hasn't yet been committed to history

	// mouse related state
	lastClickTime time.Time    // used to detect a double-click
	mPos          image.Point  // the position of the most recent mouse event
	mSweepOrigin  text.Address // keeps track of the origin of a sweep

	clipboard *clip.Clipboard // the clipboard to be used for copy or paste events
}

// NewEditor returns a new Editor with a clipping rectangle defined by size, a font face
// defined by face and height, and an OptionSet opt.
func NewEditor(size image.Point, face font.Face, height int, opt OptionSet) *Editor {
	ed := &Editor{
		buf: text.NewBuffer(),

		img:   image.NewRGBA(image.Rectangle{Max: size}),
		dirty: true,

		bgcol:      image.NewUniform(opt.BGColor),
		selcol:     image.NewUniform(opt.SelColor),
		cursor:     opt.Cursor(height),
		font:       fontface{face: face, height: height - 3},
		lineHeight: height,
		margin:     opt.Margin,

		history:   new(hist.History),
		clipboard: new(clip.Clipboard),
	}
	return ed
}

func (ed *Editor) Release() {
}

func (ed *Editor) Bounds() image.Rectangle {
	return ed.img.Bounds()
}

func (ed *Editor) Size() image.Point {
	return ed.Bounds().Size()
}

// Resize resizes the Editor. Subsequent calls to RGBA will return an image of
// at least size, and a clipping rectangle of size.
func (ed *Editor) Resize(size image.Point) {
	r := image.Rectangle{Max: size}
	ed.img = image.NewRGBA(r)
}

// RGBA returns an image representing the current state of the Editor. The image
// may be larger than the rectangle returned by Bounds, which represents
// the portion of the image currently scrolled into view.
func (ed *Editor) RGBA() (img *image.RGBA) {
	if ed.dirty {
		ed.redraw()
		ed.dirty = false
	}
	return ed.img
}

// Dirty reports whether the next call to RGBA will result in a different
// image than the previous call
func (ed *Editor) Dirty() bool {
	return ed.dirty
}

// Contents returns the contents of the Editor.
func (ed *Editor) Contents() []byte {
	return ed.buf.Contents()
}

// Load replaces the contents of the Editor with s, and
// resets the Editor's history.
func (ed *Editor) Load(s []byte) {
	ed.buf.ClearSel(ed.dot)
	ed.buf.InsertString(text.Address{0, 0}, string(s))
	ed.history = new(hist.History)
	ed.dot = text.Selection{}
}

// SetSaved instructs the Editor that the current contents should be
// considered saved. After calling SetSaved, the client can call
// Saved to see if the Editor has unsaved content.
func (ed *Editor) SetSaved() {
	// TODO: ensure ed.uncommitted is empty?
	if ed.uncommitted != nil {
		panic("TODO")
	}
	ed.savePoint = ed.history.Current()
}

// Saved reports whether the Editor has been modified since the last
// time SetSaved was called.
func (ed *Editor) Saved() bool {
	return ed.history.Current() == ed.savePoint && ed.uncommitted == nil
}

// SendKeyEvent sends a key event to be interpreted by the Editor.
func (ed *Editor) SendKeyEvent(e key.Event) {
	ed.handleKeyEvent(e)
}

// SendMouseEvent sends a mouse event to be interpreted by the Editor.
func (ed *Editor) SendMouseEvent(e mouse.Event) {
	ed.handleMouseEvent(e)
}

// SendScrollEvent sends a scroll event to be interpreted by the Editor.
func (ed *Editor) SendScrollEvent(e mouse.ScrollEvent) {
	var pt image.Point
	if e.Precise {
		pt.X = int(e.Dx)
		pt.Y = int(e.Dy)
	} else {
		pt.X = int(e.Dx * float32(ed.lineHeight))
		pt.Y = int(e.Dy * float32(ed.lineHeight))
	}
	oldPt := ed.scrollPt
	ed.scroll(pt)
	if ed.scrollPt != oldPt {
		ed.dirty = true
	}
}
<commit_msg>editor: add missing period<commit_after>// Package editor provides a graphical, editable text area widget.
package editor // import "sigint.ca/graphics/editor"

import (
	"image"
	"time"

	"sigint.ca/clip"
	"sigint.ca/graphics/editor/internal/hist"
	"sigint.ca/graphics/editor/internal/text"

	"golang.org/x/image/font"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"
)

// An Editor is a graphical, editable text area widget, intended to be
// compatible with golang.org/x/exp/shiny, or any other graphical
// window package capable of drawing a widget via an image.RGBA.
// See sigint.ca/cmd/edit for an example program using this type.
type Editor struct {
	// textual state
	buf *text.Buffer
	dot text.Selection // the current selection

	// images and drawing data
	img      *image.RGBA
	dirty    bool
	scrollPt image.Point // the part of the image in view

	// configurable
	bgcol      *image.Uniform
	selcol     *image.Uniform
	cursor     image.Image // the cursor to draw when nothing is selected
	font       fontface
	lineHeight int
	margin     image.Point

	// history
	history     *hist.History        // represents the Editor's history
	savePoint   *hist.Transformation // records the last time the Editor was saved, for use by Saved and SetSaved
	uncommitted *hist.Transformation // recent input which hasn't yet been committed to history

	// mouse related state
	lastClickTime time.Time    // used to detect a double-click
	mPos          image.Point  // the position of the most recent mouse event
	mSweepOrigin  text.Address // keeps track of the origin of a sweep

	clipboard *clip.Clipboard // the clipboard to be used for copy or paste events
}

// NewEditor returns a new Editor with a clipping rectangle defined by size, a font face
// defined by face and height, and an OptionSet opt.
func NewEditor(size image.Point, face font.Face, height int, opt OptionSet) *Editor {
	ed := &Editor{
		buf: text.NewBuffer(),

		img:   image.NewRGBA(image.Rectangle{Max: size}),
		dirty: true,

		bgcol:      image.NewUniform(opt.BGColor),
		selcol:     image.NewUniform(opt.SelColor),
		cursor:     opt.Cursor(height),
		font:       fontface{face: face, height: height - 3},
		lineHeight: height,
		margin:     opt.Margin,

		history:   new(hist.History),
		clipboard: new(clip.Clipboard),
	}
	return ed
}

func (ed *Editor) Release() {
}

func (ed *Editor) Bounds() image.Rectangle {
	return ed.img.Bounds()
}

func (ed *Editor) Size() image.Point {
	return ed.Bounds().Size()
}

// Resize resizes the Editor. Subsequent calls to RGBA will return an image of
// at least size, and a clipping rectangle of size.
func (ed *Editor) Resize(size image.Point) {
	r := image.Rectangle{Max: size}
	ed.img = image.NewRGBA(r)
}

// RGBA returns an image representing the current state of the Editor. The image
// may be larger than the rectangle returned by Bounds, which represents
// the portion of the image currently scrolled into view.
func (ed *Editor) RGBA() (img *image.RGBA) {
	if ed.dirty {
		ed.redraw()
		ed.dirty = false
	}
	return ed.img
}

// Dirty reports whether the next call to RGBA will result in a different
// image than the previous call.
func (ed *Editor) Dirty() bool {
	return ed.dirty
}

// Contents returns the contents of the Editor.
func (ed *Editor) Contents() []byte {
	return ed.buf.Contents()
}

// Load replaces the contents of the Editor with s, and
// resets the Editor's history.
func (ed *Editor) Load(s []byte) {
	ed.buf.ClearSel(ed.dot)
	ed.buf.InsertString(text.Address{0, 0}, string(s))
	ed.history = new(hist.History)
	ed.dot = text.Selection{}
}

// SetSaved instructs the Editor that the current contents should be
// considered saved. After calling SetSaved, the client can call
// Saved to see if the Editor has unsaved content.
func (ed *Editor) SetSaved() {
	// TODO: ensure ed.uncommitted is empty?
	if ed.uncommitted != nil {
		panic("TODO")
	}
	ed.savePoint = ed.history.Current()
}

// Saved reports whether the Editor has been modified since the last
// time SetSaved was called.
func (ed *Editor) Saved() bool {
	return ed.history.Current() == ed.savePoint && ed.uncommitted == nil
}

// SendKeyEvent sends a key event to be interpreted by the Editor.
func (ed *Editor) SendKeyEvent(e key.Event) {
	ed.handleKeyEvent(e)
}

// SendMouseEvent sends a mouse event to be interpreted by the Editor.
func (ed *Editor) SendMouseEvent(e mouse.Event) {
	ed.handleMouseEvent(e)
}

// SendScrollEvent sends a scroll event to be interpreted by the Editor.
func (ed *Editor) SendScrollEvent(e mouse.ScrollEvent) {
	var pt image.Point
	if e.Precise {
		pt.X = int(e.Dx)
		pt.Y = int(e.Dy)
	} else {
		pt.X = int(e.Dx * float32(ed.lineHeight))
		pt.Y = int(e.Dy * float32(ed.lineHeight))
	}
	oldPt := ed.scrollPt
	ed.scroll(pt)
	if ed.scrollPt != oldPt {
		ed.dirty = true
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/spf13/cobra"

	"github.com/squaremo/ambergreen/common/store"
	"github.com/squaremo/ambergreen/common/data"
)

type selectOpts struct {
	store store.Store
	spec
}

func (opts *selectOpts) addCommandTo(top *cobra.Command) {
	cmd := &cobra.Command{
		Use:   "select <name> [options]",
		Short: "include instances in a service",
		Run:   opts.run,
	}
	opts.addSpecVars(cmd)
	top.AddCommand(cmd)
}

func (opts *selectOpts) run(_ *cobra.Command, args []string) {
	if len(args) < 2 {
		exitWithErrorf("You must supply <service> and <name>")
	}
	serviceName, name := args[0], args[1]
	service, err := opts.store.GetServiceDetails(serviceName)
	if err != nil {
		exitWithErrorf("Error fetching service: ", err)
	}

	spec, err := opts.makeSpec()
	if err != nil {
		exitWithErrorf("Unable to parse options into instance spec: ", err)
	}

	addInstanceSpec(&service, data.InstanceGroup(name), spec)
	if err = opts.store.AddService(serviceName, service); err != nil {
		exitWithErrorf("Error updating service: ", err)
	}
	fmt.Println("Selected instance group", name, "in service", serviceName)
}

func addInstanceSpec(service *data.Service, name data.InstanceGroup, spec *data.InstanceSpec) {
	specs := service.InstanceSpecs
	if specs == nil {
		specs = make(map[data.InstanceGroup]data.InstanceSpec)
		service.InstanceSpecs = specs
	}
	specs[name] = *spec
}
<commit_msg>Correct usage message for amberctl select<commit_after>package main

import (
	"fmt"

	"github.com/spf13/cobra"

	"github.com/squaremo/ambergreen/common/data"
	"github.com/squaremo/ambergreen/common/store"
)

type selectOpts struct {
	store store.Store
	spec
}

func (opts *selectOpts) addCommandTo(top *cobra.Command) {
	cmd := &cobra.Command{
		Use:   "select <service> <name> [options]",
		Short: "include instances in a service",
		Run:   opts.run,
	}
	opts.addSpecVars(cmd)
	top.AddCommand(cmd)
}

func (opts *selectOpts) run(_ *cobra.Command, args []string) {
	if len(args) < 2 {
		exitWithErrorf("You must supply <service> and <name>")
	}
	serviceName, name := args[0], args[1]
	service, err := opts.store.GetServiceDetails(serviceName)
	if err != nil {
		exitWithErrorf("Error fetching service: ", err)
	}

	spec, err := opts.makeSpec()
	if err != nil {
		exitWithErrorf("Unable to parse options into instance spec: ", err)
	}

	addInstanceSpec(&service, data.InstanceGroup(name), spec)
	if err = opts.store.AddService(serviceName, service); err != nil {
		exitWithErrorf("Error updating service: ", err)
	}
	fmt.Println("Selected instance group", name, "in service", serviceName)
}

func addInstanceSpec(service *data.Service, name data.InstanceGroup, spec *data.InstanceSpec) {
	specs := service.InstanceSpecs
	if specs == nil {
		specs = make(map[data.InstanceGroup]data.InstanceSpec)
		service.InstanceSpecs = specs
	}
	specs[name] = *spec
}
<|endoftext|>