<commit_before>/*
Copyright 2018, 2019 the Velero contributors.

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 restic

import (
	"context"
	"fmt"
	"sync"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"

	velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
	velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
	velerov1informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
	velerov1listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
)

// repositoryEnsurer ensures that Velero restic repositories are created and ready.
type repositoryEnsurer struct {
	log        logrus.FieldLogger
	repoLister velerov1listers.ResticRepositoryLister
	repoClient velerov1client.ResticRepositoriesGetter

	readyChansLock sync.Mutex
	readyChans     map[string]chan *velerov1api.ResticRepository

	// repoLocksMu synchronizes reads/writes to the repoLocks map itself
	// since maps are not threadsafe.
	repoLocksMu sync.Mutex
	repoLocks   map[repoKey]*sync.Mutex
}

type repoKey struct {
	volumeNamespace string
	backupLocation  string
}

func newRepositoryEnsurer(repoInformer velerov1informers.ResticRepositoryInformer, repoClient velerov1client.ResticRepositoriesGetter, log logrus.FieldLogger) *repositoryEnsurer {
	r := &repositoryEnsurer{
		log:        log,
		repoLister: repoInformer.Lister(),
		repoClient: repoClient,
		readyChans: make(map[string]chan *velerov1api.ResticRepository),
		repoLocks:  make(map[repoKey]*sync.Mutex),
	}

	repoInformer.Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			UpdateFunc: func(old, upd interface{}) {
				oldObj := old.(*velerov1api.ResticRepository)
				newObj := upd.(*velerov1api.ResticRepository)

				if oldObj.Status.Phase != velerov1api.ResticRepositoryPhaseReady && newObj.Status.Phase == velerov1api.ResticRepositoryPhaseReady {
					r.readyChansLock.Lock()
					defer r.readyChansLock.Unlock()

					key := repoLabels(newObj.Spec.VolumeNamespace, newObj.Spec.BackupStorageLocation).String()
					readyChan, ok := r.readyChans[key]
					if !ok {
						log.Debugf("No ready channel found for repository %s/%s", newObj.Namespace, newObj.Name)
						return
					}

					readyChan <- newObj
					delete(r.readyChans, key)
				}
			},
		},
	)

	return r
}

func repoLabels(volumeNamespace, backupLocation string) labels.Set {
	return map[string]string{
		velerov1api.ResticVolumeNamespaceLabel: volumeNamespace,
		velerov1api.StorageLocationLabel:       backupLocation,
	}
}

func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNamespace, backupLocation string) (*velerov1api.ResticRepository, error) {
	log := r.log.WithField("volumeNamespace", volumeNamespace).WithField("backupLocation", backupLocation)

	// It's only safe to have one instance of this method executing concurrently for a
	// given volumeNamespace + backupLocation, so synchronize based on that. It's fine
	// to run concurrently for *different* namespaces/locations. If you had 2 goroutines
	// running this for the same inputs, both might find no ResticRepository exists, then
	// both would create new ones for the same namespace/location.
	//
	// This issue could probably be avoided if we had a deterministic name for
	// each restic repository, and we just tried to create it, checked for an
	// AlreadyExists err, and then waited for it to be ready. However, there are
	// already repositories in the wild with non-deterministic names (i.e. using
	// GenerateName) which poses a backwards compatibility problem.
	log.Debug("Acquiring lock")

	repoMu := r.repoLock(volumeNamespace, backupLocation)
	repoMu.Lock()
	defer func() {
		repoMu.Unlock()
		log.Debug("Released lock")
	}()

	log.Debug("Acquired lock")

	selector := labels.SelectorFromSet(repoLabels(volumeNamespace, backupLocation))

	repos, err := r.repoLister.ResticRepositories(namespace).List(selector)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	if len(repos) > 1 {
		return nil, errors.Errorf("more than one ResticRepository found for workload namespace %q, backup storage location %q", volumeNamespace, backupLocation)
	}
	if len(repos) == 1 {
		if repos[0].Status.Phase != velerov1api.ResticRepositoryPhaseReady {
			return nil, errors.New("restic repository is not ready")
		}

		log.Debug("Ready repository found")
		return repos[0], nil
	}

	log.Debug("No repository found, creating one")

	// no repo found: create one and wait for it to be ready
	repo := &velerov1api.ResticRepository{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:    namespace,
			GenerateName: fmt.Sprintf("%s-%s-", volumeNamespace, backupLocation),
			Labels:       repoLabels(volumeNamespace, backupLocation),
		},
		Spec: velerov1api.ResticRepositorySpec{
			VolumeNamespace:       volumeNamespace,
			BackupStorageLocation: backupLocation,
			MaintenanceFrequency:  metav1.Duration{Duration: DefaultMaintenanceFrequency},
		},
	}

	readyChan := r.getReadyChan(selector.String())
	defer close(readyChan)

	if _, err := r.repoClient.ResticRepositories(namespace).Create(repo); err != nil {
		return nil, errors.Wrapf(err, "unable to create restic repository resource")
	}

	select {
	case <-ctx.Done():
		return nil, errors.New("timed out waiting for restic repository to become ready")
	case res := <-readyChan:
		return res, nil
	}
}

func (r *repositoryEnsurer) getReadyChan(name string) chan *velerov1api.ResticRepository {
	r.readyChansLock.Lock()
	defer r.readyChansLock.Unlock()

	r.readyChans[name] = make(chan *velerov1api.ResticRepository)
	return r.readyChans[name]
}

func (r *repositoryEnsurer) repoLock(volumeNamespace, backupLocation string) *sync.Mutex {
	r.repoLocksMu.Lock()
	defer r.repoLocksMu.Unlock()

	key := repoKey{
		volumeNamespace: volumeNamespace,
		backupLocation:  backupLocation,
	}

	if r.repoLocks[key] == nil {
		r.repoLocks[key] = new(sync.Mutex)
	}

	return r.repoLocks[key]
}
<commit_msg>repo ensurer: return error if new repo becomes NotReady<commit_after>/*
Copyright 2018, 2019 the Velero contributors.

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 restic

import (
	"context"
	"fmt"
	"sync"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"

	velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
	velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
	velerov1informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
	velerov1listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
)

// repositoryEnsurer ensures that Velero restic repositories are created and ready.
type repositoryEnsurer struct {
	log        logrus.FieldLogger
	repoLister velerov1listers.ResticRepositoryLister
	repoClient velerov1client.ResticRepositoriesGetter

	readyChansLock sync.Mutex
	readyChans     map[string]chan *velerov1api.ResticRepository

	// repoLocksMu synchronizes reads/writes to the repoLocks map itself
	// since maps are not threadsafe.
	repoLocksMu sync.Mutex
	repoLocks   map[repoKey]*sync.Mutex
}

type repoKey struct {
	volumeNamespace string
	backupLocation  string
}

func newRepositoryEnsurer(repoInformer velerov1informers.ResticRepositoryInformer, repoClient velerov1client.ResticRepositoriesGetter, log logrus.FieldLogger) *repositoryEnsurer {
	r := &repositoryEnsurer{
		log:        log,
		repoLister: repoInformer.Lister(),
		repoClient: repoClient,
		readyChans: make(map[string]chan *velerov1api.ResticRepository),
		repoLocks:  make(map[repoKey]*sync.Mutex),
	}

	repoInformer.Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			UpdateFunc: func(old, upd interface{}) {
				oldObj := old.(*velerov1api.ResticRepository)
				newObj := upd.(*velerov1api.ResticRepository)

				// we're only interested in phase-changing updates
				if oldObj.Status.Phase == newObj.Status.Phase {
					return
				}

				// we're only interested in updates where the updated object is either Ready or NotReady
				if newObj.Status.Phase != velerov1api.ResticRepositoryPhaseReady && newObj.Status.Phase != velerov1api.ResticRepositoryPhaseNotReady {
					return
				}

				r.readyChansLock.Lock()
				defer r.readyChansLock.Unlock()

				key := repoLabels(newObj.Spec.VolumeNamespace, newObj.Spec.BackupStorageLocation).String()
				readyChan, ok := r.readyChans[key]
				if !ok {
					log.Debugf("No ready channel found for repository %s/%s", newObj.Namespace, newObj.Name)
					return
				}

				readyChan <- newObj
				delete(r.readyChans, key)
			},
		},
	)

	return r
}

func repoLabels(volumeNamespace, backupLocation string) labels.Set {
	return map[string]string{
		velerov1api.ResticVolumeNamespaceLabel: volumeNamespace,
		velerov1api.StorageLocationLabel:       backupLocation,
	}
}

func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNamespace, backupLocation string) (*velerov1api.ResticRepository, error) {
	log := r.log.WithField("volumeNamespace", volumeNamespace).WithField("backupLocation", backupLocation)

	// It's only safe to have one instance of this method executing concurrently for a
	// given volumeNamespace + backupLocation, so synchronize based on that. It's fine
	// to run concurrently for *different* namespaces/locations. If you had 2 goroutines
	// running this for the same inputs, both might find no ResticRepository exists, then
	// both would create new ones for the same namespace/location.
	//
	// This issue could probably be avoided if we had a deterministic name for
	// each restic repository, and we just tried to create it, checked for an
	// AlreadyExists err, and then waited for it to be ready. However, there are
	// already repositories in the wild with non-deterministic names (i.e. using
	// GenerateName) which poses a backwards compatibility problem.
	log.Debug("Acquiring lock")

	repoMu := r.repoLock(volumeNamespace, backupLocation)
	repoMu.Lock()
	defer func() {
		repoMu.Unlock()
		log.Debug("Released lock")
	}()

	log.Debug("Acquired lock")

	selector := labels.SelectorFromSet(repoLabels(volumeNamespace, backupLocation))

	repos, err := r.repoLister.ResticRepositories(namespace).List(selector)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	if len(repos) > 1 {
		return nil, errors.Errorf("more than one ResticRepository found for workload namespace %q, backup storage location %q", volumeNamespace, backupLocation)
	}
	if len(repos) == 1 {
		if repos[0].Status.Phase != velerov1api.ResticRepositoryPhaseReady {
			return nil, errors.Errorf("restic repository is not ready: %s", repos[0].Status.Message)
		}

		log.Debug("Ready repository found")
		return repos[0], nil
	}

	log.Debug("No repository found, creating one")

	// no repo found: create one and wait for it to be ready
	repo := &velerov1api.ResticRepository{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:    namespace,
			GenerateName: fmt.Sprintf("%s-%s-", volumeNamespace, backupLocation),
			Labels:       repoLabels(volumeNamespace, backupLocation),
		},
		Spec: velerov1api.ResticRepositorySpec{
			VolumeNamespace:       volumeNamespace,
			BackupStorageLocation: backupLocation,
			MaintenanceFrequency:  metav1.Duration{Duration: DefaultMaintenanceFrequency},
		},
	}

	readyChan := r.getReadyChan(selector.String())
	defer close(readyChan)

	if _, err := r.repoClient.ResticRepositories(namespace).Create(repo); err != nil {
		return nil, errors.Wrapf(err, "unable to create restic repository resource")
	}

	select {
	// TODO it doesn't really make sense to wait for the full pod volume operation timeout
	// here - repos should become ready quickly if they don't already exist.
	case <-ctx.Done():
		return nil, errors.New("timed out waiting for restic repository to become ready")
	case res := <-readyChan:
		if res.Status.Phase == velerov1api.ResticRepositoryPhaseNotReady {
			return nil, errors.Errorf("restic repository is not ready: %s", res.Status.Message)
		}

		return res, nil
	}
}

func (r *repositoryEnsurer) getReadyChan(name string) chan *velerov1api.ResticRepository {
	r.readyChansLock.Lock()
	defer r.readyChansLock.Unlock()

	r.readyChans[name] = make(chan *velerov1api.ResticRepository)
	return r.readyChans[name]
}

func (r *repositoryEnsurer) repoLock(volumeNamespace, backupLocation string) *sync.Mutex {
	r.repoLocksMu.Lock()
	defer r.repoLocksMu.Unlock()

	key := repoKey{
		volumeNamespace: volumeNamespace,
		backupLocation:  backupLocation,
	}

	if r.repoLocks[key] == nil {
		r.repoLocks[key] = new(sync.Mutex)
	}

	return r.repoLocks[key]
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Rook 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 cluster to manage a Ceph cluster.
package cluster

import (
	"time"

	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/clusterd"
	"github.com/rook/rook/pkg/daemon/ceph/client"
	cephconfig "github.com/rook/rook/pkg/daemon/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/cluster/crash"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mon"
	"github.com/rook/rook/pkg/operator/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/csi"
	"github.com/rook/rook/pkg/operator/k8sutil"
	v1 "k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

func (c *ClusterController) configureExternalCephCluster(cluster *cluster) error {
	// Make sure the spec contains all the information we need
	err := validateExternalClusterSpec(cluster)
	if err != nil {
		return errors.Wrap(err, "failed to validate external cluster specs")
	}

	config.ConditionExport(c.context, c.namespacedName, cephv1.ConditionConnecting, v1.ConditionTrue, "ClusterConnecting", "Cluster is connecting")

	// loop until we find the secret necessary to connect to the external cluster
	// then populate clusterInfo
	cluster.Info = populateExternalClusterInfo(c.context, c.namespacedName.Namespace)

	// If the user to check the ceph health and status is not the admin,
	// we validate that ExternalCred has been populated correctly,
	// then we check if the key (whether admin or not) is encoded in base64
	if !isExternalHealthCheckUserAdmin(cluster.Info.AdminSecret) {
		if !cluster.Info.IsInitializedExternalCred(true) {
			return errors.New("invalid user health checker credentials")
		}
		if !cephconfig.IsKeyringBase64Encoded(cluster.Info.ExternalCred.Secret) {
			return errors.Errorf("invalid user health checker key %q", cluster.Info.ExternalCred.Username)
		}
	} else {
		// If the client.admin is used
		if !cephconfig.IsKeyringBase64Encoded(cluster.Info.AdminSecret) {
			return errors.Errorf("invalid user health checker key %q", client.AdminUsername)
		}
	}

	// Write connection info (ceph config file and keyring) for ceph commands
	if cluster.Spec.CephVersion.Image == "" {
		err = mon.WriteConnectionConfig(c.context, cluster.Info)
		if err != nil {
			logger.Errorf("failed to write config. attempting to continue. %v", err)
		}
	}

	// Validate versions (local and external)
	// If no image is specified we don't perform any checks
	if cluster.Spec.CephVersion.Image != "" {
		_, _, err = c.detectAndValidateCephVersion(cluster)
		if err != nil {
			return errors.Wrap(err, "failed to detect and validate ceph version")
		}

		// Write the rook-config-override configmap (used by various daemons to apply config overrides)
		// If we don't do this, daemons will never start, waiting forever for this configmap to be present
		//
		// Only do this when doing a bit of management...
		logger.Info("creating 'rook-ceph-config' configmap.")
		err = populateConfigOverrideConfigMap(cluster.context, c.namespacedName.Namespace, cluster.ownerRef)
		if err != nil {
			return errors.Wrap(err, "failed to populate config override config map")
		}
	}

	// The cluster Identity must be established at this point
	if !cluster.Info.IsInitialized(true) {
		return errors.New("the cluster identity was not established")
	}
	logger.Info("external cluster identity established")

	// Create CSI Secrets only if the user has provided the admin key
	if cluster.Info.AdminSecret != mon.AdminSecretName {
		err = csi.CreateCSISecrets(c.context, c.namespacedName.Namespace, &cluster.ownerRef)
		if err != nil {
			return errors.Wrap(err, "failed to create csi kubernetes secrets")
		}
	}

	// Create CSI config map
	err = csi.CreateCsiConfigMap(c.namespacedName.Namespace, c.context.Clientset, &cluster.ownerRef)
	if err != nil {
		return errors.Wrap(err, "failed to create csi config map")
	}

	// Save CSI configmap
	err = csi.SaveClusterConfig(c.context.Clientset, c.namespacedName.Namespace, cluster.Info, c.csiConfigMutex)
	if err != nil {
		return errors.Wrap(err, "failed to update csi cluster config")
	}
	logger.Info("successfully updated csi config map")

	// Create Crash Collector Secret
	// In 14.2.5 the crash daemon will read the client.crash key instead of the admin key
	if !cluster.Spec.CrashCollector.Disable {
		err = crash.CreateCrashCollectorSecret(c.context, c.namespacedName.Namespace, &cluster.ownerRef)
		if err != nil {
			return errors.Wrap(err, "failed to create crash collector kubernetes secret")
		}
	}

	// Everything went well so let's update the CR's status to "connected"
	config.ConditionExport(c.context, c.namespacedName, cephv1.ConditionConnected, v1.ConditionTrue, "ClusterConnected", "Cluster connected successfully")

	// Mark initialization has done
	cluster.initCompleted = true

	return nil
}

func purgeExternalCluster(clientset kubernetes.Interface, namespace string) error {
	// Purge the config maps
	cmsToDelete := []string{
		mon.EndpointConfigMapName,
		config.StoreName,
		k8sutil.ConfigOverrideName,
	}
	for _, cm := range cmsToDelete {
		err := clientset.CoreV1().ConfigMaps(namespace).Delete(cm, &metav1.DeleteOptions{})
		if err != nil && !kerrors.IsNotFound(err) {
			logger.Errorf("failed to delete config map %+v. %v", cm, err)
		}
	}

	// Purge the secrets
	secretsToDelete := []string{
		mon.AppName,
		mon.OperatorCreds,
		csi.CsiRBDNodeSecret,
		csi.CsiRBDProvisionerSecret,
		csi.CsiCephFSNodeSecret,
		csi.CsiCephFSProvisionerSecret,
	}
	for _, secret := range secretsToDelete {
		err := clientset.CoreV1().Secrets(namespace).Delete(secret, &metav1.DeleteOptions{})
		if err != nil && !kerrors.IsNotFound(err) {
			logger.Errorf("failed to delete config map %+v. %v", secret, err)
		}
	}

	return nil
}

func validateExternalClusterSpec(cluster *cluster) error {
	if cluster.Spec.CephVersion.Image != "" {
		if cluster.Spec.DataDirHostPath == "" {
			return errors.New("dataDirHostPath must be specified")
		}
	}

	return nil
}

// Add validation in the code to fail if the external cluster has no OSDs keep waiting
func populateExternalClusterInfo(context *clusterd.Context, namespace string) *cephconfig.ClusterInfo {
	var clusterInfo *cephconfig.ClusterInfo
	for {
		var err error
		clusterInfo, _, _, err = mon.LoadClusterInfo(context, namespace)
		if err != nil {
			logger.Warningf("waiting for the connection info of the external cluster. retrying in %s.", externalConnectionRetry.String())
			time.Sleep(externalConnectionRetry)
			continue
		} else {
			// If an admin key was provided we don't need to load the other resources
			// Some people might want to give the admin key
			// The necessary users/keys/secrets will be created by Rook
			// This is also done to allow backward compatibility
			if isExternalHealthCheckUserAdmin(clusterInfo.AdminSecret) {
				break
			}
			externalCred, err := mon.ValidateAndLoadExternalClusterSecrets(context, namespace)
			if err != nil {
				logger.Warningf("waiting for the connection info of the external cluster. retrying in %s.", externalConnectionRetry.String())
				logger.Debugf("%v", err)
				time.Sleep(externalConnectionRetry)
				continue
			} else {
				clusterInfo.ExternalCred = externalCred
				logger.Infof("found the cluster info to connect to the external cluster. will use %q to check health and monitor status. mons=%+v", clusterInfo.ExternalCred.Username, clusterInfo.Monitors)
				break
			}
		}
	}

	return clusterInfo
}

func isExternalHealthCheckUserAdmin(adminSecret string) bool {
	return adminSecret != mon.AdminSecretName
}
<commit_msg>ceph: create missing secret on external cluster<commit_after>/*
Copyright 2020 The Rook 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 cluster to manage a Ceph cluster.
package cluster

import (
	"time"

	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"github.com/rook/rook/pkg/clusterd"
	"github.com/rook/rook/pkg/daemon/ceph/client"
	cephconfig "github.com/rook/rook/pkg/daemon/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/cluster/crash"
	"github.com/rook/rook/pkg/operator/ceph/cluster/mon"
	"github.com/rook/rook/pkg/operator/ceph/config"
	"github.com/rook/rook/pkg/operator/ceph/csi"
	"github.com/rook/rook/pkg/operator/k8sutil"
	v1 "k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

func (c *ClusterController) configureExternalCephCluster(cluster *cluster) error {
	// Make sure the spec contains all the information we need
	err := validateExternalClusterSpec(cluster)
	if err != nil {
		return errors.Wrap(err, "failed to validate external cluster specs")
	}

	config.ConditionExport(c.context, c.namespacedName, cephv1.ConditionConnecting, v1.ConditionTrue, "ClusterConnecting", "Cluster is connecting")

	// loop until we find the secret necessary to connect to the external cluster
	// then populate clusterInfo
	cluster.Info = populateExternalClusterInfo(c.context, c.namespacedName.Namespace)

	// If the user to check the ceph health and status is not the admin,
	// we validate that ExternalCred has been populated correctly,
	// then we check if the key (whether admin or not) is encoded in base64
	if !isExternalHealthCheckUserAdmin(cluster.Info.AdminSecret) {
		if !cluster.Info.IsInitializedExternalCred(true) {
			return errors.New("invalid user health checker credentials")
		}
		if !cephconfig.IsKeyringBase64Encoded(cluster.Info.ExternalCred.Secret) {
			return errors.Errorf("invalid user health checker key %q", cluster.Info.ExternalCred.Username)
		}
	} else {
		// If the client.admin is used
		if !cephconfig.IsKeyringBase64Encoded(cluster.Info.AdminSecret) {
			return errors.Errorf("invalid user health checker key %q", client.AdminUsername)
		}
	}

	// Write connection info (ceph config file and keyring) for ceph commands
	if cluster.Spec.CephVersion.Image == "" {
		err = mon.WriteConnectionConfig(c.context, cluster.Info)
		if err != nil {
			logger.Errorf("failed to write config. attempting to continue. %v", err)
		}
	}

	// Validate versions (local and external)
	// If no image is specified we don't perform any checks
	if cluster.Spec.CephVersion.Image != "" {
		_, _, err = c.detectAndValidateCephVersion(cluster)
		if err != nil {
			return errors.Wrap(err, "failed to detect and validate ceph version")
		}

		// Write the rook-config-override configmap (used by various daemons to apply config overrides)
		// If we don't do this, daemons will never start, waiting forever for this configmap to be present
		//
		// Only do this when doing a bit of management...
		logger.Infof("creating %q configmap", k8sutil.ConfigOverrideName)
		err = populateConfigOverrideConfigMap(c.context, c.namespacedName.Namespace, cluster.ownerRef)
		if err != nil {
			return errors.Wrap(err, "failed to populate config override config map")
		}

		logger.Infof("creating %q secret", config.StoreName)
		err = config.GetStore(c.context, c.namespacedName.Namespace, &cluster.ownerRef).CreateOrUpdate(cluster.Info)
		if err != nil {
			return errors.Wrap(err, "failed to update the global config")
		}
	}

	// The cluster Identity must be established at this point
	if !cluster.Info.IsInitialized(true) {
		return errors.New("the cluster identity was not established")
	}
	logger.Info("external cluster identity established")

	// Create CSI Secrets only if the user has provided the admin key
	if cluster.Info.AdminSecret != mon.AdminSecretName {
		err = csi.CreateCSISecrets(c.context, c.namespacedName.Namespace, &cluster.ownerRef)
		if err != nil {
			return errors.Wrap(err, "failed to create csi kubernetes secrets")
		}
	}

	// Create CSI config map
	err = csi.CreateCsiConfigMap(c.namespacedName.Namespace, c.context.Clientset, &cluster.ownerRef)
	if err != nil {
		return errors.Wrap(err, "failed to create csi config map")
	}

	// Save CSI configmap
	err = csi.SaveClusterConfig(c.context.Clientset, c.namespacedName.Namespace, cluster.Info, c.csiConfigMutex)
	if err != nil {
		return errors.Wrap(err, "failed to update csi cluster config")
	}
	logger.Info("successfully updated csi config map")

	// Create Crash Collector Secret
	// In 14.2.5 the crash daemon will read the client.crash key instead of the admin key
	if !cluster.Spec.CrashCollector.Disable {
		err = crash.CreateCrashCollectorSecret(c.context, c.namespacedName.Namespace, &cluster.ownerRef)
		if err != nil {
			return errors.Wrap(err, "failed to create crash collector kubernetes secret")
		}
	}

	// Everything went well so let's update the CR's status to "connected"
	config.ConditionExport(c.context, c.namespacedName, cephv1.ConditionConnected, v1.ConditionTrue, "ClusterConnected", "Cluster connected successfully")

	// Mark initialization has done
	cluster.initCompleted = true

	return nil
}

func purgeExternalCluster(clientset kubernetes.Interface, namespace string) error {
	// Purge the config maps
	cmsToDelete := []string{
		mon.EndpointConfigMapName,
		k8sutil.ConfigOverrideName,
	}
	for _, cm := range cmsToDelete {
		err := clientset.CoreV1().ConfigMaps(namespace).Delete(cm, &metav1.DeleteOptions{})
		if err != nil && !kerrors.IsNotFound(err) {
			logger.Errorf("failed to delete config map %q. %v", cm, err)
		}
	}

	// Purge the secrets
	secretsToDelete := []string{
		mon.AppName,
		mon.OperatorCreds,
		csi.CsiRBDNodeSecret,
		csi.CsiRBDProvisionerSecret,
		csi.CsiCephFSNodeSecret,
		csi.CsiCephFSProvisionerSecret,
		config.StoreName,
	}
	for _, secret := range secretsToDelete {
		err := clientset.CoreV1().Secrets(namespace).Delete(secret, &metav1.DeleteOptions{})
		if err != nil && !kerrors.IsNotFound(err) {
			logger.Errorf("failed to delete secret %q. %v", secret, err)
		}
	}

	return nil
}

func validateExternalClusterSpec(cluster *cluster) error {
	if cluster.Spec.CephVersion.Image != "" {
		if cluster.Spec.DataDirHostPath == "" {
			return errors.New("dataDirHostPath must be specified")
		}
	}

	return nil
}

// Add validation in the code to fail if the external cluster has no OSDs keep waiting
func populateExternalClusterInfo(context *clusterd.Context, namespace string) *cephconfig.ClusterInfo {
	var clusterInfo *cephconfig.ClusterInfo
	for {
		var err error
		clusterInfo, _, _, err = mon.LoadClusterInfo(context, namespace)
		if err != nil {
			logger.Warningf("waiting for the connection info of the external cluster. retrying in %s.", externalConnectionRetry.String())
			time.Sleep(externalConnectionRetry)
			continue
		} else {
			// If an admin key was provided we don't need to load the other resources
			// Some people might want to give the admin key
			// The necessary users/keys/secrets will be created by Rook
			// This is also done to allow backward compatibility
			if isExternalHealthCheckUserAdmin(clusterInfo.AdminSecret) {
				break
			}
			externalCred, err := mon.ValidateAndLoadExternalClusterSecrets(context, namespace)
			if err != nil {
				logger.Warningf("waiting for the connection info of the external cluster. retrying in %s.", externalConnectionRetry.String())
				logger.Debugf("%v", err)
				time.Sleep(externalConnectionRetry)
				continue
			} else {
				clusterInfo.ExternalCred = externalCred
				logger.Infof("found the cluster info to connect to the external cluster. will use %q to check health and monitor status. mons=%+v", clusterInfo.ExternalCred.Username, clusterInfo.Monitors)
				break
			}
		}
	}

	return clusterInfo
}

func isExternalHealthCheckUserAdmin(adminSecret string) bool {
	return adminSecret != mon.AdminSecretName
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/calavera/dkvolume"

	"github.com/noahdesu/go-ceph/rados"
	"github.com/noahdesu/go-ceph/rbd"
)

type volume struct {
	name        string
	device		string
	connections int
}

type cephDriver struct {
	root       	string
	pool		string
	volumes    	map[string]*volume
	m          	sync.Mutex
}

func newCephDriver(root, pool string) cephDriver {
	d := cephDriver{
		root:   root,
		pool:	pool,
		volumes: map[string]*volume{},
	}
	return d
}

func checkFs(device string) (string, error) {
	log.Printf("blkid %s", device)
 	out, err := exec.Command("blkid", device).Output()
 	if out != nil {
	 	sout := strings.TrimRight(string(out), "\n")
		return sout, err
 	}
 	return "", err
}

func ensureFs(name string) dkvolume.Response {
	log.Printf("ensureFs(%q)", name)
	device, err := rbdMap(name)
	if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}
	defer rbdUnmap(*device)

	t, err := checkFs(*device)
	log.Printf("t => %q", t)
	if !strings.HasSuffix(t, "TYPE=\"xfs\"") {
		log.Printf("Formatting %s", *device)
		out, _, err := sh(fmt.Sprintf("mkfs.xfs -f %s", *device))
		if err != nil {
			return dkvolume.Response{Err: err.Error()}
		}
		log.Printf(string(out))
	}
	return dkvolume.Response{}
}

func rbdExists(name string) (bool, error) {
	conn, err := rados.NewConn()
	if err != nil {
		log.Fatal(err)
		return false, err
	}
	conn.ReadDefaultConfigFile()
	conn.Connect()
	defer conn.Shutdown()

	ioContext, err := conn.OpenIOContext("rbd")
	if err != nil {
		log.Fatal(err)
		return false, err
	}
	defer ioContext.Destroy()

	volumes, err := rbd.GetImageNames(ioContext)
	if err != nil {
		log.Fatal(err)
		return false, err
	}
	for _, volumeName := range volumes {
		if name == volumeName {
			return true, nil
		}
	}
	return false, nil
}

func (d cephDriver) Create(r dkvolume.Request) dkvolume.Response {
	log.Printf("Create(%q)", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	name := r.Name
	mountpoint := d.mountpoint(name)

	if _, ok := d.volumes[mountpoint]; ok {
		return dkvolume.Response{}
	}

	exists, err := rbdExists(name)
	if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	if !exists {
		if out, _, err := sh(fmt.Sprintf("rbd create --size 128 %s", name)); err != nil {
			log.Print(string(out))
			return dkvolume.Response{Err: err.Error()}
		}
	}

	return ensureFs(name)
}

func (d cephDriver) Remove(r dkvolume.Request) dkvolume.Response {
	log.Printf("Remove(%q)", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	name := r.Name
	mountpoint := d.mountpoint(name)

	if volume, ok := d.volumes[mountpoint]; ok {
		if volume.connections <= 1 {
			cmd := fmt.Sprintf("rbd rm %s", name)
			if out, _, err := sh(cmd); err != nil {
				log.Print(string(out))
				return dkvolume.Response{Err: err.Error()}
			}
			delete(d.volumes, mountpoint)
		}
	}
	return dkvolume.Response{}
}

func (d cephDriver) Path(r dkvolume.Request) dkvolume.Response {
	return dkvolume.Response{Mountpoint: d.mountpoint(r.Name)}
}

func (d cephDriver) Mount(r dkvolume.Request) dkvolume.Response {
	log.Printf("Mount(%q)", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	mountpoint := d.mountpoint(r.Name)
	log.Printf("mountpoint(%q) => %q", r.Name, mountpoint)

	vol, ok := d.volumes[mountpoint]
	if ok && vol.connections > 0 {
		vol.connections++
		return dkvolume.Response{Mountpoint: mountpoint}
	}

	fi, err := os.Lstat(mountpoint)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(mountpoint, 0755); err != nil {
			return dkvolume.Response{Err: err.Error()}
		}
	} else if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	if fi != nil && !fi.IsDir() {
		return dkvolume.Response{Err: fmt.Sprintf("%v already exist and it's not a directory", mountpoint)}
	}

	device, err := d.mountVolume(r.Name, mountpoint);
	if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	d.volumes[mountpoint] = &volume{name: r.Name, device: *device, connections: 1}

	return dkvolume.Response{Mountpoint: mountpoint}
}

func (d cephDriver) Unmount(r dkvolume.Request) dkvolume.Response {
	log.Printf("Unmount(%q)\n", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	mountpoint := d.mountpoint(r.Name)
	log.Printf("mountpoint(%q) => %s\n", r.Name, mountpoint)

	if volume, ok := d.volumes[mountpoint]; ok {
		if volume.connections == 1 {
			if err := d.unmountVolume(mountpoint, volume.device); err != nil {
				return dkvolume.Response{Err: err.Error()}
			}
		}
		volume.connections--
	} else {
		return dkvolume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", mountpoint)}
	}

	return dkvolume.Response{}
}

func (d *cephDriver) mountpoint(name string) string {
	return filepath.Join(d.root, name)
}

func execCommand(name string, arg... string) ([]byte, []byte, error) {
 	cmd := exec.Command(name, arg...)
	stdout, err := cmd.StdoutPipe(); if err != nil {
		return nil, nil, err
	}
 	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	sout, err := ioutil.ReadAll(stdout); if err != nil {
		return nil, nil, err
	}
	serr, err := ioutil.ReadAll(stderr); if err != nil {
		return nil, nil, err
	}
	if err := cmd.Wait(); err != nil {
		return nil, nil, err
	}
	return sout, serr, nil
}

func sh(s string) ([]byte, []byte, error) {
	log.Print(s)
	return execCommand("sh", "-c", s)
}

func rbdMap(name string) (*string, error) {
	log.Printf("rbdMap(%q)", name)
	out, _, err := sh(fmt.Sprintf("rbd map %s", name))
	if err != nil {
		log.Print(string(out))
		return nil, err
	}
	out1, _, err := sh(fmt.Sprintf("rbd showmapped |grep %s| awk 'NR==1 {print $5}'", name))
	if err != nil {
		log.Print(string(out1))
		return nil, err
	}

	device := strings.TrimRight(string(out1), "\n")
	log.Printf("rbd map %s => %q", name, device)
	return &device, nil
}

func rbdUnmap(device string) error {
	log.Printf("rbdUnmap(%q)", device)
	if out, _, err := sh(fmt.Sprintf("rbd unmap %s", device)); err != nil {
		log.Print(string(out))
		return err
	}
	return nil
}

func (d *cephDriver) mountVolume(name, target string) (*string, error) {
	log.Printf("mountVolume(%q, %q)", name, target)
	device, err := rbdMap(name)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	log.Printf("rbdMap(%q) => %q", name, *device)
	if out, _, err := sh(fmt.Sprintf("mount %s %s", *device, target)); err != nil {
		log.Print(string(out))
		return nil, err
	}
	return device, nil
}

func (d *cephDriver) unmountVolume(target, device string) error {
	log.Printf("unmountVolume(%q, %q)", target, device)
	cmd := fmt.Sprintf("umount %s", target)
	if out, _, err := sh(cmd); err != nil {
		log.Print(string(out))
		return err
	}
	return rbdUnmap(device)
}
<commit_msg>Update driver.go<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/calavera/dkvolume"

	"github.com/noahdesu/go-ceph/rados"
	"github.com/noahdesu/go-ceph/rbd"
)

type volume struct {
	name        string
	device		string
	connections int
}

type cephDriver struct {
	root       	string
	pool		string
	volumes    	map[string]*volume
	m          	sync.Mutex
}

func newCephDriver(root, pool string) cephDriver {
	d := cephDriver{
		root:   root,
		pool:	pool,
		volumes: map[string]*volume{},
	}
	return d
}

func checkFs(device string) (string, error) {
	log.Printf("blkid %s", device)
 	out, err := exec.Command("blkid", device).Output()
 	if out != nil {
	 	sout := strings.TrimRight(string(out), "\n")
		return sout, err
 	}
 	return "", err
}

func ensureFs(name string) dkvolume.Response {
	log.Printf("ensureFs(%q)", name)
	device, err := rbdMap(name)
	if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}
	defer rbdUnmap(*device)

	t, err := checkFs(*device)
	log.Printf("t => %q", t)
	if !strings.HasSuffix(t, "TYPE=\"xfs\"") {
		log.Printf("Formatting %s", *device)
		out, _, err := sh(fmt.Sprintf("mkfs.xfs -f %s", *device))
		if err != nil {
			return dkvolume.Response{Err: err.Error()}
		}
		log.Printf(string(out))
	}
	return dkvolume.Response{}
}

func rbdExists(name string) (bool, error) {
	conn, err := rados.NewConn()
	if err != nil {
		log.Fatal(err)
		return false, err
	}
	conn.ReadDefaultConfigFile()
	conn.Connect()
	defer conn.Shutdown()

	ioContext, err := conn.OpenIOContext("rbd")
	if err != nil {
		log.Fatal(err)
		return false, err
	}
	defer ioContext.Destroy()

	volumes, err := rbd.GetImageNames(ioContext)
	if err != nil {
		log.Fatal(err)
		return false, err
	}
	for _, volumeName := range volumes {
		if name == volumeName {
			return true, nil
		}
	}
	return false, nil
}

func (d cephDriver) Create(r dkvolume.Request) dkvolume.Response {
	log.Printf("Create(%q)", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	name := r.Name
	mountpoint := d.mountpoint(name)

	if _, ok := d.volumes[mountpoint]; ok {
		return dkvolume.Response{}
	}

	exists, err := rbdExists(name)
	if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	if !exists {
		if out, _, err := sh(fmt.Sprintf("rbd create --size 512 %s", name)); err != nil {
			log.Print(string(out))
			return dkvolume.Response{Err: err.Error()}
		}
	}

	return ensureFs(name)
}

func (d cephDriver) Remove(r dkvolume.Request) dkvolume.Response {
	log.Printf("Remove(%q)", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	name := r.Name
	mountpoint := d.mountpoint(name)

	if volume, ok := d.volumes[mountpoint]; ok {
		if volume.connections <= 1 {
			cmd := fmt.Sprintf("rbd rm %s", name)
			if out, _, err := sh(cmd); err != nil {
				log.Print(string(out))
				return dkvolume.Response{Err: err.Error()}
			}
			delete(d.volumes, mountpoint)
		}
	}
	return dkvolume.Response{}
}

func (d cephDriver) Path(r dkvolume.Request) dkvolume.Response {
	return dkvolume.Response{Mountpoint: d.mountpoint(r.Name)}
}

func (d cephDriver) Mount(r dkvolume.Request) dkvolume.Response {
	log.Printf("Mount(%q)", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	mountpoint := d.mountpoint(r.Name)
	log.Printf("mountpoint(%q) => %q", r.Name, mountpoint)

	vol, ok := d.volumes[mountpoint]
	if ok && vol.connections > 0 {
		vol.connections++
		return dkvolume.Response{Mountpoint: mountpoint}
	}

	fi, err := os.Lstat(mountpoint)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(mountpoint, 0755); err != nil {
			return dkvolume.Response{Err: err.Error()}
		}
	} else if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	if fi != nil && !fi.IsDir() {
		return dkvolume.Response{Err: fmt.Sprintf("%v already exist and it's not a directory", mountpoint)}
	}

	device, err := d.mountVolume(r.Name, mountpoint);
	if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	d.volumes[mountpoint] = &volume{name: r.Name, device: *device, connections: 1}

	return dkvolume.Response{Mountpoint: mountpoint}
}

func (d cephDriver) Unmount(r dkvolume.Request) dkvolume.Response {
	log.Printf("Unmount(%q)\n", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	mountpoint := d.mountpoint(r.Name)
	log.Printf("mountpoint(%q) => %s\n", r.Name, mountpoint)

	if volume, ok := d.volumes[mountpoint]; ok {
		if volume.connections == 1 {
			if err := d.unmountVolume(mountpoint, volume.device); err != nil {
				return dkvolume.Response{Err: err.Error()}
			}
		}
		volume.connections--
	} else {
		return dkvolume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", mountpoint)}
	}

	return dkvolume.Response{}
}

func (d *cephDriver) mountpoint(name string) string {
	return filepath.Join(d.root, name)
}

func execCommand(name string, arg... string) ([]byte, []byte, error) {
 	cmd := exec.Command(name, arg...)
	stdout, err := cmd.StdoutPipe(); if err != nil {
		return nil, nil, err
	}
 	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	sout, err := ioutil.ReadAll(stdout); if err != nil {
		return nil, nil, err
	}
	serr, err := ioutil.ReadAll(stderr); if err != nil {
		return nil, nil, err
	}
	if err := cmd.Wait(); err != nil {
		return nil, nil, err
	}
	return sout, serr, nil
}

func sh(s string) ([]byte, []byte, error) {
	log.Print(s)
	return execCommand("sh", "-c", s)
}

func rbdMap(name string) (*string, error) {
	log.Printf("rbdMap(%q)", name)
	out, _, err := sh(fmt.Sprintf("rbd map %s", name))
	if err != nil {
		log.Print(string(out))
		return nil, err
	}
	out1, _, err := sh(fmt.Sprintf("rbd showmapped |grep %s| awk 'NR==1 {print $5}'", name))
	if err != nil {
		log.Print(string(out1))
		return nil, err
	}

	device := strings.TrimRight(string(out1), "\n")
	log.Printf("rbd map %s => %q", name, device)
	return &device, nil
}

func rbdUnmap(device string) error {
	log.Printf("rbdUnmap(%q)", device)
	if out, _, err := sh(fmt.Sprintf("rbd unmap %s", device)); err != nil {
		log.Print(string(out))
		return err
	}
	return nil
}

func (d *cephDriver) mountVolume(name, target string) (*string, error) {
	log.Printf("mountVolume(%q, %q)", name, target)
	device, err := rbdMap(name)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	log.Printf("rbdMap(%q) => %q", name, *device)
	if out, _, err := sh(fmt.Sprintf("mount %s %s", *device, target)); err != nil {
		log.Print(string(out))
		return nil, err
	}
	return device, nil
}

func (d *cephDriver) unmountVolume(target, device string) error {
	log.Printf("unmountVolume(%q, %q)", target, device)
	cmd := fmt.Sprintf("umount %s", target)
	if out, _, err := sh(cmd); err != nil {
		log.Print(string(out))
		return err
	}
	return rbdUnmap(device)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package featuretests

import (
	"fmt"
	"strings"

	"github.com/juju/cmd"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v5"
	goyaml "gopkg.in/yaml.v1"

	"github.com/juju/juju/cmd/envcmd"
	cmdjuju "github.com/juju/juju/cmd/juju"
	cmdaction "github.com/juju/juju/cmd/juju/action"
	cmdmachine "github.com/juju/juju/cmd/juju/machine"
	cmdservice "github.com/juju/juju/cmd/juju/service"
	"github.com/juju/juju/component/all"
	jjj "github.com/juju/juju/juju"
	"github.com/juju/juju/process"
	"github.com/juju/juju/testcharms"
	coretesting "github.com/juju/juju/testing"
)

var repoDir = testcharms.Repo.Path()

func initProcessesSuites() {
	if err := all.RegisterForServer(); err != nil {
		panic(err)
	}

	gc.Suite(&processesHookContextSuite{})
	gc.Suite(&processesWorkerSuite{})
	gc.Suite(&processesCmdJujuSuite{})
}

type processesBaseSuite struct {
	initialized bool
	env         *procsEnviron
}

func (s *processesBaseSuite) SetUpTest(c *gc.C) {
	if !s.initialized {
		initJuju(c)
		s.env = newProcsEnv(c, "local")

		s.env.bootstrap(c)
		s.initialized = true
	}
}

func (s *processesBaseSuite) TearDownSuite(c *gc.C) {
	if s.initialized {
		s.env.destroy(c)
	}
}

type processesHookContextSuite struct {
	processesBaseSuite
}

func (s *processesHookContextSuite) TestHookLifecycle(c *gc.C) {
	// start: info, launch, info
	// config-changed: info, set-status, info
	// stop: info, destroy, info

	svc := s.env.addService(c, "proc-hooks", "a-service")

	// Add/start the unit.

	unit := svc.deploy(c, "myproc", "xyz123", "running")

	unit.checkState(c, []process.Info{{
		Process: charm.Process{
			Name: "myproc",
			Type: "myplugin",
		},
		Details: process.Details{
			ID: "xyz123",
			Status: process.PluginStatus{
				Label: "running",
			},
		},
	}})
	unit.checkPluginLog(c, []string{
		"...",
	})

	// Change the config.

	unit.setConfigStatus(c, "okay")

	unit.checkState(c, []process.Info{{
		Process: charm.Process{
			Name: "myproc",
			Type: "myplugin",
		},
		Details: process.Details{
			ID: "xyz123",
			Status: process.PluginStatus{
				Label: "okay",
			},
		},
	}})
	unit.checkPluginLog(c, []string{
		"...",
	})

	// Stop the unit.

	unit.destroy(c)

	unit.checkState(c, nil)
	unit.checkPluginLog(c, []string{
		"...",
	})
}

// TODO(ericsnow) Add a test specifically for each supported plugin
// (e.g. docker)?

func (s *processesHookContextSuite) TestRegister(c *gc.C) {
	//s.addUnit(c, "proc-actions", "a-service")
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestLaunch(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestInfo(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestUnregister(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestDestroy(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesWorkerSuite struct {
	processesBaseSuite
}

func (s *processesWorkerSuite) TestSetStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesWorkerSuite) TestCleanUp(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesCmdJujuSuite struct {
	processesBaseSuite
}

func (s *processesCmdJujuSuite) TestStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type procsEnviron struct {
	name    string
	machine string
}

func newProcsEnv(c *gc.C, envName string) *procsEnviron {
	return &procsEnviron{
		name:    envName,
		machine: "1",
	}
}

func (env *procsEnviron) run(c *gc.C, cmd string, args ...string) string {
	args = append([]string{"--environment=" + env.name}, args...)
	c.Logf(" COMMAND: juju %s %s", cmd, strings.Join(args, " "))

	command := lookUpCommand(cmd)
	ctx, err := coretesting.RunCommand(c, command, args...)
	c.Assert(err, jc.ErrorIsNil)

	return strings.TrimSpace(coretesting.Stdout(ctx))
}

func initJuju(c *gc.C) {
	err := jjj.InitJujuHome()
	c.Assert(err, jc.ErrorIsNil)
}

// TODO(ericsnow) Instead, directly access the command registry...
func lookUpCommand(cmd string) cmd.Command {
	switch cmd {
	case "bootstrap":
		return envcmd.Wrap(&cmdjuju.BootstrapCommand{})
	case "destroy-environment":
		return &cmdjuju.DestroyEnvironmentCommand{}
	case "add-machine":
		return envcmd.Wrap(&cmdmachine.AddCommand{})
	case "deploy":
		return envcmd.Wrap(&cmdjuju.DeployCommand{})
	case "service set":
		return envcmd.Wrap(&cmdservice.SetCommand{})
	case "service add-unit":
		return envcmd.Wrap(&cmdservice.AddUnitCommand{})
	case "destroy-unit":
		return envcmd.Wrap(&cmdjuju.RemoveUnitCommand{})
	case "action do":
		return envcmd.Wrap(&cmdaction.DoCommand{})
	case "action fetch":
		return envcmd.Wrap(&cmdaction.FetchCommand{})
	default:
		panic("unknown command: " + cmd)
	}
	return nil
}

func (env *procsEnviron) bootstrap(c *gc.C) {
	env.run(c, "bootstrap")
}

func (env *procsEnviron) addService(c *gc.C, charmName, serviceName string) *procsService {
	if serviceName == "" {
		serviceName = charmName
	}
	charmURL := "local:quantal/" + charmName

	env.run(c, "add-machine", "--series=quantal")
	env.run(c, "deploy", "--to="+env.machine, "--repository="+repoDir, charmURL, serviceName)
	// We leave unit /0 alive to keep the machine alive.

	svc := &procsService{
		env:       env,
		charmName: charmName,
		name:      serviceName,
	}
	svc.dummy = procsUnit{
		svc: svc,
		id:  serviceName + "/0",
	}
	return svc
}

func (env *procsEnviron) destroy(c *gc.C) {
	env.run(c, "destroy-environment", "--force")
}

type procsService struct {
	env       *procsEnviron
	charmName string
	name      string
	dummy     procsUnit
	lastUnit  int
}

func (svc *procsService) block(c *gc.C) {
	svc.dummy.runAction(c, "noop", nil)
}

func (svc *procsService) setConfig(c *gc.C, settings map[string]string) {
	// Try to force at least a little sequentiality on Juju.
	svc.block(c)

	args := []string{svc.name}
	for k, v := range settings {
		args = append(args, fmt.Sprintf("%s=%q", k, v))
	}
	svc.env.run(c, "service set", args...)
}

func (svc *procsService) deploy(c *gc.C, procName, pluginID, status string) *procsUnit {
	settings := map[string]string{
		"plugin-name":   procName,
		"plugin-id":     pluginID,
		"plugin-status": status,
	}
	svc.setConfig(c, settings)

	svc.env.run(c, "service add-unit", "--to="+svc.env.machine, svc.name)

	// TODO(ericsnow) wait until ready...

	svc.lastUnit += 1
	return &procsUnit{
		svc: svc,
		id:  fmt.Sprintf("%s/%d", svc.name, svc.lastUnit),
	}
}

type procsUnit struct {
	svc *procsService
	id  string
}

func (u *procsUnit) setConfig(c *gc.C, settings map[string]string) {
	u.svc.setConfig(c, settings)
}

func (u *procsUnit) setConfigStatus(c *gc.C, status string) {
	settings := map[string]string{"plugin-status": status}
	u.setConfig(c, settings)
}

func (u *procsUnit) destroy(c *gc.C) {
	u.svc.env.run(c, "destroy-unit", u.id)

	// TODO(ericsnow) wait until ready...
}

func (u *procsUnit) runAction(c *gc.C, action string, actionArgs map[string]interface{}) map[string]string {
	// Send the command.
	args := []string{
		u.id,
		action,
	}
	for k, v := range actionArgs {
		args = append(args, fmt.Sprintf("%s=%q", k, v))
	}
	doOut := u.svc.env.run(c, "action do", args...)
	c.Assert(strings.Split(doOut, ": "), gc.HasLen, 2)
	actionID := strings.Split(doOut, ": ")[1]

	// Get the results.
	fetchOut := u.svc.env.run(c, "action fetch", "--wait=0", actionID)
	result := struct {
		Status  string
		Results map[string]interface{}
	}{}
	err := goyaml.Unmarshal([]byte(fetchOut), &result)
	c.Assert(err, jc.ErrorIsNil)

	// Check and coerce the results.
	if !c.Check(result.Status, gc.Equals, "completed") {
		c.Logf(" got:\n" + fetchOut)
	}
	results := make(map[string]string, len(result.Results))
	for k, v := range result.Results {
		results[k] = v.(string)
	}
	return results
}

func (u *procsUnit) injectStatus(c *gc.C, pluginID, status string) {
	args := map[string]interface{}{
		"id":     pluginID,
		"status": status,
	}
	u.runAction(c, "plugin-setstatus", args)
}

func (u *procsUnit) prepPlugin(c *gc.C, procName, pluginID, status string) {
	args := map[string]interface{}{
		"name":   procName,
		"id":     pluginID,
		"status": status,
	}
	u.runAction(c, "plugin-prep", args)
}

func (u *procsUnit) checkState(c *gc.C, expected []process.Info) {
	var procs []process.Info

	results := u.runAction(c, "list", nil)
	out := strings.TrimSpace(results["out"])
	if out != " [no processes registered]" {
		for _, section := range strings.Split(out, "\n\n") {
			var proc process.Info
			err := goyaml.Unmarshal([]byte(section), &proc)
			c.Assert(err, jc.ErrorIsNil)
			procs = append(procs, proc)
		}
	}

	c.Check(procs, jc.DeepEquals, expected)
}

func (u *procsUnit) checkPluginLog(c *gc.C, expected []string) {
	results := u.runAction(c, "plugin-dump", nil)
	c.Assert(results, gc.HasLen, 1)
	output, ok := results["output"]
	c.Assert(ok, jc.IsTrue)
	lines := strings.Split(output, "\n")
	// TODO(ericsnow) strip header...

	c.Check(lines, jc.DeepEquals, expected)
}
<commit_msg>Properly quote config options and action args.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package featuretests

import (
	"fmt"
	"strings"

	"github.com/juju/cmd"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v5"
	goyaml "gopkg.in/yaml.v1"

	"github.com/juju/juju/cmd/envcmd"
	cmdjuju "github.com/juju/juju/cmd/juju"
	cmdaction "github.com/juju/juju/cmd/juju/action"
	cmdmachine "github.com/juju/juju/cmd/juju/machine"
	cmdservice "github.com/juju/juju/cmd/juju/service"
	"github.com/juju/juju/component/all"
	jjj "github.com/juju/juju/juju"
	"github.com/juju/juju/process"
	"github.com/juju/juju/testcharms"
	coretesting "github.com/juju/juju/testing"
)

var repoDir = testcharms.Repo.Path()

func initProcessesSuites() {
	if err := all.RegisterForServer(); err != nil {
		panic(err)
	}

	gc.Suite(&processesHookContextSuite{})
	gc.Suite(&processesWorkerSuite{})
	gc.Suite(&processesCmdJujuSuite{})
}

type processesBaseSuite struct {
	initialized bool
	env         *procsEnviron
}

func (s *processesBaseSuite) SetUpTest(c *gc.C) {
	if !s.initialized {
		initJuju(c)
		s.env = newProcsEnv(c, "local")

		s.env.bootstrap(c)
		s.initialized = true
	}
}

func (s *processesBaseSuite) TearDownSuite(c *gc.C) {
	if s.initialized {
		s.env.destroy(c)
	}
}

type processesHookContextSuite struct {
	processesBaseSuite
}

func (s *processesHookContextSuite) TestHookLifecycle(c *gc.C) {
	// start: info, launch, info
	// config-changed: info, set-status, info
	// stop: info, destroy, info

	svc := s.env.addService(c, "proc-hooks", "a-service")

	// Add/start the unit.

	unit := svc.deploy(c, "myproc", "xyz123", "running")

	unit.checkState(c, []process.Info{{
		Process: charm.Process{
			Name: "myproc",
			Type: "myplugin",
		},
		Details: process.Details{
			ID: "xyz123",
			Status: process.PluginStatus{
				Label: "running",
			},
		},
	}})
	unit.checkPluginLog(c, []string{
		"...",
	})

	// Change the config.

	unit.setConfigStatus(c, "okay")

	unit.checkState(c, []process.Info{{
		Process: charm.Process{
			Name: "myproc",
			Type: "myplugin",
		},
		Details: process.Details{
			ID: "xyz123",
			Status: process.PluginStatus{
				Label: "okay",
			},
		},
	}})
	unit.checkPluginLog(c, []string{
		"...",
	})

	// Stop the unit.

	unit.destroy(c)

	unit.checkState(c, nil)
	unit.checkPluginLog(c, []string{
		"...",
	})
}

// TODO(ericsnow) Add a test specifically for each supported plugin
// (e.g. docker)?

func (s *processesHookContextSuite) TestRegister(c *gc.C) {
	//s.addUnit(c, "proc-actions", "a-service")
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestLaunch(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestInfo(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestUnregister(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesHookContextSuite) TestDestroy(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesWorkerSuite struct {
	processesBaseSuite
}

func (s *processesWorkerSuite) TestSetStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

func (s *processesWorkerSuite) TestCleanUp(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type processesCmdJujuSuite struct {
	processesBaseSuite
}

func (s *processesCmdJujuSuite) TestStatus(c *gc.C) {
	// TODO(ericsnow) Finish!
	c.Skip("not finished")
}

type procsEnviron struct {
	name    string
	machine string
}

func newProcsEnv(c *gc.C, envName string) *procsEnviron {
	return &procsEnviron{
		name:    envName,
		machine: "1",
	}
}

func (env *procsEnviron) run(c *gc.C, cmd string, args ...string) string {
	args = append([]string{"--environment=" + env.name}, args...)
	c.Logf(" COMMAND: juju %s %s", cmd, strings.Join(args, " "))

	command := lookUpCommand(cmd)
	ctx, err := coretesting.RunCommand(c, command, args...)
	c.Assert(err, jc.ErrorIsNil)

	return strings.TrimSpace(coretesting.Stdout(ctx))
}

func initJuju(c *gc.C) {
	err := jjj.InitJujuHome()
	c.Assert(err, jc.ErrorIsNil)
}

// TODO(ericsnow) Instead, directly access the command registry...
func lookUpCommand(cmd string) cmd.Command {
	switch cmd {
	case "bootstrap":
		return envcmd.Wrap(&cmdjuju.BootstrapCommand{})
	case "destroy-environment":
		return &cmdjuju.DestroyEnvironmentCommand{}
	case "add-machine":
		return envcmd.Wrap(&cmdmachine.AddCommand{})
	case "deploy":
		return envcmd.Wrap(&cmdjuju.DeployCommand{})
	case "service set":
		return envcmd.Wrap(&cmdservice.SetCommand{})
	case "service add-unit":
		return envcmd.Wrap(&cmdservice.AddUnitCommand{})
	case "destroy-unit":
		return envcmd.Wrap(&cmdjuju.RemoveUnitCommand{})
	case "action do":
		return envcmd.Wrap(&cmdaction.DoCommand{})
	case "action fetch":
		return envcmd.Wrap(&cmdaction.FetchCommand{})
	default:
		panic("unknown command: " + cmd)
	}
	return nil
}

func (env *procsEnviron) bootstrap(c *gc.C) {
	env.run(c, "bootstrap")
}

func (env *procsEnviron) addService(c *gc.C, charmName, serviceName string) *procsService {
	if serviceName == "" {
		serviceName = charmName
	}
	charmURL := "local:quantal/" + charmName

	env.run(c, "add-machine", "--series=quantal")
	env.run(c, "deploy", "--to="+env.machine, "--repository="+repoDir, charmURL, serviceName)
	// We leave unit /0 alive to keep the machine alive.

	svc := &procsService{
		env:       env,
		charmName: charmName,
		name:      serviceName,
	}
	svc.dummy = procsUnit{
		svc: svc,
		id:  serviceName + "/0",
	}
	return svc
}

func (env *procsEnviron) destroy(c *gc.C) {
	env.run(c, "destroy-environment", "--force")
}

type procsService struct {
	env       *procsEnviron
	charmName string
	name      string
	dummy     procsUnit
	lastUnit  int
}

func (svc *procsService) block(c *gc.C) {
	svc.dummy.runAction(c, "noop", nil)
}

func (svc *procsService) setConfig(c *gc.C, settings map[string]string) {
	// Try to force at least a little sequentiality on Juju.
	svc.block(c)

	args := []string{svc.name}
	for k, v := range settings {
		args = append(args, fmt.Sprintf("'%s=%s'", k, v))
	}
	svc.env.run(c, "service set", args...)
}

func (svc *procsService) deploy(c *gc.C, procName, pluginID, status string) *procsUnit {
	settings := map[string]string{
		"plugin-name":   procName,
		"plugin-id":     pluginID,
		"plugin-status": status,
	}
	svc.setConfig(c, settings)

	svc.env.run(c, "service add-unit", "--to="+svc.env.machine, svc.name)

	// TODO(ericsnow) wait until ready...

	svc.lastUnit += 1
	return &procsUnit{
		svc: svc,
		id:  fmt.Sprintf("%s/%d", svc.name, svc.lastUnit),
	}
}

type procsUnit struct {
	svc *procsService
	id  string
}

func (u *procsUnit) setConfig(c *gc.C, settings map[string]string) {
	u.svc.setConfig(c, settings)
}

func (u *procsUnit) setConfigStatus(c *gc.C, status string) {
	settings := map[string]string{"plugin-status": status}
	u.setConfig(c, settings)
}

func (u *procsUnit) destroy(c *gc.C) {
	u.svc.env.run(c, "destroy-unit", u.id)

	// TODO(ericsnow) wait until ready...
}

func (u *procsUnit) runAction(c *gc.C, action string, actionArgs map[string]interface{}) map[string]string {
	// Send the command.
	args := []string{
		u.id,
		action,
	}
	for k, v := range actionArgs {
		args = append(args, fmt.Sprintf("'%s=%s'", k, v))
	}
	doOut := u.svc.env.run(c, "action do", args...)
	c.Assert(strings.Split(doOut, ": "), gc.HasLen, 2)
	actionID := strings.Split(doOut, ": ")[1]

	// Get the results.
	fetchOut := u.svc.env.run(c, "action fetch", "--wait=0", actionID)
	result := struct {
		Status  string
		Results map[string]interface{}
	}{}
	err := goyaml.Unmarshal([]byte(fetchOut), &result)
	c.Assert(err, jc.ErrorIsNil)

	// Check and coerce the results.
	if !c.Check(result.Status, gc.Equals, "completed") {
		c.Logf(" got:\n" + fetchOut)
	}
	results := make(map[string]string, len(result.Results))
	for k, v := range result.Results {
		results[k] = v.(string)
	}
	return results
}

func (u *procsUnit) injectStatus(c *gc.C, pluginID, status string) {
	args := map[string]interface{}{
		"id":     pluginID,
		"status": status,
	}
	u.runAction(c, "plugin-setstatus", args)
}

func (u *procsUnit) prepPlugin(c *gc.C, procName, pluginID, status string) {
	args := map[string]interface{}{
		"name":   procName,
		"id":     pluginID,
		"status": status,
	}
	u.runAction(c, "plugin-prep", args)
}

func (u *procsUnit) checkState(c *gc.C, expected []process.Info) {
	var procs []process.Info

	results := u.runAction(c, "list", nil)
	out := strings.TrimSpace(results["out"])
	if out != " [no processes registered]" {
		for _, section := range strings.Split(out, "\n\n") {
			var proc process.Info
			err := goyaml.Unmarshal([]byte(section), &proc)
			c.Assert(err, jc.ErrorIsNil)
			procs = append(procs, proc)
		}
	}

	c.Check(procs, jc.DeepEquals, expected)
}

func (u *procsUnit) checkPluginLog(c *gc.C, expected []string) {
	results := u.runAction(c, "plugin-dump", nil)
	c.Assert(results, gc.HasLen, 1)
	output, ok := results["output"]
	c.Assert(ok, jc.IsTrue)
	lines := strings.Split(output, "\n")
	// TODO(ericsnow) strip header...

	c.Check(lines, jc.DeepEquals, expected)
}
<|endoftext|>


<commit_before>//package Policy provides RBAC policy enforcement similar to the OpenStack oslo.policy library.
package policy

import "fmt"

//go:generate go tool yacc -v "" -o parser.go parser.y
//go:generate sed -i -e "s/yyEofCode/yyEOFCode/" parser.go
//go:generate sed -i -e 1d parser.go

type rule func(c Context) bool

//Check is the interface for checks
type Check func(c Context, key, match string) bool

//Enforcer is responsible for loading and enforcing rules.
type Enforcer struct {
	rules  map[string]rule
	checks map[string]Check
}

//Enforce checks authorization of a rule for the given Context
func (p *Enforcer) Enforce(rule string, c Context) bool {
	c.rules = &p.rules
	c.checks = p.checks
	r, ok := p.rules[rule]
	return ok && r(c)
}

//AddCheck registers a custom check for the given name.
//A custom check can by used by specifing the name as the left side of the check.
//E.g. mycheck:valueformycheck
func (p *Enforcer) AddCheck(name string, c Check) {
	p.checks[name] = c
}

//NewEnforcer parses the provided rule set and returns a policy enforcer
//By default the Enforcer registers the following checks
// "rule": RuleCheck
// "role": RoleCheck
// "http": HttpCheck
// "default": DefaultCheck
func NewEnforcer(rules map[string]string) (*Enforcer, error) {
	p := Enforcer{
		rules:  make(map[string]rule, len(rules)),
		checks: make(map[string]Check, 4),
	}
	p.AddCheck("rule", RuleCheck)
	p.AddCheck("role", RoleCheck)
	p.AddCheck("http", HTTPCheck)
	p.AddCheck("default", DefaultCheck)

	for name, str := range rules {
		lexer := newLexer(str)
		if yyParse(lexer) != 0 {
			return nil, fmt.Errorf("Failed to parse rule %s: %s", name, lexer.parseResult.(string))
		}
		p.rules[name] = lexer.parseResult.(rule)
	}
	return &p, nil
}

//Context encapsulates the external data required for enforcing a rules. Populating a Context object is left to the application using the policy engine.
type Context struct {
	//Authentication context information from the keystone token, e.g. user_id, user_domain_id...
	Auth map[string]string
	//Roles assigned to the user for the current scope
	Roles []string
	//Request variables that are referenced in policy rules
	Request map[string]string

	rules  *map[string]rule
	checks map[string]Check
}

//RuleCheck provides the standard rule:... check
func RuleCheck(c Context, key, match string) bool {
	rule, ok := (*c.rules)[match]
	if !ok {
		return false
	}
	return rule(c)
}

//RoleCheck provides the standard role:... check.
func RoleCheck(c Context, key, match string) bool {
	for _, r := range c.Roles {
		if r == match {
			return true
		}
	}
	return false
}

//HTTPCheck implements the http:... check
func HTTPCheck(c Context, key, match string) bool {
	return false // not implemented yet
}

//DefaultCheck is used whenever there is no specific check registered for the left hand side.
//It simply tries to match the right side if the check to the authentication credential given by
//the left side. E.g. user_id:%(target.user_id)
func DefaultCheck(c Context, key, match string) bool {
	cred, ok := c.Auth[key]
	if !ok {
		return false
	}
	return cred == match
}

func (c Context) genericCheck(key, match string, isVariable bool) bool {
	if isVariable {
		m, ok := c.Request[match]
		if !ok {
			return false
		}
		match = m
	}

	if check, ok := c.checks[key]; ok {
		return check(c, key, match)
	}
	return c.checks["default"](c, key, match)

}

func (c Context) checkVariable(variable string, match interface{}) bool {

	val, ok := c.Request[variable]
	if !ok {
		return false
	}
	return val == match
}
<commit_msg>Add debug flag to Context<commit_after>//package Policy provides RBAC policy enforcement similar to the OpenStack oslo.policy library.
package policy

import (
	"fmt"
	"log"
)

//go:generate go tool yacc -v "" -o parser.go parser.y
//go:generate sed -i -e "s/yyEofCode/yyEOFCode/" parser.go
//go:generate sed -i -e 1d parser.go

type rule func(c Context) bool

//Check is the interface for checks
type Check func(c Context, key, match string) bool

//Enforcer is responsible for loading and enforcing rules.
type Enforcer struct {
	rules  map[string]rule
	checks map[string]Check
}

//Enforce checks authorization of a rule for the given Context
func (p *Enforcer) Enforce(rule string, c Context) bool {
	c.rules = &p.rules
	c.checks = p.checks
	r, ok := p.rules[rule]
	return ok && r(c)
}

//AddCheck registers a custom check for the given name.
//A custom check can by used by specifing the name as the left side of the check.
//E.g. mycheck:valueformycheck
func (p *Enforcer) AddCheck(name string, c Check) {
	p.checks[name] = c
}

//NewEnforcer parses the provided rule set and returns a policy enforcer
//By default the Enforcer registers the following checks
// "rule": RuleCheck
// "role": RoleCheck
// "http": HttpCheck
// "default": DefaultCheck
func NewEnforcer(rules map[string]string) (*Enforcer, error) {
	p := Enforcer{
		rules:  make(map[string]rule, len(rules)),
		checks: make(map[string]Check, 4),
	}
	p.AddCheck("rule", RuleCheck)
	p.AddCheck("role", RoleCheck)
	p.AddCheck("http", HTTPCheck)
	p.AddCheck("default", DefaultCheck)

	for name, str := range rules {
		lexer := newLexer(str)
		if yyParse(lexer) != 0 {
			return nil, fmt.Errorf("Failed to parse rule %s: %s", name, lexer.parseResult.(string))
		}
		p.rules[name] = lexer.parseResult.(rule)
	}
	return &p, nil
}

//Context encapsulates the external data required for enforcing a rules. Populating a Context object is left to the application using the policy engine.
type Context struct {
	//Authentication context information from the keystone token, e.g. user_id, user_domain_id...
	Auth map[string]string
	//Roles assigned to the user for the current scope
	Roles []string
	//Request variables that are referenced in policy rules
	Request map[string]string

	rules  *map[string]rule
	checks map[string]Check
	debug  bool
}

//RuleCheck provides the standard rule:... check
func RuleCheck(c Context, key, match string) bool {
	rule, ok := (*c.rules)[match]
	if !ok {
		return false
	}
	return rule(c)
}

//RoleCheck provides the standard role:... check.
func RoleCheck(c Context, key, match string) bool {
	for _, r := range c.Roles {
		if r == match {
			return true
		}
	}
	return false
}

//HTTPCheck implements the http:... check
func HTTPCheck(c Context, key, match string) bool {
	return false // not implemented yet
}

//DefaultCheck is used whenever there is no specific check registered for the left hand side.
//It simply tries to match the right side if the check to the authentication credential given by
//the left side. E.g. user_id:%(target.user_id)
func DefaultCheck(c Context, key, match string) bool {
	cred, ok := c.Auth[key]
	if !ok {
		return false
	}
	return cred == match
}

func (c Context) genericCheck(key, match string, isVariable bool) bool {
	if c.debug {
		log.Printf("executing %s:%s", key, match)
	}
	if isVariable {
		m, ok := c.Request[match]
		if !ok {
			if c.debug {
				log.Printf("request variable %s not present in context. failing\n", match)
			}
			return false
		}
		match = m
	}

	if check, ok := c.checks[key]; ok {
		result := check(c, key, match)
		if c.debug {
			log.Printf("check %s returned: %v\n", key, result)
		}
		return result
	}
	result := c.checks["default"](c, key, match)
	if c.debug {
		log.Printf("default check result: %s:%s = %v\n", key, match, result)
	}
	return result

}

func (c Context) checkVariable(variable string, match interface{}) bool {
	if c.debug {
		log.Printf("executing '%s':%s\n", match, variable)
	}
	val, ok := c.Request[variable]
	if !ok {
		if c.debug {
			log.Printf("variable %s not present in context. failing\n", variable)
		}
		return false
	}
	if c.debug {
		log.Printf("'%s':%s = %v\n", match, val, val == match)
	}
	return val == match
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 config

import (
	"strings"
	"testing"

	"gopkg.in/yaml.v2"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestReadConfig(t *testing.T) {
	baseConfig := &GlobalConfig{
		Global: &ContextConfig{
			DefaultRepo: "test-repository",
		},
		ContextConfigs: []*ContextConfig{
			{
				Kubecontext:        "test-context",
				InsecureRegistries: []string{"bad.io", "worse.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "context-local-repository",
			},
		},
	}

	tests := []struct {
		description string
		filename    string
		expectedCfg *GlobalConfig
		content     *GlobalConfig
	}{
		{
			description: "first read",
			filename:    "config",
			content:     baseConfig,
			expectedCfg: baseConfig,
		},
		{
			description: "second run uses cached result",
			expectedCfg: baseConfig,
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			tmpDir := t.NewTempDir().
				Chdir()

			if test.content != nil {
				c, _ := yaml.Marshal(*test.content)
				tmpDir.Write(test.filename, string(c))
			}

			cfg, err := ReadConfigFile(test.filename)

			t.CheckErrorAndDeepEqual(false, err, test.expectedCfg, cfg)
		})
	}
}

func TestResolveConfigFile(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		actual, err := ResolveConfigFile("")
		t.CheckNoError(err)
		const suffix = ".skaffold/config"
		if !strings.HasSuffix(actual, suffix) {
			t.Errorf("expecting %q to have suffix %q", actual, suffix)
		}
	})

	testutil.Run(t, "", func(t *testutil.T) {
		cfg := t.TempFile("givenConfigurationFile", nil)
		actual, err := ResolveConfigFile(cfg)
		t.CheckErrorAndDeepEqual(false, err, cfg, actual)
	})
}

func Test_getConfigForKubeContextWithGlobalDefaults(t *testing.T) {
	const someKubeContext = "this_is_a_context"
	sampleConfig1 := &ContextConfig{
		Kubecontext:        someKubeContext,
		InsecureRegistries: []string{"bad.io", "worse.io"},
		LocalCluster:       util.BoolPtr(true),
		DefaultRepo:        "my-private-registry",
	}
	sampleConfig2 := &ContextConfig{
		Kubecontext:        "another_context",
		InsecureRegistries: []string{"good.io", "better.io"},
		LocalCluster:       util.BoolPtr(false),
		DefaultRepo:        "my-public-registry",
	}

	tests := []struct {
		name           string
		kubecontext    string
		cfg            *GlobalConfig
		expectedConfig *ContextConfig
	}{
		{
			name: "global config when kubecontext is empty",
			cfg: &GlobalConfig{
				Global: &ContextConfig{
					InsecureRegistries: []string{"mediocre.io"},
					LocalCluster:       util.BoolPtr(true),
					DefaultRepo:        "my-private-registry",
				},
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
						DefaultRepo: "value",
					},
				},
			},
			expectedConfig: &ContextConfig{
				InsecureRegistries: []string{"mediocre.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "my-private-registry",
			},
		},
		{
			name:           "no global config and no kubecontext",
			cfg:            &GlobalConfig{},
			expectedConfig: &ContextConfig{},
		},
		{
			name:        "config for unknown kubecontext",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{},
			expectedConfig: &ContextConfig{
				Kubecontext: someKubeContext,
			},
		},
		{
			name:        "config for kubecontext when globals are empty",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				ContextConfigs: []*ContextConfig{sampleConfig2, sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext without merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         sampleConfig2,
				ContextConfigs: []*ContextConfig{sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global: sampleConfig2,
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
					},
				},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		{
			name:        "config for unknown kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{Global: sampleConfig2},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		/* todo(corneliusweig): this behavior can be enabled with `mergo.WithAppendSlice` -> clarify requirements
		{
			name:        "merge global and context-specific insecure-registries",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         &ContextConfig{
					InsecureRegistries: []string{"good.io", "better.io"},
				},
				ContextConfigs: []*ContextConfig{{
					Kubecontext: someKubeContext,
					InsecureRegistries: []string{"bad.io", "worse.io"},
				}},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"bad.io", "worse.io", "good.io", "better.io"},
			},
		},*/
	}
	for _, test := range tests {
		testutil.Run(t, test.name, func(t *testutil.T) {
			actual, err := getConfigForKubeContextWithGlobalDefaults(test.cfg, test.kubecontext)
			t.CheckErrorAndDeepEqual(false, err, test.expectedConfig, actual)
		})
	}
}
<commit_msg>Fix windows filepath<commit_after>/*
Copyright 2019 The Skaffold 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 config

import (
	"path/filepath"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestReadConfig(t *testing.T) {
	baseConfig := &GlobalConfig{
		Global: &ContextConfig{
			DefaultRepo: "test-repository",
		},
		ContextConfigs: []*ContextConfig{
			{
				Kubecontext:        "test-context",
				InsecureRegistries: []string{"bad.io", "worse.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "context-local-repository",
			},
		},
	}

	tests := []struct {
		description string
		filename    string
		expectedCfg *GlobalConfig
		content     *GlobalConfig
	}{
		{
			description: "first read",
			filename:    "config",
			content:     baseConfig,
			expectedCfg: baseConfig,
		},
		{
			description: "second run uses cached result",
			expectedCfg: baseConfig,
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			tmpDir := t.NewTempDir().
				Chdir()

			if test.content != nil {
				c, _ := yaml.Marshal(*test.content)
				tmpDir.Write(test.filename, string(c))
			}

			cfg, err := ReadConfigFile(test.filename)

			t.CheckErrorAndDeepEqual(false, err, test.expectedCfg, cfg)
		})
	}
}

func TestResolveConfigFile(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		actual, err := ResolveConfigFile("")
		t.CheckNoError(err)
		suffix := filepath.FromSlash(".skaffold/config")
		if !strings.HasSuffix(actual, suffix) {
			t.Errorf("expecting %q to have suffix %q", actual, suffix)
		}
	})

	testutil.Run(t, "", func(t *testutil.T) {
		cfg := t.TempFile("givenConfigurationFile", nil)
		actual, err := ResolveConfigFile(cfg)
		t.CheckErrorAndDeepEqual(false, err, cfg, actual)
	})
}

func Test_getConfigForKubeContextWithGlobalDefaults(t *testing.T) {
	const someKubeContext = "this_is_a_context"
	sampleConfig1 := &ContextConfig{
		Kubecontext:        someKubeContext,
		InsecureRegistries: []string{"bad.io", "worse.io"},
		LocalCluster:       util.BoolPtr(true),
		DefaultRepo:        "my-private-registry",
	}
	sampleConfig2 := &ContextConfig{
		Kubecontext:        "another_context",
		InsecureRegistries: []string{"good.io", "better.io"},
		LocalCluster:       util.BoolPtr(false),
		DefaultRepo:        "my-public-registry",
	}

	tests := []struct {
		name           string
		kubecontext    string
		cfg            *GlobalConfig
		expectedConfig *ContextConfig
	}{
		{
			name: "global config when kubecontext is empty",
			cfg: &GlobalConfig{
				Global: &ContextConfig{
					InsecureRegistries: []string{"mediocre.io"},
					LocalCluster:       util.BoolPtr(true),
					DefaultRepo:        "my-private-registry",
				},
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
						DefaultRepo: "value",
					},
				},
			},
			expectedConfig: &ContextConfig{
				InsecureRegistries: []string{"mediocre.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "my-private-registry",
			},
		},
		{
			name:           "no global config and no kubecontext",
			cfg:            &GlobalConfig{},
			expectedConfig: &ContextConfig{},
		},
		{
			name:        "config for unknown kubecontext",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{},
			expectedConfig: &ContextConfig{
				Kubecontext: someKubeContext,
			},
		},
		{
			name:        "config for kubecontext when globals are empty",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				ContextConfigs: []*ContextConfig{sampleConfig2, sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext without merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         sampleConfig2,
				ContextConfigs: []*ContextConfig{sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global: sampleConfig2,
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
					},
				},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		{
			name:        "config for unknown kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{Global: sampleConfig2},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		/* todo(corneliusweig): this behavior can be enabled with `mergo.WithAppendSlice` -> clarify requirements
		{
			name:        "merge global and context-specific insecure-registries",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         &ContextConfig{
					InsecureRegistries: []string{"good.io", "better.io"},
				},
				ContextConfigs: []*ContextConfig{{
					Kubecontext: someKubeContext,
					InsecureRegistries: []string{"bad.io", "worse.io"},
				}},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"bad.io", "worse.io", "good.io", "better.io"},
			},
		},*/
	}
	for _, test := range tests {
		testutil.Run(t, test.name, func(t *testutil.T) {
			actual, err := getConfigForKubeContextWithGlobalDefaults(test.cfg, test.kubecontext)
			t.CheckErrorAndDeepEqual(false, err, test.expectedConfig, actual)
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 sources

import (
	"context"
	"fmt"
	"io"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubectl"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
)

// BuildContextSource is the generic type for the different build context sources the kaniko builder can use
type BuildContextSource interface {
	Setup(ctx context.Context, out io.Writer, artifact *latest.Artifact, initialTag string, dependencies []string) (string, error)
	Pod(args []string) *v1.Pod
	ModifyPod(ctx context.Context, p *v1.Pod) error
	Cleanup(ctx context.Context) error
}

// Retrieve returns the correct build context based on the config
func Retrieve(cli *kubectl.CLI, clusterDetails *latest.ClusterDetails, artifact *latest.KanikoArtifact) BuildContextSource {
	if artifact.BuildContext.LocalDir != nil {
		return &LocalDir{
			clusterDetails: clusterDetails,
			artifact:       artifact,
			kubectl:        cli,
		}
	}

	return &GCSBucket{
		clusterDetails: clusterDetails,
		artifact:       artifact,
	}
}

func podTemplate(clusterDetails *latest.ClusterDetails, artifact *latest.KanikoArtifact, args []string, version string) *v1.Pod {
	userAgent := fmt.Sprintf("UpstreamClient(skaffold-%s)", version)

	env := []v1.EnvVar{{
		Name:  "GOOGLE_APPLICATION_CREDENTIALS",
		Value: "/secret/kaniko-secret",
	}, {
		// This should be same https://github.com/GoogleContainerTools/kaniko/blob/77cfb912f3483c204bfd09e1ada44fd200b15a78/pkg/executor/push.go#L49
		Name:  "UPSTREAM_CLIENT_TYPE",
		Value: userAgent,
	}}

	env = setProxy(clusterDetails, env)

	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "kaniko-",
			Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
			Namespace:    clusterDetails.Namespace,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:            constants.DefaultKanikoContainerName,
					Image:           artifact.Image,
					Args:            args,
					ImagePullPolicy: v1.PullIfNotPresent,
					Env:             env,
					Resources:       resourceRequirements(clusterDetails.Resources),
				},
			},
			RestartPolicy: v1.RestartPolicyNever,
		},
	}
	if clusterDetails.PullSecretName != "" {
		volumeMount := v1.VolumeMount{
			Name:      constants.DefaultKanikoSecretName,
			MountPath: "/secret",
		}
		pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount)
		v := v1.Volume{
			Name: constants.DefaultKanikoSecretName,
			VolumeSource: v1.VolumeSource{
				Secret: &v1.SecretVolumeSource{
					SecretName: clusterDetails.PullSecretName,
				},
			},
		}

		pod.Spec.Volumes = append(pod.Spec.Volumes, v)
	}
	if artifact.Cache != nil && artifact.Cache.HostPath != "" {
		volumeMount := v1.VolumeMount{
			Name:      constants.DefaultKanikoCacheDirName,
			MountPath: constants.DefaultKanikoCacheDirMountPath,
		}

		pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount)

		volume := v1.Volume{
			Name: constants.DefaultKanikoCacheDirName,
			VolumeSource: v1.VolumeSource{
				HostPath: &v1.HostPathVolumeSource{
					Path: artifact.Cache.HostPath,
				},
			},
		}
		pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
	}

	if clusterDetails.DockerConfig == nil {
		return pod
	}

	volumeMount := v1.VolumeMount{
		Name:      constants.DefaultKanikoDockerConfigSecretName,
		MountPath: constants.DefaultKanikoDockerConfigPath,
	}

	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, volumeMount)

	volume := v1.Volume{
		Name: constants.DefaultKanikoDockerConfigSecretName,
		VolumeSource: v1.VolumeSource{
			Secret: &v1.SecretVolumeSource{
				SecretName: clusterDetails.DockerConfig.SecretName,
			},
		},
	}

	pod.Spec.Volumes = append(pod.Spec.Volumes, volume)

	return pod
}

func setProxy(clusterDetails *latest.ClusterDetails, env []v1.EnvVar) []v1.EnvVar {
	if clusterDetails.HTTPProxy != "" {
		proxy := v1.EnvVar{
			Name:  "HTTP_PROXY",
			Value: clusterDetails.HTTPProxy,
		}
		env = append(env, proxy)
	}

	if clusterDetails.HTTPSProxy != "" {
		proxy := v1.EnvVar{
			Name:  "HTTPS_PROXY",
			Value: clusterDetails.HTTPSProxy,
		}
		env = append(env, proxy)
	}
	return env
}

func resourceRequirements(rr *latest.ResourceRequirements) v1.ResourceRequirements {
	req := v1.ResourceRequirements{}

	if rr != nil {
		if rr.Limits != nil {
			req.Limits = v1.ResourceList{}
			if rr.Limits.CPU != "" {
				req.Limits[v1.ResourceCPU] = resource.MustParse(rr.Limits.CPU)
			}

			if rr.Limits.Memory != "" {
				req.Limits[v1.ResourceMemory] = resource.MustParse(rr.Limits.Memory)
			}
		}

		if rr.Requests != nil {
			req.Requests = v1.ResourceList{}
			if rr.Requests.CPU != "" {
				req.Requests[v1.ResourceCPU] = resource.MustParse(rr.Requests.CPU)
			}
			if rr.Requests.Memory != "" {
				req.Requests[v1.ResourceMemory] = resource.MustParse(rr.Requests.Memory)
			}
		}
	}

	return req
}
<commit_msg>refactoring<commit_after>/*
Copyright 2019 The Skaffold 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 sources

import (
	"context"
	"fmt"
	"io"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubectl"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
)

// BuildContextSource is the generic type for the different build context sources the kaniko builder can use
type BuildContextSource interface {
	Setup(ctx context.Context, out io.Writer, artifact *latest.Artifact, initialTag string, dependencies []string) (string, error)
	Pod(args []string) *v1.Pod
	ModifyPod(ctx context.Context, p *v1.Pod) error
	Cleanup(ctx context.Context) error
}

// Retrieve returns the correct build context based on the config
func Retrieve(cli *kubectl.CLI, clusterDetails *latest.ClusterDetails, artifact *latest.KanikoArtifact) BuildContextSource {
	if artifact.BuildContext.LocalDir != nil {
		return &LocalDir{
			clusterDetails: clusterDetails,
			artifact:       artifact,
			kubectl:        cli,
		}
	}

	return &GCSBucket{
		clusterDetails: clusterDetails,
		artifact:       artifact,
	}
}

func podTemplate(clusterDetails *latest.ClusterDetails, artifact *latest.KanikoArtifact, args []string, version string) *v1.Pod {
	userAgent := fmt.Sprintf("UpstreamClient(skaffold-%s)", version)

	env := []v1.EnvVar{{
		Name:  "GOOGLE_APPLICATION_CREDENTIALS",
		Value: "/secret/kaniko-secret",
	}, {
		// This should be same https://github.com/GoogleContainerTools/kaniko/blob/77cfb912f3483c204bfd09e1ada44fd200b15a78/pkg/executor/push.go#L49
		Name:  "UPSTREAM_CLIENT_TYPE",
		Value: userAgent,
	}}

	env = setProxy(clusterDetails, env)

	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "kaniko-",
			Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
			Namespace:    clusterDetails.Namespace,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:            constants.DefaultKanikoContainerName,
					Image:           artifact.Image,
					Args:            args,
					ImagePullPolicy: v1.PullIfNotPresent,
					Env:             env,
					Resources:       resourceRequirements(clusterDetails.Resources),
				},
			},
			RestartPolicy: v1.RestartPolicyNever,
		},
	}
	if clusterDetails.PullSecretName != "" {
		vm, v := getVolumeMountAndSecretVolumes(constants.DefaultKanikoSecretName, "/secret", clusterDetails.PullSecretName)
		pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, vm)
		pod.Spec.Volumes = append(pod.Spec.Volumes, v)
	}
	if artifact.Cache != nil && artifact.Cache.HostPath != "" {
		vm, v := getVolumeMountAndHostPathVolumes(constants.DefaultKanikoCacheDirName, constants.DefaultKanikoCacheDirMountPath, artifact.Cache.HostPath)
		pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, vm)
		pod.Spec.Volumes = append(pod.Spec.Volumes, v)
	}

	if clusterDetails.DockerConfig == nil {
		return pod
	}

	vm, v := getVolumeMountAndSecretVolumes(constants.DefaultKanikoDockerConfigSecretName, constants.DefaultKanikoDockerConfigPath, clusterDetails.DockerConfig.SecretName)
	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, vm)
	pod.Spec.Volumes = append(pod.Spec.Volumes, v)

	return pod
}

func getVolumeMountAndSecretVolumes(volumeName string, mountPath string, secretName string) (v1.VolumeMount, v1.Volume){
	volumeMount := v1.VolumeMount{
		Name:      volumeName,
		MountPath: mountPath,
	}
	volume := v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			Secret: &v1.SecretVolumeSource{
				SecretName: secretName,
			},
		},
	}
	return volumeMount, volume
}

func getVolumeMountAndHostPathVolumes(volumeName string, mountPath string, path string) (v1.VolumeMount, v1.Volume){
	volumeMount := v1.VolumeMount{
		Name:      volumeName,
		MountPath: mountPath,
	}
	volume := v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			HostPath: &v1.HostPathVolumeSource{
				Path: path,
			},
		},
	}
	return volumeMount, volume
}

func setProxy(clusterDetails *latest.ClusterDetails, env []v1.EnvVar) []v1.EnvVar {
	if clusterDetails.HTTPProxy != "" {
		proxy := v1.EnvVar{
			Name:  "HTTP_PROXY",
			Value: clusterDetails.HTTPProxy,
		}
		env = append(env, proxy)
	}

	if clusterDetails.HTTPSProxy != "" {
		proxy := v1.EnvVar{
			Name:  "HTTPS_PROXY",
			Value: clusterDetails.HTTPSProxy,
		}
		env = append(env, proxy)
	}
	return env
}

func resourceRequirements(rr *latest.ResourceRequirements) v1.ResourceRequirements {
	req := v1.ResourceRequirements{}

	if rr != nil {
		if rr.Limits != nil {
			req.Limits = v1.ResourceList{}
			if rr.Limits.CPU != "" {
				req.Limits[v1.ResourceCPU] = resource.MustParse(rr.Limits.CPU)
			}

			if rr.Limits.Memory != "" {
				req.Limits[v1.ResourceMemory] = resource.MustParse(rr.Limits.Memory)
			}
		}

		if rr.Requests != nil {
			req.Requests = v1.ResourceList{}
			if rr.Requests.CPU != "" {
				req.Requests[v1.ResourceCPU] = resource.MustParse(rr.Requests.CPU)
			}
			if rr.Requests.Memory != "" {
				req.Requests[v1.ResourceMemory] = resource.MustParse(rr.Requests.Memory)
			}
		}
	}

	return req
}
<|endoftext|>