<commit_before>/*
Copyright 2014 The Kubernetes Authors 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 validation

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
	"github.com/emicklei/go-restful/swagger"
	"github.com/golang/glog"
)

type InvalidTypeError struct {
	ExpectedKind reflect.Kind
	ObservedKind reflect.Kind
	FieldName    string
}

func (i *InvalidTypeError) Error() string {
	return fmt.Sprintf("expected type %s, for field %s, got %s", i.ExpectedKind.String(), i.FieldName, i.ObservedKind.String())
}

func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName string) error {
	return &InvalidTypeError{expected, observed, fieldName}
}

// Schema is an interface that knows how to validate an API object serialized to a byte array.
type Schema interface {
	ValidateBytes(data []byte) error
}

type NullSchema struct{}

func (NullSchema) ValidateBytes(data []byte) error { return nil }

type SwaggerSchema struct {
	api swagger.ApiDeclaration
}

func NewSwaggerSchemaFromBytes(data []byte) (Schema, error) {
	schema := &SwaggerSchema{}
	err := json.Unmarshal(data, &schema.api)
	if err != nil {
		return nil, err
	}
	return schema, nil
}

func (s *SwaggerSchema) ValidateBytes(data []byte) error {
	var obj interface{}
	out, err := yaml.ToJSON(data)
	if err != nil {
		return err
	}
	data = out
	if err := json.Unmarshal(data, &obj); err != nil {
		return err
	}
	fields, ok := obj.(map[string]interface{})
	if !ok {
		return fmt.Errorf("error in unmarshaling data %s", string(data))
	}
	apiVersion := fields["apiVersion"].(string)
	kind := fields["kind"].(string)
	return s.ValidateObject(obj, apiVersion, "", apiVersion+"."+kind)
}

func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, typeName string) error {
	models := s.api.Models
	// TODO: handle required fields here too.
	model, ok := models[typeName]
	if !ok {
		return fmt.Errorf("couldn't find type: %s", typeName)
	}
	properties := model.Properties
	if len(properties) == 0 {
		// The object does not have any sub-fields.
		return nil
	}
	fields, ok := obj.(map[string]interface{})
	if !ok {
		return fmt.Errorf("expected object of type map[string]interface{} as value of %s field", fieldName)
	}
	if len(fieldName) > 0 {
		fieldName = fieldName + "."
	}
	for key, value := range fields {
		details, ok := properties[key]
		if !ok {
			glog.Infof("unknown field: %s", key)
			// Some properties can be missing because of
			// https://github.com/GoogleCloudPlatform/kubernetes/issues/6842.
			glog.Info("this may be a false alarm, see https://github.com/GoogleCloudPlatform/kubernetes/issues/6842")
			continue
		}
		if details.Type == nil && details.Ref == nil {
			return fmt.Errorf("could not find the type of %s from object: %v", key, details)
		}
		var fieldType string
		if details.Type != nil {
			fieldType = *details.Type
		} else {
			fieldType = *details.Ref
		}
		if value == nil {
			glog.V(2).Infof("Skipping nil field: %s", key)
			continue
		}
		err := s.validateField(value, apiVersion, fieldName+key, fieldType, &details)
		if err != nil {
			glog.Errorf("Validation failed for: %s, %v", key, value)
			return err
		}
	}
	return nil
}

func (s *SwaggerSchema) validateField(value interface{}, apiVersion, fieldName, fieldType string, fieldDetails *swagger.ModelProperty) error {
	if strings.HasPrefix(fieldType, apiVersion) {
		return s.ValidateObject(value, apiVersion, fieldName, fieldType)
	}
	switch fieldType {
	case "string":
		// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
		_, isString := value.(string)
		_, isNumber := value.(float64)
		_, isInteger := value.(int)
		if !isString && !isNumber && !isInteger {
			return NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "array":
		arr, ok := value.([]interface{})
		if !ok {
			return NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName)
		}
		var arrType string
		glog.Infof("field detail %v", fieldDetails)
		if fieldDetails.Items.Ref == nil && fieldDetails.Items.Type == nil {
			return NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName)
		}
		if fieldDetails.Items.Ref != nil {
			arrType = *fieldDetails.Items.Ref
		} else {
			arrType = *fieldDetails.Items.Type
		}
		for ix := range arr {
			err := s.validateField(arr[ix], apiVersion, fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil)
			if err != nil {
				return err
			}
		}
	case "uint64":
	case "int64":
	case "integer":
		_, isNumber := value.(float64)
		_, isInteger := value.(int)
		if !isNumber && !isInteger {
			return NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "float64":
		if _, ok := value.(float64); !ok {
			return NewInvalidTypeError(reflect.Float64, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "boolean":
		if _, ok := value.(bool); !ok {
			return NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "any":
	default:
		return fmt.Errorf("unexpected type: %v", fieldType)
	}
	return nil
}
<commit_msg>Update go-restful to get stable sorting in spec<commit_after>/*
Copyright 2014 The Kubernetes Authors 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 validation

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
	"github.com/emicklei/go-restful/swagger"
	"github.com/golang/glog"
)

type InvalidTypeError struct {
	ExpectedKind reflect.Kind
	ObservedKind reflect.Kind
	FieldName    string
}

func (i *InvalidTypeError) Error() string {
	return fmt.Sprintf("expected type %s, for field %s, got %s", i.ExpectedKind.String(), i.FieldName, i.ObservedKind.String())
}

func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName string) error {
	return &InvalidTypeError{expected, observed, fieldName}
}

// Schema is an interface that knows how to validate an API object serialized to a byte array.
type Schema interface {
	ValidateBytes(data []byte) error
}

type NullSchema struct{}

func (NullSchema) ValidateBytes(data []byte) error { return nil }

type SwaggerSchema struct {
	api swagger.ApiDeclaration
}

func NewSwaggerSchemaFromBytes(data []byte) (Schema, error) {
	schema := &SwaggerSchema{}
	err := json.Unmarshal(data, &schema.api)
	if err != nil {
		return nil, err
	}
	return schema, nil
}

func (s *SwaggerSchema) ValidateBytes(data []byte) error {
	var obj interface{}
	out, err := yaml.ToJSON(data)
	if err != nil {
		return err
	}
	data = out
	if err := json.Unmarshal(data, &obj); err != nil {
		return err
	}
	fields, ok := obj.(map[string]interface{})
	if !ok {
		return fmt.Errorf("error in unmarshaling data %s", string(data))
	}
	apiVersion := fields["apiVersion"].(string)
	kind := fields["kind"].(string)
	return s.ValidateObject(obj, apiVersion, "", apiVersion+"."+kind)
}

func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, typeName string) error {
	models := s.api.Models
	// TODO: handle required fields here too.
	model, ok := models.At(typeName)
	if !ok {
		return fmt.Errorf("couldn't find type: %s", typeName)
	}
	properties := model.Properties
	if len(properties.List) == 0 {
		// The object does not have any sub-fields.
		return nil
	}
	fields, ok := obj.(map[string]interface{})
	if !ok {
		return fmt.Errorf("expected object of type map[string]interface{} as value of %s field", fieldName)
	}
	if len(fieldName) > 0 {
		fieldName = fieldName + "."
	}
	for key, value := range fields {
		details, ok := properties.At(key)
		if !ok {
			glog.Infof("unknown field: %s", key)
			// Some properties can be missing because of
			// https://github.com/GoogleCloudPlatform/kubernetes/issues/6842.
			glog.Info("this may be a false alarm, see https://github.com/GoogleCloudPlatform/kubernetes/issues/6842")
			continue
		}
		if details.Type == nil && details.Ref == nil {
			return fmt.Errorf("could not find the type of %s from object: %v", key, details)
		}
		var fieldType string
		if details.Type != nil {
			fieldType = *details.Type
		} else {
			fieldType = *details.Ref
		}
		if value == nil {
			glog.V(2).Infof("Skipping nil field: %s", key)
			continue
		}
		err := s.validateField(value, apiVersion, fieldName+key, fieldType, &details)
		if err != nil {
			glog.Errorf("Validation failed for: %s, %v", key, value)
			return err
		}
	}
	return nil
}

func (s *SwaggerSchema) validateField(value interface{}, apiVersion, fieldName, fieldType string, fieldDetails *swagger.ModelProperty) error {
	if strings.HasPrefix(fieldType, apiVersion) {
		return s.ValidateObject(value, apiVersion, fieldName, fieldType)
	}
	switch fieldType {
	case "string":
		// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
		_, isString := value.(string)
		_, isNumber := value.(float64)
		_, isInteger := value.(int)
		if !isString && !isNumber && !isInteger {
			return NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "array":
		arr, ok := value.([]interface{})
		if !ok {
			return NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName)
		}
		var arrType string
		glog.Infof("field detail %v", fieldDetails)
		if fieldDetails.Items.Ref == nil && fieldDetails.Items.Type == nil {
			return NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName)
		}
		if fieldDetails.Items.Ref != nil {
			arrType = *fieldDetails.Items.Ref
		} else {
			arrType = *fieldDetails.Items.Type
		}
		for ix := range arr {
			err := s.validateField(arr[ix], apiVersion, fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil)
			if err != nil {
				return err
			}
		}
	case "uint64":
	case "int64":
	case "integer":
		_, isNumber := value.(float64)
		_, isInteger := value.(int)
		if !isNumber && !isInteger {
			return NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "float64":
		if _, ok := value.(float64); !ok {
			return NewInvalidTypeError(reflect.Float64, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "boolean":
		if _, ok := value.(bool); !ok {
			return NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName)
		}
	case "any":
	default:
		return fmt.Errorf("unexpected type: %v", fieldType)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 cephfs

import (
	"github.com/kubernetes-csi/drivers/pkg/csi-common"
)

type identityServer struct {
	*csicommon.DefaultIdentityServer
}
<commit_msg>cephfs: Identity Service advertises PluginCapability_Service_CONTROLLER_SERVICE<commit_after>/*
Copyright 2018 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 cephfs

import (
	"context"

	"github.com/container-storage-interface/spec/lib/go/csi/v0"
	"github.com/kubernetes-csi/drivers/pkg/csi-common"
)

type identityServer struct {
	*csicommon.DefaultIdentityServer
}

func (is *identityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
	return &csi.GetPluginCapabilitiesResponse{
		Capabilities: []*csi.PluginCapability{
			{
				Type: &csi.PluginCapability_Service_{
					Service: &csi.PluginCapability_Service{
						Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
					},
				},
			},
		},
	}, nil
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"
	"os"
	"sync"

	"github.com/Sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/tools/cache"

	"github.com/zalando-incubator/postgres-operator/pkg/apiserver"
	"github.com/zalando-incubator/postgres-operator/pkg/cluster"
	"github.com/zalando-incubator/postgres-operator/pkg/spec"
	"github.com/zalando-incubator/postgres-operator/pkg/util/config"
	"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
	"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
	"github.com/zalando-incubator/postgres-operator/pkg/util/ringlog"
)

// Controller represents operator controller
type Controller struct {
	config   spec.ControllerConfig
	opConfig *config.Config

	logger     *logrus.Entry
	KubeClient k8sutil.KubernetesClient
	apiserver  *apiserver.Server

	stopCh chan struct{}

	curWorkerID      uint32 //initialized with 0
	curWorkerCluster sync.Map
	clusterWorkers   map[spec.NamespacedName]uint32
	clustersMu       sync.RWMutex
	clusters         map[spec.NamespacedName]*cluster.Cluster
	clusterLogs      map[spec.NamespacedName]ringlog.RingLogger
	clusterHistory   map[spec.NamespacedName]ringlog.RingLogger // history of the cluster changes
	teamClusters     map[string][]spec.NamespacedName

	postgresqlInformer cache.SharedIndexInformer
	podInformer        cache.SharedIndexInformer
	nodesInformer      cache.SharedIndexInformer
	podCh              chan spec.PodEvent

	clusterEventQueues  []*cache.FIFO // [workerID]Queue
	lastClusterSyncTime int64

	workerLogs map[uint32]ringlog.RingLogger
}

// NewController creates a new controller
func NewController(controllerConfig *spec.ControllerConfig) *Controller {
	logger := logrus.New()

	c := &Controller{
		config:           *controllerConfig,
		opConfig:         &config.Config{},
		logger:           logger.WithField("pkg", "controller"),
		curWorkerCluster: sync.Map{},
		clusterWorkers:   make(map[spec.NamespacedName]uint32),
		clusters:         make(map[spec.NamespacedName]*cluster.Cluster),
		clusterLogs:      make(map[spec.NamespacedName]ringlog.RingLogger),
		clusterHistory:   make(map[spec.NamespacedName]ringlog.RingLogger),
		teamClusters:     make(map[string][]spec.NamespacedName),
		stopCh:           make(chan struct{}),
		podCh:            make(chan spec.PodEvent),
	}
	logger.Hooks.Add(c)

	return c
}

func (c *Controller) initClients() {
	var err error

	c.KubeClient, err = k8sutil.NewFromConfig(c.config.RestConfig)
	if err != nil {
		c.logger.Fatalf("could not create kubernetes clients: %v", err)
	}
}

func (c *Controller) initOperatorConfig() {
	configMapData := make(map[string]string)

	if c.config.ConfigMapName != (spec.NamespacedName{}) {
		configMap, err := c.KubeClient.ConfigMaps(c.config.ConfigMapName.Namespace).
			Get(c.config.ConfigMapName.Name, metav1.GetOptions{})
		if err != nil {
			panic(err)
		}

		configMapData = configMap.Data
	} else {
		c.logger.Infoln("no ConfigMap specified. Loading default values")
	}

	// by default, the operator listens to all namespaces
	if configMapData["watched_namespace"] == "" {
		c.logger.Infoln("The operator config map specifies no namespace to watch. Falling back to watching all namespaces.")
		configMapData["watched_namespace"] = v1.NamespaceAll
	}

	watchedNsEnvVar, isPresentInEnv := os.LookupEnv("WATCHED_NAMESPACE")
	if isPresentInEnv {
		c.logger.Infoln("The WATCHED_NAMESPACE env variable takes priority over the same param from the operator configMap\n")
		// special case: v1.NamespaceAll currently also evaluates to the empty string
		// so when the env var is set to the empty string, use the default ns
		// since the meaning of this env var is only one namespace
		if watchedNsEnvVar == "" {
			c.logger.Infof("The WATCHED_NAMESPACE env var evaluates to the empty string, falling back to watching the 'default' namespace.\n", watchedNsEnvVar)
			configMapData["watched_namespace"] = v1.NamespaceDefault
		} else {
			c.logger.Infof("Watch the %q namespace specified in the env variable WATCHED_NAMESPACE\n", watchedNsEnvVar)
			configMapData["watched_namespace"] = watchedNsEnvVar
		}
	}

	if c.config.NoDatabaseAccess {
		configMapData["enable_database_access"] = "false"
	}
	if c.config.NoTeamsAPI {
		configMapData["enable_teams_api"] = "false"
	}

	c.opConfig = config.NewFromMap(configMapData)

	scalyrAPIKey := os.Getenv("SCALYR_API_KEY")
	if scalyrAPIKey != "" {
		c.opConfig.ScalyrAPIKey = scalyrAPIKey
	}
}

func (c *Controller) initController() {
	c.initClients()
	c.initOperatorConfig()

	// earliest point where we can check if the namespace to watch actually exists
	if c.opConfig.WatchedNamespace != v1.NamespaceAll {
		_, err := c.KubeClient.Namespaces().Get(c.opConfig.WatchedNamespace, metav1.GetOptions{})
		if err != nil {
			c.logger.Fatalf("Operator was told to watch the %q namespace but was unable to find it via Kubernetes API.", c.opConfig.WatchedNamespace)
		}

	}

	c.initSharedInformers()

	c.logger.Infof("config: %s", c.opConfig.MustMarshal())

	if c.opConfig.DebugLogging {
		c.logger.Logger.Level = logrus.DebugLevel
	}

	if err := c.createCRD(); err != nil {
		c.logger.Fatalf("could not register CustomResourceDefinition: %v", err)
	}

	if infraRoles, err := c.getInfrastructureRoles(&c.opConfig.InfrastructureRolesSecretName); err != nil {
		c.logger.Warningf("could not get infrastructure roles: %v", err)
	} else {
		c.config.InfrastructureRoles = infraRoles
	}

	c.clusterEventQueues = make([]*cache.FIFO, c.opConfig.Workers)
	c.workerLogs = make(map[uint32]ringlog.RingLogger, c.opConfig.Workers)
	for i := range c.clusterEventQueues {
		c.clusterEventQueues[i] = cache.NewFIFO(func(obj interface{}) (string, error) {
			e, ok := obj.(spec.ClusterEvent)
			if !ok {
				return "", fmt.Errorf("could not cast to ClusterEvent")
			}

			return queueClusterKey(e.EventType, e.UID), nil
		})
	}

	c.apiserver = apiserver.New(c, c.opConfig.APIPort, c.logger.Logger)
}

func (c *Controller) initSharedInformers() {
	// Postgresqls
	c.postgresqlInformer = cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc:  c.clusterListFunc,
			WatchFunc: c.clusterWatchFunc,
		},
		&spec.Postgresql{},
		constants.QueueResyncPeriodTPR,
		cache.Indexers{})

	c.postgresqlInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.postgresqlAdd,
		UpdateFunc: c.postgresqlUpdate,
		DeleteFunc: c.postgresqlDelete,
	})

	// Pods
	podLw := &cache.ListWatch{
		ListFunc:  c.podListFunc,
		WatchFunc: c.podWatchFunc,
	}

	c.podInformer = cache.NewSharedIndexInformer(
		podLw,
		&v1.Pod{},
		constants.QueueResyncPeriodPod,
		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})

	c.podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.podAdd,
		UpdateFunc: c.podUpdate,
		DeleteFunc: c.podDelete,
	})

	// Kubernetes Nodes
	nodeLw := &cache.ListWatch{
		ListFunc:  c.nodeListFunc,
		WatchFunc: c.nodeWatchFunc,
	}

	c.nodesInformer = cache.NewSharedIndexInformer(
		nodeLw,
		&v1.Node{},
		constants.QueueResyncPeriodNode,
		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})

	c.nodesInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.nodeAdd,
		UpdateFunc: c.nodeUpdate,
		DeleteFunc: c.nodeDelete,
	})
}

// Run starts background controller processes
func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	c.initController()

	wg.Add(5)
	go c.runPodInformer(stopCh, wg)
	go c.runPostgresqlInformer(stopCh, wg)
	go c.clusterResync(stopCh, wg)
	go c.apiserver.Run(stopCh, wg)
	go c.kubeNodesInformer(stopCh, wg)

	for i := range c.clusterEventQueues {
		wg.Add(1)
		c.workerLogs[uint32(i)] = ringlog.New(c.opConfig.RingLogLines)
		go c.processClusterEventsQueue(i, stopCh, wg)
	}

	c.logger.Info("started working in background")
}

func (c *Controller) runPodInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	c.podInformer.Run(stopCh)
}

func (c *Controller) runPostgresqlInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	c.postgresqlInformer.Run(stopCh)
}

func queueClusterKey(eventType spec.EventType, uid types.UID) string {
	return fmt.Sprintf("%s-%s", eventType, uid)
}

func (c *Controller) kubeNodesInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	c.nodesInformer.Run(stopCh)
}
<commit_msg>Streamline handling of the watched_namespace param/envvar<commit_after>package controller

import (
	"fmt"
	"os"
	"sync"

	"github.com/Sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/tools/cache"

	"github.com/zalando-incubator/postgres-operator/pkg/apiserver"
	"github.com/zalando-incubator/postgres-operator/pkg/cluster"
	"github.com/zalando-incubator/postgres-operator/pkg/spec"
	"github.com/zalando-incubator/postgres-operator/pkg/util/config"
	"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
	"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
	"github.com/zalando-incubator/postgres-operator/pkg/util/ringlog"
)

// Controller represents operator controller
type Controller struct {
	config   spec.ControllerConfig
	opConfig *config.Config

	logger     *logrus.Entry
	KubeClient k8sutil.KubernetesClient
	apiserver  *apiserver.Server

	stopCh chan struct{}

	curWorkerID      uint32 //initialized with 0
	curWorkerCluster sync.Map
	clusterWorkers   map[spec.NamespacedName]uint32
	clustersMu       sync.RWMutex
	clusters         map[spec.NamespacedName]*cluster.Cluster
	clusterLogs      map[spec.NamespacedName]ringlog.RingLogger
	clusterHistory   map[spec.NamespacedName]ringlog.RingLogger // history of the cluster changes
	teamClusters     map[string][]spec.NamespacedName

	postgresqlInformer cache.SharedIndexInformer
	podInformer        cache.SharedIndexInformer
	nodesInformer      cache.SharedIndexInformer
	podCh              chan spec.PodEvent

	clusterEventQueues  []*cache.FIFO // [workerID]Queue
	lastClusterSyncTime int64

	workerLogs map[uint32]ringlog.RingLogger
}

// NewController creates a new controller
func NewController(controllerConfig *spec.ControllerConfig) *Controller {
	logger := logrus.New()

	c := &Controller{
		config:           *controllerConfig,
		opConfig:         &config.Config{},
		logger:           logger.WithField("pkg", "controller"),
		curWorkerCluster: sync.Map{},
		clusterWorkers:   make(map[spec.NamespacedName]uint32),
		clusters:         make(map[spec.NamespacedName]*cluster.Cluster),
		clusterLogs:      make(map[spec.NamespacedName]ringlog.RingLogger),
		clusterHistory:   make(map[spec.NamespacedName]ringlog.RingLogger),
		teamClusters:     make(map[string][]spec.NamespacedName),
		stopCh:           make(chan struct{}),
		podCh:            make(chan spec.PodEvent),
	}
	logger.Hooks.Add(c)

	return c
}

func (c *Controller) initClients() {
	var err error

	c.KubeClient, err = k8sutil.NewFromConfig(c.config.RestConfig)
	if err != nil {
		c.logger.Fatalf("could not create kubernetes clients: %v", err)
	}
}

func (c *Controller) initOperatorConfig() {
	configMapData := make(map[string]string)

	if c.config.ConfigMapName != (spec.NamespacedName{}) {
		configMap, err := c.KubeClient.ConfigMaps(c.config.ConfigMapName.Namespace).
			Get(c.config.ConfigMapName.Name, metav1.GetOptions{})
		if err != nil {
			panic(err)
		}

		configMapData = configMap.Data
	} else {
		c.logger.Infoln("no ConfigMap specified. Loading default values")
	}

	watchedNsConfigMapVar, isPresentInOperatorConfigMap := configMapData["watched_namespace"]
	watchedNsEnvVar, isPresentInOperatorEnv := os.LookupEnv("WATCHED_NAMESPACE")

	if (!isPresentInOperatorConfigMap) && (!isPresentInOperatorEnv) {
		c.logger.Infoln("Neither the operator config map nor operator pod's environment define a namespace to watch. Default to watching all namespaces.")
		configMapData["watched_namespace"] = v1.NamespaceAll
	}

	if (isPresentInOperatorConfigMap) && (!isPresentInOperatorEnv) {

		// special case: v1.NamespaceAll currently also evaluates to the empty string
		// so when the param evaluates to the empty string, we use the default ns
		// since the meaning of the param is a single namespace and *not* all namespaces
		if watchedNsConfigMapVar == "" {
			c.logger.Infof("The watched namespace field in the operator config map evaluates to the empty string, falling back to watching the 'default' namespace.\n")
			configMapData["watched_namespace"] = v1.NamespaceDefault
		}
	}

	if isPresentInOperatorEnv {

		if isPresentInOperatorConfigMap {
			c.logger.Infof("Both WATCHED_NAMESPACE=%q env var and wacthed_namespace=%q field in operator config map are defined. The env variable takes priority over the configMap param\n", watchedNsEnvVar, watchedNsConfigMapVar)
		}

		// handle the empty string consistently
		if watchedNsEnvVar == "" {
			c.logger.Infoln("The WATCHED_NAMESPACE env var evaluates to the empty string, falling back to watching the 'default' namespace")
			configMapData["watched_namespace"] = v1.NamespaceDefault
		} else {
			c.logger.Infof("Watch the %q namespace specified in the env variable WATCHED_NAMESPACE\n", watchedNsEnvVar)
			configMapData["watched_namespace"] = watchedNsEnvVar
		}
	}

	if c.config.NoDatabaseAccess {
		configMapData["enable_database_access"] = "false"
	}
	if c.config.NoTeamsAPI {
		configMapData["enable_teams_api"] = "false"
	}

	c.opConfig = config.NewFromMap(configMapData)

	scalyrAPIKey := os.Getenv("SCALYR_API_KEY")
	if scalyrAPIKey != "" {
		c.opConfig.ScalyrAPIKey = scalyrAPIKey
	}
}

func (c *Controller) initController() {
	c.initClients()
	c.initOperatorConfig()

	// earliest point where we can check if the namespace to watch actually exists
	if c.opConfig.WatchedNamespace != v1.NamespaceAll {
		_, err := c.KubeClient.Namespaces().Get(c.opConfig.WatchedNamespace, metav1.GetOptions{})
		if err != nil {
			c.logger.Fatalf("Operator was told to watch the %q namespace but was unable to find it via Kubernetes API.", c.opConfig.WatchedNamespace)
		}

	}

	c.initSharedInformers()

	c.logger.Infof("config: %s", c.opConfig.MustMarshal())

	if c.opConfig.DebugLogging {
		c.logger.Logger.Level = logrus.DebugLevel
	}

	if err := c.createCRD(); err != nil {
		c.logger.Fatalf("could not register CustomResourceDefinition: %v", err)
	}

	if infraRoles, err := c.getInfrastructureRoles(&c.opConfig.InfrastructureRolesSecretName); err != nil {
		c.logger.Warningf("could not get infrastructure roles: %v", err)
	} else {
		c.config.InfrastructureRoles = infraRoles
	}

	c.clusterEventQueues = make([]*cache.FIFO, c.opConfig.Workers)
	c.workerLogs = make(map[uint32]ringlog.RingLogger, c.opConfig.Workers)
	for i := range c.clusterEventQueues {
		c.clusterEventQueues[i] = cache.NewFIFO(func(obj interface{}) (string, error) {
			e, ok := obj.(spec.ClusterEvent)
			if !ok {
				return "", fmt.Errorf("could not cast to ClusterEvent")
			}

			return queueClusterKey(e.EventType, e.UID), nil
		})
	}

	c.apiserver = apiserver.New(c, c.opConfig.APIPort, c.logger.Logger)
}

func (c *Controller) initSharedInformers() {
	// Postgresqls
	c.postgresqlInformer = cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc:  c.clusterListFunc,
			WatchFunc: c.clusterWatchFunc,
		},
		&spec.Postgresql{},
		constants.QueueResyncPeriodTPR,
		cache.Indexers{})

	c.postgresqlInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.postgresqlAdd,
		UpdateFunc: c.postgresqlUpdate,
		DeleteFunc: c.postgresqlDelete,
	})

	// Pods
	podLw := &cache.ListWatch{
		ListFunc:  c.podListFunc,
		WatchFunc: c.podWatchFunc,
	}

	c.podInformer = cache.NewSharedIndexInformer(
		podLw,
		&v1.Pod{},
		constants.QueueResyncPeriodPod,
		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})

	c.podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.podAdd,
		UpdateFunc: c.podUpdate,
		DeleteFunc: c.podDelete,
	})

	// Kubernetes Nodes
	nodeLw := &cache.ListWatch{
		ListFunc:  c.nodeListFunc,
		WatchFunc: c.nodeWatchFunc,
	}

	c.nodesInformer = cache.NewSharedIndexInformer(
		nodeLw,
		&v1.Node{},
		constants.QueueResyncPeriodNode,
		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})

	c.nodesInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.nodeAdd,
		UpdateFunc: c.nodeUpdate,
		DeleteFunc: c.nodeDelete,
	})
}

// Run starts background controller processes
func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	c.initController()

	wg.Add(5)
	go c.runPodInformer(stopCh, wg)
	go c.runPostgresqlInformer(stopCh, wg)
	go c.clusterResync(stopCh, wg)
	go c.apiserver.Run(stopCh, wg)
	go c.kubeNodesInformer(stopCh, wg)

	for i := range c.clusterEventQueues {
		wg.Add(1)
		c.workerLogs[uint32(i)] = ringlog.New(c.opConfig.RingLogLines)
		go c.processClusterEventsQueue(i, stopCh, wg)
	}

	c.logger.Info("started working in background")
}

func (c *Controller) runPodInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	c.podInformer.Run(stopCh)
}

func (c *Controller) runPostgresqlInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	c.postgresqlInformer.Run(stopCh)
}

func queueClusterKey(eventType spec.EventType, uid types.UID) string {
	return fmt.Sprintf("%s-%s", eventType, uid)
}

func (c *Controller) kubeNodesInformer(stopCh <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	c.nodesInformer.Run(stopCh)
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"

	"github.com/coreos/kube-prometheus-controller/pkg/spec"
	"k8s.io/client-go/1.5/pkg/api/v1"
	"k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1"
)

func makeDeployment(p *spec.Prometheus, old *v1beta1.Deployment) *v1beta1.Deployment {
	// TODO(fabxc): is this the right point to inject defaults?
	// Ideally we would do it before storing but that's currently not possible.
	// Potentially an update handler on first insertion.

	baseImage := p.Spec.BaseImage
	if baseImage == "" {
		baseImage = "quay.io/prometheus/prometheus"
	}
	version := p.Spec.Version
	if version == "" {
		version = "v1.3.0-beta.0"
	}
	replicas := p.Spec.Replicas
	if replicas < 1 {
		replicas = 1
	}
	image := fmt.Sprintf("%s:%s", baseImage, version)

	depl := &v1beta1.Deployment{
		ObjectMeta: v1.ObjectMeta{
			Name: p.Name,
		},
		Spec: makeDeploymentSpec(p.Name, image, replicas),
	}
	if old != nil {
		depl.Annotations = old.Annotations
	}
	return depl
}

func makeDeploymentSpec(name, image string, replicas int32) v1beta1.DeploymentSpec {
	return v1beta1.DeploymentSpec{
		Replicas: &replicas,
		Template: v1.PodTemplateSpec{
			ObjectMeta: v1.ObjectMeta{
				Labels: map[string]string{
					"prometheus.coreos.com/name": name,
					"prometheus.coreos.com/type": "prometheus",
				},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "prometheus",
						Image: image,
						Ports: []v1.ContainerPort{
							{
								Name:          "web",
								ContainerPort: 9090,
								Protocol:      v1.ProtocolTCP,
							},
						},
						Args: []string{
							"-storage.local.retention=12h",
							"-storage.local.memory-chunks=500000",
							"-config.file=/etc/prometheus/prometheus.yaml",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config-volume",
								ReadOnly:  true,
								MountPath: "/etc/prometheus",
							},
						},
					}, {
						Name:  "reloader",
						Image: "jimmidyson/configmap-reload",
						Args: []string{
							"-webhook-url=http://localhost:9090/-/reload",
							"-volume-dir=/etc/prometheus/",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config-volume",
								ReadOnly:  true,
								MountPath: "/etc/prometheus",
							},
						},
					},
				},
				Volumes: []v1.Volume{
					{
						Name: "config-volume",
						VolumeSource: v1.VolumeSource{
							ConfigMap: &v1.ConfigMapVolumeSource{
								LocalObjectReference: v1.LocalObjectReference{
									Name: name,
								},
							},
						},
					},
				},
			},
		},
	}
}
<commit_msg>Set appropriate probes and termination period.<commit_after>package controller

import (
	"fmt"

	"github.com/coreos/kube-prometheus-controller/pkg/spec"
	"k8s.io/client-go/1.5/pkg/api/v1"
	"k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/1.5/pkg/util/intstr"
)

func makeDeployment(p *spec.Prometheus, old *v1beta1.Deployment) *v1beta1.Deployment {
	// TODO(fabxc): is this the right point to inject defaults?
	// Ideally we would do it before storing but that's currently not possible.
	// Potentially an update handler on first insertion.

	baseImage := p.Spec.BaseImage
	if baseImage == "" {
		baseImage = "quay.io/prometheus/prometheus"
	}
	version := p.Spec.Version
	if version == "" {
		version = "v1.3.0-beta.0"
	}
	replicas := p.Spec.Replicas
	if replicas < 1 {
		replicas = 1
	}
	image := fmt.Sprintf("%s:%s", baseImage, version)

	depl := &v1beta1.Deployment{
		ObjectMeta: v1.ObjectMeta{
			Name: p.Name,
		},
		Spec: makeDeploymentSpec(p.Name, image, replicas),
	}
	if old != nil {
		depl.Annotations = old.Annotations
	}
	return depl
}

func makeDeploymentSpec(name, image string, replicas int32) v1beta1.DeploymentSpec {
	// Prometheus may take quite long to shut down to checkpoint existing data.
	// Allow up to 10 minutes for clean termination.
	terminationGracePeriod := int64(600)

	return v1beta1.DeploymentSpec{
		Replicas: &replicas,
		Template: v1.PodTemplateSpec{
			ObjectMeta: v1.ObjectMeta{
				Labels: map[string]string{
					"prometheus.coreos.com/name": name,
					"prometheus.coreos.com/type": "prometheus",
				},
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "prometheus",
						Image: image,
						Ports: []v1.ContainerPort{
							{
								Name:          "web",
								ContainerPort: 9090,
								Protocol:      v1.ProtocolTCP,
							},
						},
						Args: []string{
							"-storage.local.retention=12h",
							"-storage.local.memory-chunks=500000",
							"-config.file=/etc/prometheus/prometheus.yaml",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config-volume",
								ReadOnly:  true,
								MountPath: "/etc/prometheus",
							},
						},
						ReadinessProbe: &v1.Probe{
							Handler: v1.Handler{
								HTTPGet: &v1.HTTPGetAction{
									Path: "/status",
									Port: intstr.FromString("web"),
								},
							},
							InitialDelaySeconds: 1,
							TimeoutSeconds:      3,
							PeriodSeconds:       5,
							// For larger servers, restoring a checkpoint on startup may take quite a bit of time.
							// Wait up to 5 minutes.
							FailureThreshold: 100,
						},
					}, {
						Name:  "reloader",
						Image: "jimmidyson/configmap-reload",
						Args: []string{
							"-webhook-url=http://localhost:9090/-/reload",
							"-volume-dir=/etc/prometheus/",
						},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "config-volume",
								ReadOnly:  true,
								MountPath: "/etc/prometheus",
							},
						},
					},
				},
				TerminationGracePeriodSeconds: &terminationGracePeriod,
				Volumes: []v1.Volume{
					{
						Name: "config-volume",
						VolumeSource: v1.VolumeSource{
							ConfigMap: &v1.ConfigMapVolumeSource{
								LocalObjectReference: v1.LocalObjectReference{
									Name: name,
								},
							},
						},
					},
				},
			},
		},
	}
}
<|endoftext|>


<commit_before>// package kbucket implements a kademlia 'k-bucket' routing table.
package kbucket

import (
	"container/list"
	"fmt"
	"sort"
	"sync"
	"time"

	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"
)

var log = u.Logger("table")

// RoutingTable defines the routing table.
type RoutingTable struct {

	// ID of the local peer
	local ID

	// Blanket lock, refine later for better performance
	tabLock sync.RWMutex

	// latency metrics
	metrics peer.Metrics

	// Maximum acceptable latency for peers in this cluster
	maxLatency time.Duration

	// kBuckets define all the fingers to other nodes.
	Buckets    []*Bucket
	bucketsize int
}

// NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
func NewRoutingTable(bucketsize int, localID ID, latency time.Duration, m peer.Metrics) *RoutingTable {
	rt := new(RoutingTable)
	rt.Buckets = []*Bucket{newBucket()}
	rt.bucketsize = bucketsize
	rt.local = localID
	rt.maxLatency = latency
	rt.metrics = m
	return rt
}

// Update adds or moves the given peer to the front of its respective bucket
// If a peer gets removed from a bucket, it is returned
func (rt *RoutingTable) Update(p peer.ID) peer.ID {
	rt.tabLock.Lock()
	defer rt.tabLock.Unlock()
	peerID := ConvertPeerID(p)
	cpl := commonPrefixLen(peerID, rt.local)

	bucketID := cpl
	if bucketID >= len(rt.Buckets) {
		bucketID = len(rt.Buckets) - 1
	}

	bucket := rt.Buckets[bucketID]
	e := bucket.find(p)
	if e == nil {
		// New peer, add to bucket
		if rt.metrics.LatencyEWMA(p) > rt.maxLatency {
			// Connection doesnt meet requirements, skip!
			return ""
		}
		bucket.pushFront(p)

		// Are we past the max bucket size?
		if bucket.len() > rt.bucketsize {
			// If this bucket is the rightmost bucket, and its full
			// we need to split it and create a new bucket
			if bucketID == len(rt.Buckets)-1 {
				return rt.nextBucket()
			} else {
				// If the bucket cant split kick out least active node
				return bucket.popBack()
			}
		}
		return ""
	}
	// If the peer is already in the table, move it to the front.
	// This signifies that it it "more active" and the less active nodes
	// Will as a result tend towards the back of the list
	bucket.moveToFront(e)
	return ""
}

func (rt *RoutingTable) nextBucket() peer.ID {
	bucket := rt.Buckets[len(rt.Buckets)-1]
	newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
	rt.Buckets = append(rt.Buckets, newBucket)
	if newBucket.len() > rt.bucketsize {
		return rt.nextBucket()
	}

	// If all elements were on left side of split...
	if bucket.len() > rt.bucketsize {
		return bucket.popBack()
	}
	return ""
}

// A helper struct to sort peers by their distance to the local node
type peerDistance struct {
	p        peer.ID
	distance ID
}

// peerSorterArr implements sort.Interface to sort peers by xor distance
type peerSorterArr []*peerDistance

func (p peerSorterArr) Len() int      { return len(p) }
func (p peerSorterArr) Swap(a, b int) { p[a], p[b] = p[b], p[a] }
func (p peerSorterArr) Less(a, b int) bool {
	return p[a].distance.less(p[b].distance)
}

//

func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr {
	for e := peerList.Front(); e != nil; e = e.Next() {
		p := e.Value.(peer.ID)
		pID := ConvertPeerID(p)
		pd := peerDistance{
			p:        p,
			distance: xor(target, pID),
		}
		peerArr = append(peerArr, &pd)
		if e == nil {
			log.Debug("list element was nil")
			return peerArr
		}
	}
	return peerArr
}

// Find a specific peer by ID or return nil
func (rt *RoutingTable) Find(id peer.ID) peer.ID {
	srch := rt.NearestPeers(ConvertPeerID(id), 1)
	if len(srch) == 0 || srch[0] != id {
		return ""
	}
	return srch[0]
}

// NearestPeer returns a single peer that is nearest to the given ID
func (rt *RoutingTable) NearestPeer(id ID) peer.ID {
	peers := rt.NearestPeers(id, 1)
	if len(peers) > 0 {
		return peers[0]
	}

	log.Errorf("NearestPeer: Returning nil, table size = %d", rt.Size())
	return ""
}

// NearestPeers returns a list of the 'count' closest peers to the given ID
func (rt *RoutingTable) NearestPeers(id ID, count int) []peer.ID {
	rt.tabLock.RLock()
	defer rt.tabLock.RUnlock()
	cpl := commonPrefixLen(id, rt.local)

	// Get bucket at cpl index or last bucket
	var bucket *Bucket
	if cpl >= len(rt.Buckets) {
		cpl = len(rt.Buckets) - 1
	}
	bucket = rt.Buckets[cpl]

	var peerArr peerSorterArr
	if bucket.len() == 0 {
		// In the case of an unusual split, one bucket may be empty.
		// if this happens, search both surrounding buckets for nearest peer
		if cpl > 0 {
			plist := rt.Buckets[cpl-1].list
			peerArr = copyPeersFromList(id, peerArr, plist)
		}

		if cpl < len(rt.Buckets)-1 {
			plist := rt.Buckets[cpl+1].list
			peerArr = copyPeersFromList(id, peerArr, plist)
		}
	} else {
		peerArr = copyPeersFromList(id, peerArr, bucket.list)
	}

	// Sort by distance to local peer
	sort.Sort(peerArr)

	var out []peer.ID
	for i := 0; i < count && i < peerArr.Len(); i++ {
		out = append(out, peerArr[i].p)
	}

	return out
}

// Size returns the total number of peers in the routing table
func (rt *RoutingTable) Size() int {
	var tot int
	for _, buck := range rt.Buckets {
		tot += buck.len()
	}
	return tot
}

// ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
// NOTE: This is potentially unsafe... use at your own risk
func (rt *RoutingTable) ListPeers() []peer.ID {
	var peers []peer.ID
	for _, buck := range rt.Buckets {
		for e := buck.getIter(); e != nil; e = e.Next() {
			peers = append(peers, e.Value.(peer.ID))
		}
	}
	return peers
}

// Print prints a descriptive statement about the provided RoutingTable
func (rt *RoutingTable) Print() {
	fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
	rt.tabLock.RLock()
	peers := rt.ListPeers()
	for i, p := range peers {
		fmt.Printf("%d) %s %s\n", i, p.Pretty(), rt.metrics.LatencyEWMA(p).String())
	}
}
<commit_msg>dht bugfix: unlock on print<commit_after>// package kbucket implements a kademlia 'k-bucket' routing table.
package kbucket

import (
	"container/list"
	"fmt"
	"sort"
	"sync"
	"time"

	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"
)

var log = u.Logger("table")

// RoutingTable defines the routing table.
type RoutingTable struct {

	// ID of the local peer
	local ID

	// Blanket lock, refine later for better performance
	tabLock sync.RWMutex

	// latency metrics
	metrics peer.Metrics

	// Maximum acceptable latency for peers in this cluster
	maxLatency time.Duration

	// kBuckets define all the fingers to other nodes.
	Buckets    []*Bucket
	bucketsize int
}

// NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
func NewRoutingTable(bucketsize int, localID ID, latency time.Duration, m peer.Metrics) *RoutingTable {
	rt := new(RoutingTable)
	rt.Buckets = []*Bucket{newBucket()}
	rt.bucketsize = bucketsize
	rt.local = localID
	rt.maxLatency = latency
	rt.metrics = m
	return rt
}

// Update adds or moves the given peer to the front of its respective bucket
// If a peer gets removed from a bucket, it is returned
func (rt *RoutingTable) Update(p peer.ID) peer.ID {
	rt.tabLock.Lock()
	defer rt.tabLock.Unlock()
	peerID := ConvertPeerID(p)
	cpl := commonPrefixLen(peerID, rt.local)

	bucketID := cpl
	if bucketID >= len(rt.Buckets) {
		bucketID = len(rt.Buckets) - 1
	}

	bucket := rt.Buckets[bucketID]
	e := bucket.find(p)
	if e == nil {
		// New peer, add to bucket
		if rt.metrics.LatencyEWMA(p) > rt.maxLatency {
			// Connection doesnt meet requirements, skip!
			return ""
		}
		bucket.pushFront(p)

		// Are we past the max bucket size?
		if bucket.len() > rt.bucketsize {
			// If this bucket is the rightmost bucket, and its full
			// we need to split it and create a new bucket
			if bucketID == len(rt.Buckets)-1 {
				return rt.nextBucket()
			} else {
				// If the bucket cant split kick out least active node
				return bucket.popBack()
			}
		}
		return ""
	}
	// If the peer is already in the table, move it to the front.
	// This signifies that it it "more active" and the less active nodes
	// Will as a result tend towards the back of the list
	bucket.moveToFront(e)
	return ""
}

func (rt *RoutingTable) nextBucket() peer.ID {
	bucket := rt.Buckets[len(rt.Buckets)-1]
	newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
	rt.Buckets = append(rt.Buckets, newBucket)
	if newBucket.len() > rt.bucketsize {
		return rt.nextBucket()
	}

	// If all elements were on left side of split...
	if bucket.len() > rt.bucketsize {
		return bucket.popBack()
	}
	return ""
}

// A helper struct to sort peers by their distance to the local node
type peerDistance struct {
	p        peer.ID
	distance ID
}

// peerSorterArr implements sort.Interface to sort peers by xor distance
type peerSorterArr []*peerDistance

func (p peerSorterArr) Len() int      { return len(p) }
func (p peerSorterArr) Swap(a, b int) { p[a], p[b] = p[b], p[a] }
func (p peerSorterArr) Less(a, b int) bool {
	return p[a].distance.less(p[b].distance)
}

//

func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr {
	for e := peerList.Front(); e != nil; e = e.Next() {
		p := e.Value.(peer.ID)
		pID := ConvertPeerID(p)
		pd := peerDistance{
			p:        p,
			distance: xor(target, pID),
		}
		peerArr = append(peerArr, &pd)
		if e == nil {
			log.Debug("list element was nil")
			return peerArr
		}
	}
	return peerArr
}

// Find a specific peer by ID or return nil
func (rt *RoutingTable) Find(id peer.ID) peer.ID {
	srch := rt.NearestPeers(ConvertPeerID(id), 1)
	if len(srch) == 0 || srch[0] != id {
		return ""
	}
	return srch[0]
}

// NearestPeer returns a single peer that is nearest to the given ID
func (rt *RoutingTable) NearestPeer(id ID) peer.ID {
	peers := rt.NearestPeers(id, 1)
	if len(peers) > 0 {
		return peers[0]
	}

	log.Errorf("NearestPeer: Returning nil, table size = %d", rt.Size())
	return ""
}

// NearestPeers returns a list of the 'count' closest peers to the given ID
func (rt *RoutingTable) NearestPeers(id ID, count int) []peer.ID {
	rt.tabLock.RLock()
	defer rt.tabLock.RUnlock()
	cpl := commonPrefixLen(id, rt.local)

	// Get bucket at cpl index or last bucket
	var bucket *Bucket
	if cpl >= len(rt.Buckets) {
		cpl = len(rt.Buckets) - 1
	}
	bucket = rt.Buckets[cpl]

	var peerArr peerSorterArr
	if bucket.len() == 0 {
		// In the case of an unusual split, one bucket may be empty.
		// if this happens, search both surrounding buckets for nearest peer
		if cpl > 0 {
			plist := rt.Buckets[cpl-1].list
			peerArr = copyPeersFromList(id, peerArr, plist)
		}

		if cpl < len(rt.Buckets)-1 {
			plist := rt.Buckets[cpl+1].list
			peerArr = copyPeersFromList(id, peerArr, plist)
		}
	} else {
		peerArr = copyPeersFromList(id, peerArr, bucket.list)
	}

	// Sort by distance to local peer
	sort.Sort(peerArr)

	var out []peer.ID
	for i := 0; i < count && i < peerArr.Len(); i++ {
		out = append(out, peerArr[i].p)
	}

	return out
}

// Size returns the total number of peers in the routing table
func (rt *RoutingTable) Size() int {
	var tot int
	for _, buck := range rt.Buckets {
		tot += buck.len()
	}
	return tot
}

// ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
// NOTE: This is potentially unsafe... use at your own risk
func (rt *RoutingTable) ListPeers() []peer.ID {
	var peers []peer.ID
	for _, buck := range rt.Buckets {
		for e := buck.getIter(); e != nil; e = e.Next() {
			peers = append(peers, e.Value.(peer.ID))
		}
	}
	return peers
}

// Print prints a descriptive statement about the provided RoutingTable
func (rt *RoutingTable) Print() {
	fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
	rt.tabLock.RLock()
	peers := rt.ListPeers()
	for i, p := range peers {
		fmt.Printf("%d) %s %s\n", i, p.Pretty(), rt.metrics.LatencyEWMA(p).String())
	}
	rt.tabLock.RUnlock()
}
<|endoftext|>


<commit_before>// Copyright 2017 Istio 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 main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	u "istio.io/test-infra/toolbox/util"
)

var (
	owner        = flag.String("owner", "istio", "Github owner or org")
	tokenFile    = flag.String("token_file", "", "File containing Github API Access Token.")
	op           = flag.String("op", "", "Operation to be performed")
	repo         = flag.String("repo", "", "Repository to which op is applied")
	pipelineType = flag.String("pipeline", "", "Pipeline type daily/monthly")
	baseBranch   = flag.String("base_branch", "", "Branch to which op is applied")
	refSHA       = flag.String("ref_sha", "", "Commit SHA used by the operation")
	tag          = flag.String("tag", "", "Tag of the release candidate")
	prNum        = flag.Int("pr_num", 0, "PR number")

	githubClnt *u.GithubClient
)

const (
	masterBranch = "master"
	testCommand  = "/test"
	maxRetests   = 3
)

func fastForward(repo, baseBranch, refSHA string) error {
	isAncestor, err := githubClnt.SHAIsAncestorOfBranch(repo, masterBranch, refSHA)
	if err != nil {
		return err
	}
	if !isAncestor {
		log.Printf("SHA %s is not an ancestor of branch %s, resorts to no-op\n", refSHA, masterBranch)
		return nil
	}
	return githubClnt.FastForward(repo, baseBranch, refSHA)
}

func getBaseSha(repo string, prNumber int) (string, error) {
	pr, err := githubClnt.GetPR(repo, prNumber)
	if err != nil {
		return "", err
	}
	return *pr.Base.SHA, nil
}

// CreateReleaseRequest triggers release pipeline by creating a PR.
func CreateReleaseRequest(repo, pipelineType, tag, branch, sha string) error {
	log.Printf("Creating PR to trigger build on %s branch\n", branch)
	prTitle := fmt.Sprintf("%s %s", strings.ToUpper(pipelineType), tag)
	prBody := "This is a generated PR that triggers a release, and will be automatically merged when all required tests have passed."
	timestamp := fmt.Sprintf("%v", time.Now().UnixNano())
	srcBranch := "release_" + timestamp
	edit := func() error {
		f, err := os.Create(fmt.Sprintf("./%s/release_params.sh", pipelineType))
		if err != nil {
			return err
		}
		defer func() {
			cerr := f.Close()
			if err != nil {
				log.Print(cerr)
			}
		}()

		if _, err := f.WriteString("export CB_BRANCH=" + branch + "\n"); err != nil {
			return err
		}
		if _, err := f.WriteString("export CB_PIPELINE_TYPE=" + pipelineType + "\n"); err != nil {
			return err
		}
		if _, err := f.WriteString("export CB_VERSION=" + tag + "\n"); err != nil {
			return err
		}
		if _, err := f.WriteString("export CB_COMMIT=" + sha + "\n"); err != nil {
			return err
		}
		return nil
	}
	_, err := githubClnt.CreatePRUpdateRepo(srcBranch, branch, repo, prTitle, prBody, edit)
	return err
}

// CleanupReleaseRequests merges tested release requests, and close the expired ones (not passing)
func CleanupReleaseRequests(owner, repo string) error {
	pullQueries := []string{
		fmt.Sprintf("repo:%s/%s", owner, repo),
		"type:pr",
		"is:open",
	}

	allPulls, err := githubClnt.SearchIssues(pullQueries, "created", "desc")
	if err != nil {
		return err
	}
	log.Printf("Found %d PRs", len(allPulls))

	utc, _ := time.LoadLocation("UTC")
	for _, pull := range allPulls {
		pr, err := githubClnt.GetPR(repo, *pull.Number)
		if err != nil {
			return err
		}

		// Close the PR if it is expired (after 1 day)
		expiresAt := pr.CreatedAt.In(utc).Add(24 * time.Hour)
		if time.Now().In(utc).After(expiresAt) {
			log.Printf("Closing expired https://github.com/%s/%s/pull/%d..", owner, repo, *pr.Number)

			if err2 := githubClnt.CreateComment(repo, pull, "Tests did not pass and this request has expired. Closing out."); err != nil {
				return err2
			}
			if err2 := githubClnt.ClosePR(repo, pr); err != nil {
				return err2
			}
			log.Printf("Closed https://github.com/%s/%s/pull/%d and deleted branch.", owner, repo, *pr.Number)

			if err2 := githubClnt.DeleteBranch(repo, pr); err != nil {
				// Proceed to other PRs even if we cannot delete the branch.
				log.Printf("Cannot delete branch: %v.", err2)
			} else {
				log.Print("Deleted branch")
			}
			break
		}

		status, combinedStatus, err := githubClnt.GetPRTestResults(repo, pr, true)
		if err != nil {
			return err
		}
		ci := u.NewCIState()
		switch status {
		case ci.Success:
			log.Printf("Merging https://github.com/%s/%s/pull/%d.", owner, repo, *pr.Number)
			if err = githubClnt.MergePR(repo, *pr.Number, "Release qualification passed"); err != nil {
				return err
			}
			log.Printf("Merged https://github.com/%s/%s/pull/%d.", owner, repo, *pr.Number)

			// Re-fetch PR since it has been updated.
			pr, err = githubClnt.GetPR(repo, *pull.Number)
			if err != nil {
				return err
			}
			if err = githubClnt.DeleteBranch(repo, pr); err != nil {
				// Proceed to other PRs even if we cannot delete the branch.
				log.Printf("Cannot delete branch: %v.", err)
			} else {
				log.Print("Deleted branch")
			}

		case ci.Pending:
			log.Printf("https://github.com/%s/%s/pull/%d is still being tested. Skipping.", owner, repo, *pr.Number)
		case ci.Error:
		case ci.Failure:
			// Trigger a retest
			comments, err := githubClnt.ListIssueComments(repo, pull)
			if err != nil {
				return err
			}
			retestCount := 0
			for _, comment := range comments {
				if strings.HasPrefix(*comment.Body, testCommand) {
					retestCount++
				}
			}
			if retestCount < maxRetests {
				log.Printf("Retesting https://github.com/%s/%s/pull/%d.", owner, repo, *pr.Number)
				comment := ""
				for _, status := range combinedStatus.Statuses {
					if *status.State == ci.Error || *status.State == ci.Failure {
						context := *status.Context
						if strings.HasPrefix(context, "prow/") {
							testName := context[5:]
							comment += testCommand + " " + testName + "\n"
						}
					}
				}
				if err := githubClnt.CreateComment(repo, pull, comment); err != nil {
					return err
				}
				log.Printf("Commented: %s", comment)
			} else {
				log.Printf("Already retested https://github.com/%s/%s/pull/%d %d times. Skipping.", owner, repo, *pr.Number, retestCount)
			}
		}
	}
	return nil
}

func init() {
	flag.Parse()
	u.AssertNotEmpty("owner", owner)
	u.AssertNotEmpty("token_file", tokenFile)
	token, err := u.GetAPITokenFromFile(*tokenFile)
	if err != nil {
		log.Printf("Error accessing user supplied token_file: %v\n", err)
	}
	githubClnt = u.NewGithubClient(*owner, token)
}

func main() {
	u.AssertNotEmpty("repo", repo)

	var err error
	switch *op {
	case "fastForward":
		u.AssertNotEmpty("base_branch", baseBranch)
		u.AssertNotEmpty("ref_sha", refSHA)
		err = fastForward(*repo, *baseBranch, *refSHA)
	// the following three cases are related to release pipeline
	case "newReleaseRequest":
		u.AssertNotEmpty("pipeline", pipelineType)
		u.AssertNotEmpty("tag", tag)
		u.AssertNotEmpty("base_branch", baseBranch)
		u.AssertNotEmpty("ref_sha", refSHA)
		err = CreateReleaseRequest(*repo, *pipelineType, *tag, *baseBranch, *refSHA)
	case "cleanupReleaseRequests":
		err = CleanupReleaseRequests(*owner, *repo)
	case "getBaseSHA":
		var baseSha string
		baseSha, err = getBaseSha(*repo, *prNum)
		if err == nil {
			fmt.Print(baseSha)
		}
	default:
		err = fmt.Errorf("unsupported operation: %s", *op)
	}

	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}
<commit_msg>Fix getBaseSha to find base sha using parents (#1149)<commit_after>// Copyright 2017 Istio 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 main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	u "istio.io/test-infra/toolbox/util"
)

var (
	owner        = flag.String("owner", "istio", "Github owner or org")
	tokenFile    = flag.String("token_file", "", "File containing Github API Access Token.")
	op           = flag.String("op", "", "Operation to be performed")
	repo         = flag.String("repo", "", "Repository to which op is applied")
	pipelineType = flag.String("pipeline", "", "Pipeline type daily/monthly")
	baseBranch   = flag.String("base_branch", "", "Branch to which op is applied")
	refSHA       = flag.String("ref_sha", "", "Commit SHA used by the operation")
	tag          = flag.String("tag", "", "Tag of the release candidate")
	prNum        = flag.Int("pr_num", 0, "PR number")

	githubClnt *u.GithubClient
)

const (
	masterBranch = "master"
	testCommand  = "/test"
	maxRetests   = 3
)

func fastForward(repo, baseBranch, refSHA string) error {
	isAncestor, err := githubClnt.SHAIsAncestorOfBranch(repo, masterBranch, refSHA)
	if err != nil {
		return err
	}
	if !isAncestor {
		log.Printf("SHA %s is not an ancestor of branch %s, resorts to no-op\n", refSHA, masterBranch)
		return nil
	}
	return githubClnt.FastForward(repo, baseBranch, refSHA)
}

func getBaseSha(repo string, prNumber int) (string, error) {
	pr, err := githubClnt.GetPR(repo, prNumber)
	if err != nil {
		return "", err
	}
	// Walk up the parents from the latest PR commit SHA, to find the first commit that have already been merged.
	commitSha := pr.Head.SHA
	for {
		commit, err := githubClnt.GetCommit(repo, *commitSha)
		if err != nil {
			return "", err
		}
		// TODO(hklai) Better handlig to find the right SHA
		if len(commit.Parents) > 1 || strings.Contains(*commit.Message, "(#") {
			return *commitSha, nil
		}
		commitSha = commit.Parents[0].SHA
	}
}

// CreateReleaseRequest triggers release pipeline by creating a PR.
func CreateReleaseRequest(repo, pipelineType, tag, branch, sha string) error {
	log.Printf("Creating PR to trigger build on %s branch\n", branch)
	prTitle := fmt.Sprintf("%s %s", strings.ToUpper(pipelineType), tag)
	prBody := "This is a generated PR that triggers a release, and will be automatically merged when all required tests have passed."
	timestamp := fmt.Sprintf("%v", time.Now().UnixNano())
	srcBranch := "release_" + timestamp
	edit := func() error {
		f, err := os.Create(fmt.Sprintf("./%s/release_params.sh", pipelineType))
		if err != nil {
			return err
		}
		defer func() {
			cerr := f.Close()
			if err != nil {
				log.Print(cerr)
			}
		}()

		if _, err := f.WriteString("export CB_BRANCH=" + branch + "\n"); err != nil {
			return err
		}
		if _, err := f.WriteString("export CB_PIPELINE_TYPE=" + pipelineType + "\n"); err != nil {
			return err
		}
		if _, err := f.WriteString("export CB_VERSION=" + tag + "\n"); err != nil {
			return err
		}
		if _, err := f.WriteString("export CB_COMMIT=" + sha + "\n"); err != nil {
			return err
		}
		return nil
	}
	_, err := githubClnt.CreatePRUpdateRepo(srcBranch, branch, repo, prTitle, prBody, edit)
	return err
}

// CleanupReleaseRequests merges tested release requests, and close the expired ones (not passing)
func CleanupReleaseRequests(owner, repo string) error {
	pullQueries := []string{
		fmt.Sprintf("repo:%s/%s", owner, repo),
		"type:pr",
		"is:open",
	}

	allPulls, err := githubClnt.SearchIssues(pullQueries, "created", "desc")
	if err != nil {
		return err
	}
	log.Printf("Found %d PRs", len(allPulls))

	utc, _ := time.LoadLocation("UTC")
	for _, pull := range allPulls {
		pr, err := githubClnt.GetPR(repo, *pull.Number)
		if err != nil {
			return err
		}

		// Close the PR if it is expired (after 1 day)
		expiresAt := pr.CreatedAt.In(utc).Add(24 * time.Hour)
		if time.Now().In(utc).After(expiresAt) {
			log.Printf("Closing expired https://github.com/%s/%s/pull/%d..", owner, repo, *pr.Number)

			if err2 := githubClnt.CreateComment(repo, pull, "Tests did not pass and this request has expired. Closing out."); err != nil {
				return err2
			}
			if err2 := githubClnt.ClosePR(repo, pr); err != nil {
				return err2
			}
			log.Printf("Closed https://github.com/%s/%s/pull/%d and deleted branch.", owner, repo, *pr.Number)

			if err2 := githubClnt.DeleteBranch(repo, pr); err != nil {
				// Proceed to other PRs even if we cannot delete the branch.
				log.Printf("Cannot delete branch: %v.", err2)
			} else {
				log.Print("Deleted branch")
			}
			break
		}

		status, combinedStatus, err := githubClnt.GetPRTestResults(repo, pr, true)
		if err != nil {
			return err
		}
		ci := u.NewCIState()
		switch status {
		case ci.Success:
			log.Printf("Merging https://github.com/%s/%s/pull/%d.", owner, repo, *pr.Number)
			if err = githubClnt.MergePR(repo, *pr.Number, "Release qualification passed"); err != nil {
				return err
			}
			log.Printf("Merged https://github.com/%s/%s/pull/%d.", owner, repo, *pr.Number)

			// Re-fetch PR since it has been updated.
			pr, err = githubClnt.GetPR(repo, *pull.Number)
			if err != nil {
				return err
			}
			if err = githubClnt.DeleteBranch(repo, pr); err != nil {
				// Proceed to other PRs even if we cannot delete the branch.
				log.Printf("Cannot delete branch: %v.", err)
			} else {
				log.Print("Deleted branch")
			}

		case ci.Pending:
			log.Printf("https://github.com/%s/%s/pull/%d is still being tested. Skipping.", owner, repo, *pr.Number)
		case ci.Error:
		case ci.Failure:
			// Trigger a retest
			comments, err := githubClnt.ListIssueComments(repo, pull)
			if err != nil {
				return err
			}
			retestCount := 0
			for _, comment := range comments {
				if strings.HasPrefix(*comment.Body, testCommand) {
					retestCount++
				}
			}
			if retestCount < maxRetests {
				log.Printf("Retesting https://github.com/%s/%s/pull/%d.", owner, repo, *pr.Number)
				comment := ""
				for _, status := range combinedStatus.Statuses {
					if *status.State == ci.Error || *status.State == ci.Failure {
						context := *status.Context
						if strings.HasPrefix(context, "prow/") {
							testName := context[5:]
							comment += testCommand + " " + testName + "\n"
						}
					}
				}
				if err := githubClnt.CreateComment(repo, pull, comment); err != nil {
					return err
				}
				log.Printf("Commented: %s", comment)
			} else {
				log.Printf("Already retested https://github.com/%s/%s/pull/%d %d times. Skipping.", owner, repo, *pr.Number, retestCount)
			}
		}
	}
	return nil
}

func init() {
	flag.Parse()
	u.AssertNotEmpty("owner", owner)
	u.AssertNotEmpty("token_file", tokenFile)
	token, err := u.GetAPITokenFromFile(*tokenFile)
	if err != nil {
		log.Printf("Error accessing user supplied token_file: %v\n", err)
	}
	githubClnt = u.NewGithubClient(*owner, token)
}

func main() {
	u.AssertNotEmpty("repo", repo)

	var err error
	switch *op {
	case "fastForward":
		u.AssertNotEmpty("base_branch", baseBranch)
		u.AssertNotEmpty("ref_sha", refSHA)
		err = fastForward(*repo, *baseBranch, *refSHA)
	// the following three cases are related to release pipeline
	case "newReleaseRequest":
		u.AssertNotEmpty("pipeline", pipelineType)
		u.AssertNotEmpty("tag", tag)
		u.AssertNotEmpty("base_branch", baseBranch)
		u.AssertNotEmpty("ref_sha", refSHA)
		err = CreateReleaseRequest(*repo, *pipelineType, *tag, *baseBranch, *refSHA)
	case "cleanupReleaseRequests":
		err = CleanupReleaseRequests(*owner, *repo)
	case "getBaseSHA":
		var baseSha string
		baseSha, err = getBaseSha(*repo, *prNum)
		if err == nil {
			fmt.Print(baseSha)
		}
	default:
		err = fmt.Errorf("unsupported operation: %s", *op)
	}

	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>// Copyright © 2013-2016 Pierre Neidhardt <ambrevar@gmail.com>
// Use of this file is governed by the license that can be found in LICENSE.

// Convert 'input' and 'output' from Go to Lua and from Lua to Go. Almost all
// scripting support is implemented in this file: in case of library change,
// this is the only file that would need some overhaul.

package main

import (
	"fmt"
	"log"
	"reflect"

	"bitbucket.org/ambrevar/golua/unicode"
	"github.com/aarzilli/golua/lua"
	"github.com/stevedonovan/luar"
)

const (
	registryWhitelist = "_whitelist"
	registryScripts   = "_scripts"
	registryActions   = "_actions"
)

// goToLua copies Go values to Lua and sets the result to global 'name'.
// Compound types are deep-copied.
// Functions are automatically converted to 'func (L *lua.State) int'.
func goToLua(L *lua.State, name string, val interface{}) {
	luar.GoToLua(L, nil, reflect.ValueOf(val), true)
	L.SetGlobal(name)
}

// Registers a Go function as a global variable and add it to the sandbox.
func sandboxRegister(L *lua.State, name string, f interface{}) {
	goToLua(L, name, f)

	L.PushString(registryWhitelist)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	L.GetGlobal(name)
	L.SetField(-2, name)
}

// MakeSandbox initializes a Lua state, removes all elements not in the
// whitelist, sets up the debug function if necessary and adds some Go helper
// functions.
// The caller is responsible for closing the Lua state.
// Add a `defer L.Close()` to the calling code if there is no error.
func MakeSandbox(logPrint func(v ...interface{})) (*lua.State, error) {
	L := lua.NewState()
	L.OpenLibs()
	unicode.GoLuaReplaceFuncs(L)

	// Store the whitelist in registry to avoid tampering it.
	L.PushString(registryWhitelist)
	err := L.DoString(luaWhitelist)
	if err != nil {
		log.Fatal("Spurious sandbox", err)
	}
	L.SetTable(lua.LUA_REGISTRYINDEX)

	// Register before setting up the sandbox: these functions will be restored
	// together with the sandbox.
	// The closure allows access to the external logger.
	luaDebug := func(L *lua.State) int { return 0 }
	if logPrint != nil {
		luaDebug = func(L *lua.State) int {
			var arglist []interface{}
			nargs := L.GetTop()
			for i := 1; i <= nargs; i++ {
				if L.IsString(i) {
					arglist = append(arglist, L.ToString(i))
				}
			}
			logPrint(arglist...)
			return 0
		}
	}

	sandboxRegister(L, "debug", luaDebug)
	sandboxRegister(L, "stringnorm", stringNorm)
	sandboxRegister(L, "stringrel", stringRel)

	// Purge _G from everything but the content of the whitelist.
	err = L.DoString(luaSetSandbox)
	if err != nil {
		log.Fatal("Cannot load function to set sandbox", err)
	}
	L.PushString(registryWhitelist)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	err = L.Call(1, 0)
	if err != nil {
		log.Fatal("Failed to set sandbox", err)
	}

	// Init script table.
	L.PushString(registryScripts)
	L.NewTable()
	L.SetTable(lua.LUA_REGISTRYINDEX)

	// Init action table.
	L.PushString(registryActions)
	L.NewTable()
	L.SetTable(lua.LUA_REGISTRYINDEX)

	return L, nil
}

// SandboxCompileAction is like SandboxCompileScripts.
func SandboxCompileAction(L *lua.State, name, code string) {
	sandboxCompile(L, registryActions, name, code)
}

// SandboxCompileScript transfers the script buffer to the Lua state L and
// references them in LUA_REGISTRYINDEX.
func SandboxCompileScript(L *lua.State, name, code string) {
	sandboxCompile(L, registryScripts, name, code)
}

func sandboxCompile(L *lua.State, registryIndex string, name, code string) {
	L.PushString(registryIndex)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	L.PushString(name)
	err := L.LoadString(code)
	if err != 0 {
		log.Fatalf("%s: %s", name, L.ToString(-1))
		L.Pop(2)
	} else {
		L.SetTable(-3)
	}
}

func outputNumbersToStrings(L *lua.State) {
	L.GetGlobal("output")

	if !L.IsTable(-1) {
		L.NewTable()
		L.SetGlobal("output")
	}

	L.GetField(-1, "tags")
	if L.IsTable(-1) {
		// First key.
		L.PushNil()
		for L.Next(-2) != 0 {
			// Use 'key' at index -2 and 'value' at index -1.
			if L.IsString(-2) && L.IsString(-1) {
				// Convert numbers to strings.
				L.ToString(-1)
				L.SetField(-3, L.ToString(-2))
			} else {
				// Remove 'value' and keep 'key' for next iteration.
				L.Pop(1)
			}
		}
	}
	L.Pop(1)

	L.Pop(1)
}

// RunAction is similar to RunScript.
func RunAction(L *lua.State, action string, input *inputInfo, output *outputInfo, exist *inputInfo) error {
	return run(L, registryActions, action, input, output, exist)
}

// RunScript executes script named 'script' with 'input' and 'output' set as global variable.
// Any change made to 'input' is discarded. Change to 'output' are transfered
// back to Go on every script call to guarantee type consistency across script
// calls (Lua is dynamically typed).
func RunScript(L *lua.State, script string, input *inputInfo, output *outputInfo) error {
	return run(L, registryScripts, script, input, output, nil)
}

// 'exist' is optional.
func run(L *lua.State, registryIndex string, code string, input *inputInfo, output *outputInfo, exist *inputInfo) error {
	// Restore the sandbox.
	err := L.DoString(luaRestoreSandbox)
	if err != nil {
		log.Fatal("Cannot load function to restore sandbox", err)
	}
	L.PushString(registryWhitelist)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	err = L.Call(1, 0)
	if err != nil {
		log.Fatal("Failed to restore sandbox", err)
	}

	goToLua(L, "input", *input)
	goToLua(L, "output", *output)

	if exist != nil {
		goToLua(L, "existinfo", *exist)
	}

	// Shortcut (mostly for prescript and postscript).
	L.GetGlobal("input")
	L.GetField(-1, "tags")
	L.SetGlobal("i")
	L.Pop(1)
	L.GetGlobal("output")
	L.GetField(-1, "tags")
	L.SetGlobal("o")
	L.Pop(1)

	// Call the compiled script.
	L.PushString(registryIndex)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	L.PushString(code)
	if L.IsTable(-2) {
		L.GetTable(-2)
		if L.IsFunction(-1) {
			err := L.Call(0, 0)
			if err != nil {
				L.SetTop(0)
				return fmt.Errorf("%s", err)
			}
		} else {
			L.Pop(1)
		}
	} else {
		L.Pop(1)
	}
	L.Pop(1)

	// Allow tags to be numbers for convenience.
	outputNumbersToStrings(L)

	L.GetGlobal("output")
	r := luar.LuaToGo(L, reflect.TypeOf(*output), -1)
	L.Pop(1)

	*output = r.(outputInfo)

	return nil
}

// LoadConfig parses the Lua file pointed by 'config' and stores it to options.
func LoadConfig(config string, options interface{}) {
	L, err := MakeSandbox(log.Println)
	defer L.Close()

	err = L.DoFile(config)
	if err != nil {
		log.Fatalf("Error loading config: %s", err)
	}

	L.GetGlobal("_G")
	r := luar.LuaToGo(L, reflect.TypeOf(options), -1)
	L.Pop(1)

	v := reflect.ValueOf(options)
	v.Elem().Set(reflect.ValueOf(r).Elem())
}
<commit_msg>Comment on why we do not use Lua references<commit_after>// Copyright © 2013-2016 Pierre Neidhardt <ambrevar@gmail.com>
// Use of this file is governed by the license that can be found in LICENSE.

// Convert 'input' and 'output' from Go to Lua and from Lua to Go. Almost all
// scripting support is implemented in this file: in case of library change,
// this is the only file that would need some overhaul.

package main

import (
	"fmt"
	"log"
	"reflect"

	"bitbucket.org/ambrevar/golua/unicode"
	"github.com/aarzilli/golua/lua"
	"github.com/stevedonovan/luar"
)

const (
	// Note: we do not use Lua references (luaL_ref) so that we do not have to
	// pass them, together with the Lua state, to the calling goroutine to ensure
	// re-entrency.
	registryWhitelist = "_whitelist"
	registryScripts   = "_scripts"
	registryActions   = "_actions"
)

// goToLua copies Go values to Lua and sets the result to global 'name'.
// Compound types are deep-copied.
// Functions are automatically converted to 'func (L *lua.State) int'.
func goToLua(L *lua.State, name string, val interface{}) {
	luar.GoToLua(L, nil, reflect.ValueOf(val), true)
	L.SetGlobal(name)
}

// Registers a Go function as a global variable and add it to the sandbox.
func sandboxRegister(L *lua.State, name string, f interface{}) {
	goToLua(L, name, f)

	L.PushString(registryWhitelist)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	L.GetGlobal(name)
	L.SetField(-2, name)
}

// MakeSandbox initializes a Lua state, removes all elements not in the
// whitelist, sets up the debug function if necessary and adds some Go helper
// functions.
// The caller is responsible for closing the Lua state.
// Add a `defer L.Close()` to the calling code if there is no error.
func MakeSandbox(logPrint func(v ...interface{})) (*lua.State, error) {
	L := lua.NewState()
	L.OpenLibs()
	unicode.GoLuaReplaceFuncs(L)

	// Store the whitelist in registry to avoid tampering it.
	L.PushString(registryWhitelist)
	err := L.DoString(luaWhitelist)
	if err != nil {
		log.Fatal("Spurious sandbox", err)
	}
	L.SetTable(lua.LUA_REGISTRYINDEX)

	// Register before setting up the sandbox: these functions will be restored
	// together with the sandbox.
	// The closure allows access to the external logger.
	luaDebug := func(L *lua.State) int { return 0 }
	if logPrint != nil {
		luaDebug = func(L *lua.State) int {
			var arglist []interface{}
			nargs := L.GetTop()
			for i := 1; i <= nargs; i++ {
				if L.IsString(i) {
					arglist = append(arglist, L.ToString(i))
				}
			}
			logPrint(arglist...)
			return 0
		}
	}

	sandboxRegister(L, "debug", luaDebug)
	sandboxRegister(L, "stringnorm", stringNorm)
	sandboxRegister(L, "stringrel", stringRel)

	// Purge _G from everything but the content of the whitelist.
	err = L.DoString(luaSetSandbox)
	if err != nil {
		log.Fatal("Cannot load function to set sandbox", err)
	}
	L.PushString(registryWhitelist)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	err = L.Call(1, 0)
	if err != nil {
		log.Fatal("Failed to set sandbox", err)
	}

	// Init script table.
	L.PushString(registryScripts)
	L.NewTable()
	L.SetTable(lua.LUA_REGISTRYINDEX)

	// Init action table.
	L.PushString(registryActions)
	L.NewTable()
	L.SetTable(lua.LUA_REGISTRYINDEX)

	return L, nil
}

// SandboxCompileAction is like SandboxCompileScripts.
func SandboxCompileAction(L *lua.State, name, code string) {
	sandboxCompile(L, registryActions, name, code)
}

// SandboxCompileScript transfers the script buffer to the Lua state L and
// references them in LUA_REGISTRYINDEX.
func SandboxCompileScript(L *lua.State, name, code string) {
	sandboxCompile(L, registryScripts, name, code)
}

func sandboxCompile(L *lua.State, registryIndex string, name, code string) {
	L.PushString(registryIndex)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	L.PushString(name)
	err := L.LoadString(code)
	if err != 0 {
		log.Fatalf("%s: %s", name, L.ToString(-1))
		L.Pop(2)
	} else {
		L.SetTable(-3)
	}
}

func outputNumbersToStrings(L *lua.State) {
	L.GetGlobal("output")

	if !L.IsTable(-1) {
		L.NewTable()
		L.SetGlobal("output")
	}

	L.GetField(-1, "tags")
	if L.IsTable(-1) {
		// First key.
		L.PushNil()
		for L.Next(-2) != 0 {
			// Use 'key' at index -2 and 'value' at index -1.
			if L.IsString(-2) && L.IsString(-1) {
				// Convert numbers to strings.
				L.ToString(-1)
				L.SetField(-3, L.ToString(-2))
			} else {
				// Remove 'value' and keep 'key' for next iteration.
				L.Pop(1)
			}
		}
	}
	L.Pop(1)

	L.Pop(1)
}

// RunAction is similar to RunScript.
func RunAction(L *lua.State, action string, input *inputInfo, output *outputInfo, exist *inputInfo) error {
	return run(L, registryActions, action, input, output, exist)
}

// RunScript executes script named 'script' with 'input' and 'output' set as global variable.
// Any change made to 'input' is discarded. Change to 'output' are transfered
// back to Go on every script call to guarantee type consistency across script
// calls (Lua is dynamically typed).
func RunScript(L *lua.State, script string, input *inputInfo, output *outputInfo) error {
	return run(L, registryScripts, script, input, output, nil)
}

// 'exist' is optional.
func run(L *lua.State, registryIndex string, code string, input *inputInfo, output *outputInfo, exist *inputInfo) error {
	// Restore the sandbox.
	err := L.DoString(luaRestoreSandbox)
	if err != nil {
		log.Fatal("Cannot load function to restore sandbox", err)
	}
	L.PushString(registryWhitelist)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	err = L.Call(1, 0)
	if err != nil {
		log.Fatal("Failed to restore sandbox", err)
	}

	goToLua(L, "input", *input)
	goToLua(L, "output", *output)

	if exist != nil {
		goToLua(L, "existinfo", *exist)
	}

	// Shortcut (mostly for prescript and postscript).
	L.GetGlobal("input")
	L.GetField(-1, "tags")
	L.SetGlobal("i")
	L.Pop(1)
	L.GetGlobal("output")
	L.GetField(-1, "tags")
	L.SetGlobal("o")
	L.Pop(1)

	// Call the compiled script.
	L.PushString(registryIndex)
	L.GetTable(lua.LUA_REGISTRYINDEX)
	L.PushString(code)
	if L.IsTable(-2) {
		L.GetTable(-2)
		if L.IsFunction(-1) {
			err := L.Call(0, 0)
			if err != nil {
				L.SetTop(0)
				return fmt.Errorf("%s", err)
			}
		} else {
			L.Pop(1)
		}
	} else {
		L.Pop(1)
	}
	L.Pop(1)

	// Allow tags to be numbers for convenience.
	outputNumbersToStrings(L)

	L.GetGlobal("output")
	r := luar.LuaToGo(L, reflect.TypeOf(*output), -1)
	L.Pop(1)

	*output = r.(outputInfo)

	return nil
}

// LoadConfig parses the Lua file pointed by 'config' and stores it to options.
func LoadConfig(config string, options interface{}) {
	L, err := MakeSandbox(log.Println)
	defer L.Close()

	err = L.DoFile(config)
	if err != nil {
		log.Fatalf("Error loading config: %s", err)
	}

	L.GetGlobal("_G")
	r := luar.LuaToGo(L, reflect.TypeOf(options), -1)
	L.Pop(1)

	v := reflect.ValueOf(options)
	v.Elem().Set(reflect.ValueOf(r).Elem())
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"sort"

	"github.com/olekukonko/tablewriter"
	"github.com/spf13/cobra"

	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/i18n"
)

type cmdAlias struct {
	global *cmdGlobal
}

func (c *cmdAlias) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("alias")
	cmd.Short = i18n.G("Manage command aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Manage command aliases`))

	// Add
	aliasAddCmd := cmdAliasAdd{global: c.global, alias: c}
	cmd.AddCommand(aliasAddCmd.Command())

	// List
	aliasListCmd := cmdAliasList{global: c.global, alias: c}
	cmd.AddCommand(aliasListCmd.Command())

	// Rename
	aliasRenameCmd := cmdAliasRename{global: c.global, alias: c}
	cmd.AddCommand(aliasRenameCmd.Command())

	// Remove
	aliasRemoveCmd := cmdAliasRemove{global: c.global, alias: c}
	cmd.AddCommand(aliasRemoveCmd.Command())

	return cmd
}

// Add
type cmdAliasAdd struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasAdd) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("add <alias> <target>")
	cmd.Short = i18n.G("Add new aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Add new aliases`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc alias add list "list -c ns46S"
    Overwrite the "list" command to pass -c ns46S.`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasAdd) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 2, 2)
	if exit {
		return err
	}

	// Look for an existing alias
	_, ok := conf.Aliases[args[0]]
	if ok {
		return fmt.Errorf(i18n.G("Alias %s already exists"), args[0])
	}

	// Add the new alias
	conf.Aliases[args[0]] = args[1]

	// Save the config
	return conf.SaveConfig(c.global.confPath)
}

// List
type cmdAliasList struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasList) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("list")
	cmd.Aliases = []string{"ls"}
	cmd.Short = i18n.G("List aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`List aliases`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasList) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 0, 0)
	if exit {
		return err
	}

	// List the aliases
	data := [][]string{}
	for k, v := range conf.Aliases {
		data = append(data, []string{k, v})
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetAutoWrapText(false)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetRowLine(true)
	table.SetHeader([]string{
		i18n.G("ALIAS"),
		i18n.G("TARGET")})
	sort.Sort(byName(data))
	table.AppendBulk(data)
	table.Render()

	return nil
}

// Rename
type cmdAliasRename struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasRename) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("rename <old alias> <new alias>")
	cmd.Aliases = []string{"mv"}
	cmd.Short = i18n.G("Rename aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Rename aliases`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc alias rename list my-list
    Rename existing alias "list" to "my-list".`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasRename) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 2, 2)
	if exit {
		return err
	}

	// Check for the existing alias
	target, ok := conf.Aliases[args[0]]
	if !ok {
		return fmt.Errorf(i18n.G("Alias %s doesn't exist"), args[0])
	}

	// Check for the new alias
	_, ok = conf.Aliases[args[1]]
	if ok {
		return fmt.Errorf(i18n.G("Alias %s already exists"), args[1])
	}

	// Rename the alias
	conf.Aliases[args[1]] = target
	delete(conf.Aliases, args[0])

	// Save the config
	return conf.SaveConfig(c.global.confPath)
}

// Remove
type cmdAliasRemove struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasRemove) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("remove <alias>")
	cmd.Aliases = []string{"rm"}
	cmd.Short = i18n.G("Remove aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Remove aliases`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc alias remove my-list
    Remove the "my-list" alias.`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasRemove) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 1, 1)
	if exit {
		return err
	}

	// Look for the alias
	_, ok := conf.Aliases[args[0]]
	if !ok {
		return fmt.Errorf(i18n.G("Alias %s doesn't exist"), args[0])
	}

	// Delete the alias
	delete(conf.Aliases, args[0])

	// Save the config
	return conf.SaveConfig(c.global.confPath)
}
<commit_msg>lxc/alias: Use renderTable for list<commit_after>package main

import (
	"fmt"

	"github.com/spf13/cobra"

	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/i18n"
)

type cmdAlias struct {
	global *cmdGlobal
}

func (c *cmdAlias) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("alias")
	cmd.Short = i18n.G("Manage command aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Manage command aliases`))

	// Add
	aliasAddCmd := cmdAliasAdd{global: c.global, alias: c}
	cmd.AddCommand(aliasAddCmd.Command())

	// List
	aliasListCmd := cmdAliasList{global: c.global, alias: c}
	cmd.AddCommand(aliasListCmd.Command())

	// Rename
	aliasRenameCmd := cmdAliasRename{global: c.global, alias: c}
	cmd.AddCommand(aliasRenameCmd.Command())

	// Remove
	aliasRemoveCmd := cmdAliasRemove{global: c.global, alias: c}
	cmd.AddCommand(aliasRemoveCmd.Command())

	return cmd
}

// Add
type cmdAliasAdd struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasAdd) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("add <alias> <target>")
	cmd.Short = i18n.G("Add new aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Add new aliases`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc alias add list "list -c ns46S"
    Overwrite the "list" command to pass -c ns46S.`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasAdd) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 2, 2)
	if exit {
		return err
	}

	// Look for an existing alias
	_, ok := conf.Aliases[args[0]]
	if ok {
		return fmt.Errorf(i18n.G("Alias %s already exists"), args[0])
	}

	// Add the new alias
	conf.Aliases[args[0]] = args[1]

	// Save the config
	return conf.SaveConfig(c.global.confPath)
}

// List
type cmdAliasList struct {
	global *cmdGlobal
	alias  *cmdAlias

	flagFormat string
}

func (c *cmdAliasList) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("list")
	cmd.Aliases = []string{"ls"}
	cmd.Short = i18n.G("List aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`List aliases`))
	cmd.Flags().StringVar(&c.flagFormat, "format", "table", i18n.G("Format (csv|json|table|yaml)")+"``")

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasList) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 0, 0)
	if exit {
		return err
	}

	// List the aliases
	data := [][]string{}
	for k, v := range conf.Aliases {
		data = append(data, []string{k, v})
	}

	header := []string{
		i18n.G("ALIAS"),
		i18n.G("TARGET"),
	}

	return renderTable(c.flagFormat, header, data, conf.Aliases)
}

// Rename
type cmdAliasRename struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasRename) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("rename <old alias> <new alias>")
	cmd.Aliases = []string{"mv"}
	cmd.Short = i18n.G("Rename aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Rename aliases`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc alias rename list my-list
    Rename existing alias "list" to "my-list".`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasRename) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 2, 2)
	if exit {
		return err
	}

	// Check for the existing alias
	target, ok := conf.Aliases[args[0]]
	if !ok {
		return fmt.Errorf(i18n.G("Alias %s doesn't exist"), args[0])
	}

	// Check for the new alias
	_, ok = conf.Aliases[args[1]]
	if ok {
		return fmt.Errorf(i18n.G("Alias %s already exists"), args[1])
	}

	// Rename the alias
	conf.Aliases[args[1]] = target
	delete(conf.Aliases, args[0])

	// Save the config
	return conf.SaveConfig(c.global.confPath)
}

// Remove
type cmdAliasRemove struct {
	global *cmdGlobal
	alias  *cmdAlias
}

func (c *cmdAliasRemove) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("remove <alias>")
	cmd.Aliases = []string{"rm"}
	cmd.Short = i18n.G("Remove aliases")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Remove aliases`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc alias remove my-list
    Remove the "my-list" alias.`))

	cmd.RunE = c.Run

	return cmd
}

func (c *cmdAliasRemove) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 1, 1)
	if exit {
		return err
	}

	// Look for the alias
	_, ok := conf.Aliases[args[0]]
	if !ok {
		return fmt.Errorf(i18n.G("Alias %s doesn't exist"), args[0])
	}

	// Delete the alias
	delete(conf.Aliases, args[0])

	// Save the config
	return conf.SaveConfig(c.global.confPath)
}
<|endoftext|>