<commit_before>// Copyright 2020 Authors of Cilium
//
// 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.

// +build !privileged_tests

package api

import (
	"context"
	"testing"
	"time"

	"github.com/cilium/cilium/pkg/api/metrics/mock"

	"gopkg.in/check.v1"
)

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

type ApiSuite struct{}

var _ = check.Suite(&ApiSuite{})

func (a *ApiSuite) TestRateLimit(c *check.C) {
	metricsAPI := mock.NewMockMetrics()
	client, err := NewClient("", "dummy-subscription", "dummy-resource-group", "", metricsAPI, 10.0, 4, true)
	c.Assert(err, check.IsNil)
	c.Assert(client, check.Not(check.IsNil))

	for i := 0; i < 10; i++ {
		client.limiter.Limit(context.TODO(), "test")
	}

	c.Assert(metricsAPI.RateLimit("test"), check.Not(check.DeepEquals), time.Duration(0))
}
<commit_msg>azure: Fix API rate limit test<commit_after>// Copyright 2020 Authors of Cilium
//
// 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.

// +build !privileged_tests

package api

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/cilium/cilium/pkg/api/metrics/mock"

	"gopkg.in/check.v1"
)

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

type ApiSuite struct{}

var _ = check.Suite(&ApiSuite{})

func (a *ApiSuite) TestRateLimit(c *check.C) {
	// Since github.com/Azure/go-autorest/autorest/azure/auth v0.5.6, the
	// MSI_ENDPOINT environment variable must be set, otherwise this test will
	// fail with the error "failed to get oauth token from MSI: MSI not
	// available".
	//
	// Temporarily set the environment variable for the duration of the test,
	// using the same workaround as used in
	// github.com/Azure/go-autorest/autorest/azure/auth's tests.
	os.Setenv("MSI_ENDPOINT", "http://localhost")
	defer func() {
		os.Unsetenv("MSI_ENDPOINT")
	}()

	metricsAPI := mock.NewMockMetrics()
	client, err := NewClient("", "dummy-subscription", "dummy-resource-group", "", metricsAPI, 10.0, 4, true)
	c.Assert(err, check.IsNil)
	c.Assert(client, check.Not(check.IsNil))

	for i := 0; i < 10; i++ {
		client.limiter.Limit(context.TODO(), "test")
	}

	c.Assert(metricsAPI.RateLimit("test"), check.Not(check.DeepEquals), time.Duration(0))
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"io"
	"sort"
	"strconv"
	"strings"

	"time"

	"github.com/docker/docker/pkg/units"
	"github.com/spf13/cobra"

	kapi "k8s.io/kubernetes/pkg/api"
	kerrors "k8s.io/kubernetes/pkg/api/errors"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"

	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/cli/describe"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deployutil "github.com/openshift/origin/pkg/deploy/util"
)

// DeployOptions holds all the options for the `deploy` command
type DeployOptions struct {
	out             io.Writer
	osClient        client.Interface
	kubeClient      kclient.Interface
	builder         *resource.Builder
	namespace       string
	baseCommandName string

	deploymentConfigName string
	deployLatest         bool
	retryDeploy          bool
	cancelDeploy         bool
	enableTriggers       bool
}

const (
	deployLong = `
View, start, cancel, or retry a deployment

This command allows you to control a deployment config. Each individual deployment is exposed
as a new replication controller, and the deployment process manages scaling down old deployments
and scaling up new ones. You can rollback to any previous deployment, or even scale multiple
deployments up at the same time.

There are several deployment strategies defined:

* Rolling (default) - scales up the new deployment in stages, gradually reducing the number
  of old deployments. If one of the new deployed pods never becomes "ready", the new deployment
  will be rolled back (scaled down to zero). Use when your application can tolerate two versions
  of code running at the same time (many web applications, scalable databases)
* Recreate - scales the old deployment down to zero, then scales the new deployment up to full.
  Use when your application cannot tolerate two versions of code running at the same time
* Custom - run your own deployment process inside a Docker container using your own scripts.

If a deployment fails, you may opt to retry it (if the error was transient). Some deployments may
never successfully complete - in which case you can use the '--latest' flag to force a redeployment.
When rolling back to a previous deployment, a new deployment will be created with an identical copy
of your config at the latest position.

If you want to cancel a running deployment, use '--cancel' but keep in mind that this is a best-effort
operation and may take some time to complete. It’s possible the deployment will partially or totally
complete before the cancellation is effective. In such a case an appropriate event will be emitted.

If no options are given, shows information about the latest deployment.`

	deployExample = `  # Display the latest deployment for the 'database' deployment config
  $ %[1]s deploy database

  # Start a new deployment based on the 'database'
  $ %[1]s deploy database --latest

  # Retry the latest failed deployment based on 'frontend'
  # The deployer pod and any hook pods are deleted for the latest failed deployment
  $ %[1]s deploy frontend --retry

  # Cancel the in-progress deployment based on 'frontend'
  $ %[1]s deploy frontend --cancel`
)

// NewCmdDeploy creates a new `deploy` command.
func NewCmdDeploy(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
	options := &DeployOptions{
		baseCommandName: fullName,
	}

	cmd := &cobra.Command{
		Use:        "deploy DEPLOYMENTCONFIG [--latest|--retry|--cancel|--enable-triggers]",
		Short:      "View, start, cancel, or retry a deployment",
		Long:       deployLong,
		Example:    fmt.Sprintf(deployExample, fullName),
		SuggestFor: []string{"deployment"},
		Run: func(cmd *cobra.Command, args []string) {
			if err := options.Complete(f, args, out); err != nil {
				kcmdutil.CheckErr(err)
			}

			if err := options.Validate(); err != nil {
				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
			}

			if err := options.RunDeploy(); err != nil {
				kcmdutil.CheckErr(err)
			}
		},
	}

	cmd.Flags().BoolVar(&options.deployLatest, "latest", false, "Start a new deployment now.")
	cmd.Flags().BoolVar(&options.retryDeploy, "retry", false, "Retry the latest failed deployment.")
	cmd.Flags().BoolVar(&options.cancelDeploy, "cancel", false, "Cancel the in-progress deployment.")
	cmd.Flags().BoolVar(&options.enableTriggers, "enable-triggers", false, "Enables all image triggers for the deployment config.")

	return cmd
}

func (o *DeployOptions) Complete(f *clientcmd.Factory, args []string, out io.Writer) error {
	if len(args) > 1 {
		return errors.New("only one deployment config name is supported as argument.")
	}
	var err error

	o.osClient, o.kubeClient, err = f.Clients()
	if err != nil {
		return err
	}
	o.namespace, _, err = f.DefaultNamespace()
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	o.builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder())

	o.out = out

	if len(args) > 0 {
		o.deploymentConfigName = args[0]
	}

	return nil
}

func (o DeployOptions) Validate() error {
	if len(o.deploymentConfigName) == 0 {
		return errors.New("a deployment config name is required.")
	}
	numOptions := 0
	if o.deployLatest {
		numOptions++
	}
	if o.retryDeploy {
		numOptions++
	}
	if o.cancelDeploy {
		numOptions++
	}
	if o.enableTriggers {
		numOptions++
	}
	if numOptions > 1 {
		return errors.New("only one of --latest, --retry, --cancel, or --enable-triggers is allowed.")
	}
	return nil
}

func (o DeployOptions) RunDeploy() error {
	r := o.builder.
		NamespaceParam(o.namespace).
		ResourceNames("deploymentconfigs", o.deploymentConfigName).
		SingleResourceType().
		Do()
	resultObj, err := r.Object()
	if err != nil {
		return err
	}
	config, ok := resultObj.(*deployapi.DeploymentConfig)
	if !ok {
		return fmt.Errorf("%s is not a valid deployment config", o.deploymentConfigName)
	}

	switch {
	case o.deployLatest:
		err = o.deploy(config, o.out)
	case o.retryDeploy:
		err = o.retry(config, o.out)
	case o.cancelDeploy:
		err = o.cancel(config, o.out)
	case o.enableTriggers:
		err = o.reenableTriggers(config, o.out)
	default:
		describer := describe.NewLatestDeploymentsDescriber(o.osClient, o.kubeClient, -1)
		desc, err := describer.Describe(config.Namespace, config.Name)
		if err != nil {
			return err
		}
		fmt.Fprint(o.out, desc)
	}

	return err
}

// deploy launches a new deployment unless there's already a deployment
// process in progress for config.
func (o DeployOptions) deploy(config *deployapi.DeploymentConfig, out io.Writer) error {
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err == nil {
		// Reject attempts to start a concurrent deployment.
		status := deployutil.DeploymentStatusFor(deployment)
		if status != deployapi.DeploymentStatusComplete && status != deployapi.DeploymentStatusFailed {
			return fmt.Errorf("#%d is already in progress (%s).\nOptionally, you can cancel this deployment using the --cancel option.", config.Status.LatestVersion, status)
		}
	} else {
		if !kerrors.IsNotFound(err) {
			return err
		}
	}

	config.Status.LatestVersion++
	_, err = o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err == nil {
		fmt.Fprintf(out, "Started deployment #%d\n", config.Status.LatestVersion)
	}
	return err
}

// retry resets the status of the latest deployment to New, which will cause
// the deployment to be retried. An error is returned if the deployment is not
// currently in a failed state.
func (o DeployOptions) retry(config *deployapi.DeploymentConfig, out io.Writer) error {
	if config.Status.LatestVersion == 0 {
		return fmt.Errorf("no deployments found for %s/%s", config.Namespace, config.Name)
	}
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			return fmt.Errorf("unable to find the latest deployment (#%d).\nYou can start a new deployment using the --latest option.", config.Status.LatestVersion)
		}
		return err
	}

	if status := deployutil.DeploymentStatusFor(deployment); status != deployapi.DeploymentStatusFailed {
		message := fmt.Sprintf("#%d is %s; only failed deployments can be retried.\n", config.Status.LatestVersion, status)
		if status == deployapi.DeploymentStatusComplete {
			message += "You can start a new deployment using the --latest option."
		} else {
			message += "Optionally, you can cancel this deployment using the --cancel option."
		}

		return fmt.Errorf(message)
	}

	// Delete the deployer pod as well as the deployment hooks pods, if any
	pods, err := o.kubeClient.Pods(config.Namespace).List(kapi.ListOptions{LabelSelector: deployutil.DeployerPodSelector(deploymentName)})
	if err != nil {
		return fmt.Errorf("failed to list deployer/hook pods for deployment #%d: %v", config.Status.LatestVersion, err)
	}
	for _, pod := range pods.Items {
		err := o.kubeClient.Pods(pod.Namespace).Delete(pod.Name, kapi.NewDeleteOptions(0))
		if err != nil {
			return fmt.Errorf("failed to delete deployer/hook pod %s for deployment #%d: %v", pod.Name, config.Status.LatestVersion, err)
		}
	}

	deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew)
	// clear out the cancellation flag as well as any previous status-reason annotation
	delete(deployment.Annotations, deployapi.DeploymentStatusReasonAnnotation)
	delete(deployment.Annotations, deployapi.DeploymentCancelledAnnotation)
	_, err = o.kubeClient.ReplicationControllers(deployment.Namespace).Update(deployment)
	if err == nil {
		fmt.Fprintf(out, "Retried #%d\n", config.Status.LatestVersion)
	}
	return err
}

// cancel cancels any deployment process in progress for config.
func (o DeployOptions) cancel(config *deployapi.DeploymentConfig, out io.Writer) error {
	deployments, err := o.kubeClient.ReplicationControllers(config.Namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(config.Name)})
	if err != nil {
		return err
	}
	if len(deployments.Items) == 0 {
		fmt.Fprintf(out, "There have been no deployments for %s/%s\n", config.Namespace, config.Name)
		return nil
	}
	sort.Sort(deployutil.ByLatestVersionDesc(deployments.Items))
	failedCancellations := []string{}
	anyCancelled := false
	for _, deployment := range deployments.Items {
		status := deployutil.DeploymentStatusFor(&deployment)
		switch status {
		case deployapi.DeploymentStatusNew,
			deployapi.DeploymentStatusPending,
			deployapi.DeploymentStatusRunning:

			if deployutil.IsDeploymentCancelled(&deployment) {
				continue
			}

			deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue
			deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser
			_, err := o.kubeClient.ReplicationControllers(deployment.Namespace).Update(&deployment)
			if err == nil {
				fmt.Fprintf(out, "Cancelled deployment #%d\n", config.Status.LatestVersion)
				anyCancelled = true
			} else {
				fmt.Fprintf(out, "Couldn't cancel deployment #%d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err)
				failedCancellations = append(failedCancellations, strconv.Itoa(deployutil.DeploymentVersionFor(&deployment)))
			}
		}
	}
	if len(failedCancellations) > 0 {
		return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", "))
	}
	if !anyCancelled {
		latest := &deployments.Items[0]
		status := string(deployutil.DeploymentStatusFor(latest))
		if deployutil.IsDeploymentCancelled(latest) {
			status = "cancelled"
		}
		timeAt := strings.ToLower(units.HumanDuration(time.Now().Sub(latest.CreationTimestamp.Time)))
		fmt.Fprintf(out, "No deployments are in progress (latest deployment #%d %s %s ago)\n",
			deployutil.DeploymentVersionFor(latest),
			strings.ToLower(status),
			timeAt)
	}
	return nil
}

// reenableTriggers enables all image triggers and then persists config.
func (o DeployOptions) reenableTriggers(config *deployapi.DeploymentConfig, out io.Writer) error {
	enabled := []string{}
	for _, trigger := range config.Spec.Triggers {
		if trigger.Type == deployapi.DeploymentTriggerOnImageChange {
			trigger.ImageChangeParams.Automatic = true
			enabled = append(enabled, trigger.ImageChangeParams.From.Name)
		}
	}
	if len(enabled) == 0 {
		fmt.Fprintln(out, "No image triggers found to enable")
		return nil
	}
	_, err := o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "Enabled image triggers: %s\n", strings.Join(enabled, ","))
	return nil
}
<commit_msg>oc: fix help message for deploy<commit_after>package cmd

import (
	"errors"
	"fmt"
	"io"
	"sort"
	"strconv"
	"strings"

	"time"

	"github.com/docker/docker/pkg/units"
	"github.com/spf13/cobra"

	kapi "k8s.io/kubernetes/pkg/api"
	kerrors "k8s.io/kubernetes/pkg/api/errors"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"

	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/cli/describe"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deployutil "github.com/openshift/origin/pkg/deploy/util"
)

// DeployOptions holds all the options for the `deploy` command
type DeployOptions struct {
	out             io.Writer
	osClient        client.Interface
	kubeClient      kclient.Interface
	builder         *resource.Builder
	namespace       string
	baseCommandName string

	deploymentConfigName string
	deployLatest         bool
	retryDeploy          bool
	cancelDeploy         bool
	enableTriggers       bool
}

const (
	deployLong = `
View, start, cancel, or retry a deployment

This command allows you to control a deployment config. Each individual deployment is exposed
as a new replication controller, and the deployment process manages scaling down old deployments
and scaling up new ones. Use '%[1]s rollback' to rollback to any previous deployment.

There are several deployment strategies defined:

* Rolling (default) - scales up the new deployment in stages, gradually reducing the number
  of old deployments. If one of the new deployed pods never becomes "ready", the new deployment
  will be rolled back (scaled down to zero). Use when your application can tolerate two versions
  of code running at the same time (many web applications, scalable databases)
* Recreate - scales the old deployment down to zero, then scales the new deployment up to full.
  Use when your application cannot tolerate two versions of code running at the same time
* Custom - run your own deployment process inside a Docker container using your own scripts.

If a deployment fails, you may opt to retry it (if the error was transient). Some deployments may
never successfully complete - in which case you can use the '--latest' flag to force a redeployment.
If a deployment config has completed deploying successfully at least once in the past, it would be
automatically rolled back in the event of a new failed deployment. Note that you would still need
to update the erroneous deployment config in order to have its template persisted across your
application.

If you want to cancel a running deployment, use '--cancel' but keep in mind that this is a best-effort
operation and may take some time to complete. It’s possible the deployment will partially or totally
complete before the cancellation is effective. In such a case an appropriate event will be emitted.

If no options are given, shows information about the latest deployment.`

	deployExample = `  # Display the latest deployment for the 'database' deployment config
  $ %[1]s deploy database

  # Start a new deployment based on the 'database'
  $ %[1]s deploy database --latest

  # Retry the latest failed deployment based on 'frontend'
  # The deployer pod and any hook pods are deleted for the latest failed deployment
  $ %[1]s deploy frontend --retry

  # Cancel the in-progress deployment based on 'frontend'
  $ %[1]s deploy frontend --cancel`
)

// NewCmdDeploy creates a new `deploy` command.
func NewCmdDeploy(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
	options := &DeployOptions{
		baseCommandName: fullName,
	}

	cmd := &cobra.Command{
		Use:        "deploy DEPLOYMENTCONFIG [--latest|--retry|--cancel|--enable-triggers]",
		Short:      "View, start, cancel, or retry a deployment",
		Long:       fmt.Sprintf(deployLong, fullName),
		Example:    fmt.Sprintf(deployExample, fullName),
		SuggestFor: []string{"deployment"},
		Run: func(cmd *cobra.Command, args []string) {
			if err := options.Complete(f, args, out); err != nil {
				kcmdutil.CheckErr(err)
			}

			if err := options.Validate(); err != nil {
				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
			}

			if err := options.RunDeploy(); err != nil {
				kcmdutil.CheckErr(err)
			}
		},
	}

	cmd.Flags().BoolVar(&options.deployLatest, "latest", false, "Start a new deployment now.")
	cmd.Flags().BoolVar(&options.retryDeploy, "retry", false, "Retry the latest failed deployment.")
	cmd.Flags().BoolVar(&options.cancelDeploy, "cancel", false, "Cancel the in-progress deployment.")
	cmd.Flags().BoolVar(&options.enableTriggers, "enable-triggers", false, "Enables all image triggers for the deployment config.")

	return cmd
}

func (o *DeployOptions) Complete(f *clientcmd.Factory, args []string, out io.Writer) error {
	if len(args) > 1 {
		return errors.New("only one deployment config name is supported as argument.")
	}
	var err error

	o.osClient, o.kubeClient, err = f.Clients()
	if err != nil {
		return err
	}
	o.namespace, _, err = f.DefaultNamespace()
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	o.builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder())

	o.out = out

	if len(args) > 0 {
		o.deploymentConfigName = args[0]
	}

	return nil
}

func (o DeployOptions) Validate() error {
	if len(o.deploymentConfigName) == 0 {
		return errors.New("a deployment config name is required.")
	}
	numOptions := 0
	if o.deployLatest {
		numOptions++
	}
	if o.retryDeploy {
		numOptions++
	}
	if o.cancelDeploy {
		numOptions++
	}
	if o.enableTriggers {
		numOptions++
	}
	if numOptions > 1 {
		return errors.New("only one of --latest, --retry, --cancel, or --enable-triggers is allowed.")
	}
	return nil
}

func (o DeployOptions) RunDeploy() error {
	r := o.builder.
		NamespaceParam(o.namespace).
		ResourceNames("deploymentconfigs", o.deploymentConfigName).
		SingleResourceType().
		Do()
	resultObj, err := r.Object()
	if err != nil {
		return err
	}
	config, ok := resultObj.(*deployapi.DeploymentConfig)
	if !ok {
		return fmt.Errorf("%s is not a valid deployment config", o.deploymentConfigName)
	}

	switch {
	case o.deployLatest:
		err = o.deploy(config, o.out)
	case o.retryDeploy:
		err = o.retry(config, o.out)
	case o.cancelDeploy:
		err = o.cancel(config, o.out)
	case o.enableTriggers:
		err = o.reenableTriggers(config, o.out)
	default:
		describer := describe.NewLatestDeploymentsDescriber(o.osClient, o.kubeClient, -1)
		desc, err := describer.Describe(config.Namespace, config.Name)
		if err != nil {
			return err
		}
		fmt.Fprint(o.out, desc)
	}

	return err
}

// deploy launches a new deployment unless there's already a deployment
// process in progress for config.
func (o DeployOptions) deploy(config *deployapi.DeploymentConfig, out io.Writer) error {
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err == nil {
		// Reject attempts to start a concurrent deployment.
		status := deployutil.DeploymentStatusFor(deployment)
		if status != deployapi.DeploymentStatusComplete && status != deployapi.DeploymentStatusFailed {
			return fmt.Errorf("#%d is already in progress (%s).\nOptionally, you can cancel this deployment using the --cancel option.", config.Status.LatestVersion, status)
		}
	} else {
		if !kerrors.IsNotFound(err) {
			return err
		}
	}

	config.Status.LatestVersion++
	_, err = o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err == nil {
		fmt.Fprintf(out, "Started deployment #%d\n", config.Status.LatestVersion)
		fmt.Fprintf(out, "Use '%s logs -f dc/%s' to track its progress.\n", o.baseCommandName, config.Name)
	}
	return err
}

// retry resets the status of the latest deployment to New, which will cause
// the deployment to be retried. An error is returned if the deployment is not
// currently in a failed state.
func (o DeployOptions) retry(config *deployapi.DeploymentConfig, out io.Writer) error {
	if config.Status.LatestVersion == 0 {
		return fmt.Errorf("no deployments found for %s/%s", config.Namespace, config.Name)
	}
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			return fmt.Errorf("unable to find the latest deployment (#%d).\nYou can start a new deployment using the --latest option.", config.Status.LatestVersion)
		}
		return err
	}

	if status := deployutil.DeploymentStatusFor(deployment); status != deployapi.DeploymentStatusFailed {
		message := fmt.Sprintf("#%d is %s; only failed deployments can be retried.\n", config.Status.LatestVersion, status)
		if status == deployapi.DeploymentStatusComplete {
			message += "You can start a new deployment using the --latest option."
		} else {
			message += "Optionally, you can cancel this deployment using the --cancel option."
		}

		return fmt.Errorf(message)
	}

	// Delete the deployer pod as well as the deployment hooks pods, if any
	pods, err := o.kubeClient.Pods(config.Namespace).List(kapi.ListOptions{LabelSelector: deployutil.DeployerPodSelector(deploymentName)})
	if err != nil {
		return fmt.Errorf("failed to list deployer/hook pods for deployment #%d: %v", config.Status.LatestVersion, err)
	}
	for _, pod := range pods.Items {
		err := o.kubeClient.Pods(pod.Namespace).Delete(pod.Name, kapi.NewDeleteOptions(0))
		if err != nil {
			return fmt.Errorf("failed to delete deployer/hook pod %s for deployment #%d: %v", pod.Name, config.Status.LatestVersion, err)
		}
	}

	deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew)
	// clear out the cancellation flag as well as any previous status-reason annotation
	delete(deployment.Annotations, deployapi.DeploymentStatusReasonAnnotation)
	delete(deployment.Annotations, deployapi.DeploymentCancelledAnnotation)
	_, err = o.kubeClient.ReplicationControllers(deployment.Namespace).Update(deployment)
	if err == nil {
		fmt.Fprintf(out, "Retried #%d\n", config.Status.LatestVersion)
	}
	return err
}

// cancel cancels any deployment process in progress for config.
func (o DeployOptions) cancel(config *deployapi.DeploymentConfig, out io.Writer) error {
	deployments, err := o.kubeClient.ReplicationControllers(config.Namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(config.Name)})
	if err != nil {
		return err
	}
	if len(deployments.Items) == 0 {
		fmt.Fprintf(out, "There have been no deployments for %s/%s\n", config.Namespace, config.Name)
		return nil
	}
	sort.Sort(deployutil.ByLatestVersionDesc(deployments.Items))
	failedCancellations := []string{}
	anyCancelled := false
	for _, deployment := range deployments.Items {
		status := deployutil.DeploymentStatusFor(&deployment)
		switch status {
		case deployapi.DeploymentStatusNew,
			deployapi.DeploymentStatusPending,
			deployapi.DeploymentStatusRunning:

			if deployutil.IsDeploymentCancelled(&deployment) {
				continue
			}

			deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue
			deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser
			_, err := o.kubeClient.ReplicationControllers(deployment.Namespace).Update(&deployment)
			if err == nil {
				fmt.Fprintf(out, "Cancelled deployment #%d\n", config.Status.LatestVersion)
				anyCancelled = true
			} else {
				fmt.Fprintf(out, "Couldn't cancel deployment #%d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err)
				failedCancellations = append(failedCancellations, strconv.Itoa(deployutil.DeploymentVersionFor(&deployment)))
			}
		}
	}
	if len(failedCancellations) > 0 {
		return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", "))
	}
	if !anyCancelled {
		latest := &deployments.Items[0]
		status := string(deployutil.DeploymentStatusFor(latest))
		if deployutil.IsDeploymentCancelled(latest) {
			status = "cancelled"
		}
		timeAt := strings.ToLower(units.HumanDuration(time.Now().Sub(latest.CreationTimestamp.Time)))
		fmt.Fprintf(out, "No deployments are in progress (latest deployment #%d %s %s ago)\n",
			deployutil.DeploymentVersionFor(latest),
			strings.ToLower(status),
			timeAt)
	}
	return nil
}

// reenableTriggers enables all image triggers and then persists config.
func (o DeployOptions) reenableTriggers(config *deployapi.DeploymentConfig, out io.Writer) error {
	enabled := []string{}
	for _, trigger := range config.Spec.Triggers {
		if trigger.Type == deployapi.DeploymentTriggerOnImageChange {
			trigger.ImageChangeParams.Automatic = true
			enabled = append(enabled, trigger.ImageChangeParams.From.Name)
		}
	}
	if len(enabled) == 0 {
		fmt.Fprintln(out, "No image triggers found to enable")
		return nil
	}
	_, err := o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "Enabled image triggers: %s\n", strings.Join(enabled, ","))
	return nil
}
<|endoftext|>


<commit_before>package engine

import (
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"time"
)

// PolicyDataObject is an informational data structure with Kind and Constructor for PolicyData
var PolicyDataObject = &runtime.Info{
	Kind:        "policy",
	Storable:    true,
	Versioned:   true,
	Constructor: func() runtime.Object { return &PolicyData{} },
}

// PolicyDataKey is the default key for the policy object (there is only one policy exists but with multiple generations)
var PolicyDataKey = runtime.KeyFromParts(runtime.SystemNS, PolicyDataObject.Kind, runtime.EmptyName)

// PolicyData is a struct which contains references to a generation for each object included into the policy
type PolicyData struct {
	runtime.TypeKind `yaml:",inline"`
	Metadata         PolicyDataMetadata

	// Objects stores all policy objects in map: namespace -> kind -> name -> generation
	Objects map[string]map[string]map[string]runtime.Generation
}

// PolicyDataMetadata is the metadata for PolicyData object that includes generation
type PolicyDataMetadata struct {
	Generation runtime.Generation
	UpdatedAt  time.Time
	UpdatedBy  string
}

// GetName returns PolicyData name
func (policyData *PolicyData) GetName() string {
	return runtime.EmptyName
}

// GetNamespace returns PolicyData namespace
func (policyData *PolicyData) GetNamespace() string {
	return runtime.SystemNS
}

// GetGeneration returns PolicyData generation
func (policyData *PolicyData) GetGeneration() runtime.Generation {
	return policyData.Metadata.Generation
}

// SetGeneration sets PolicyData generation
func (policyData *PolicyData) SetGeneration(gen runtime.Generation) {
	policyData.Metadata.Generation = gen
}

// Add adds an object to PolicyData
func (policyData *PolicyData) Add(obj lang.Base) {
	byNs, exist := policyData.Objects[obj.GetNamespace()]
	if !exist {
		byNs = make(map[string]map[string]runtime.Generation)
		policyData.Objects[obj.GetNamespace()] = byNs
	}
	byKind, exist := byNs[obj.GetKind()]
	if !exist {
		byKind = make(map[string]runtime.Generation)
		byNs[obj.GetKind()] = byKind
	}
	byKind[obj.GetName()] = obj.GetGeneration()
}

// Remove deletes an object from PolicyData
func (policyData *PolicyData) Remove(obj lang.Base) bool { // nolint: interfacer
	byNs, exist := policyData.Objects[obj.GetNamespace()]
	if !exist {
		return false
	}
	byKind, exist := byNs[obj.GetKind()]
	if !exist {
		return false
	}
	_, exist = byKind[obj.GetName()]
	delete(byKind, obj.GetName())

	return exist
}

// GetDefaultColumns returns default set of columns to be displayed
func (policyData *PolicyData) GetDefaultColumns() []string {
	return []string{"Policy Version"}
}

// AsColumns returns PolicyData representation as columns
func (policyData *PolicyData) AsColumns() map[string]string {
	result := make(map[string]string)

	result["Policy Version"] = policyData.GetGeneration().String()

	return result
}
<commit_msg>removed empty lines<commit_after>package engine

import (
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"time"
)

// PolicyDataObject is an informational data structure with Kind and Constructor for PolicyData
var PolicyDataObject = &runtime.Info{
	Kind:        "policy",
	Storable:    true,
	Versioned:   true,
	Constructor: func() runtime.Object { return &PolicyData{} },
}

// PolicyDataKey is the default key for the policy object (there is only one policy exists but with multiple generations)
var PolicyDataKey = runtime.KeyFromParts(runtime.SystemNS, PolicyDataObject.Kind, runtime.EmptyName)

// PolicyData is a struct which contains references to a generation for each object included into the policy
type PolicyData struct {
	runtime.TypeKind `yaml:",inline"`
	Metadata         PolicyDataMetadata

	// Objects stores all policy objects in map: namespace -> kind -> name -> generation
	Objects map[string]map[string]map[string]runtime.Generation
}

// PolicyDataMetadata is the metadata for PolicyData object that includes generation
type PolicyDataMetadata struct {
	Generation runtime.Generation
	UpdatedAt  time.Time
	UpdatedBy  string
}

// GetName returns PolicyData name
func (policyData *PolicyData) GetName() string {
	return runtime.EmptyName
}

// GetNamespace returns PolicyData namespace
func (policyData *PolicyData) GetNamespace() string {
	return runtime.SystemNS
}

// GetGeneration returns PolicyData generation
func (policyData *PolicyData) GetGeneration() runtime.Generation {
	return policyData.Metadata.Generation
}

// SetGeneration sets PolicyData generation
func (policyData *PolicyData) SetGeneration(gen runtime.Generation) {
	policyData.Metadata.Generation = gen
}

// Add adds an object to PolicyData
func (policyData *PolicyData) Add(obj lang.Base) {
	byNs, exist := policyData.Objects[obj.GetNamespace()]
	if !exist {
		byNs = make(map[string]map[string]runtime.Generation)
		policyData.Objects[obj.GetNamespace()] = byNs
	}
	byKind, exist := byNs[obj.GetKind()]
	if !exist {
		byKind = make(map[string]runtime.Generation)
		byNs[obj.GetKind()] = byKind
	}
	byKind[obj.GetName()] = obj.GetGeneration()
}

// Remove deletes an object from PolicyData
func (policyData *PolicyData) Remove(obj lang.Base) bool { // nolint: interfacer
	byNs, exist := policyData.Objects[obj.GetNamespace()]
	if !exist {
		return false
	}
	byKind, exist := byNs[obj.GetKind()]
	if !exist {
		return false
	}
	_, exist = byKind[obj.GetName()]
	delete(byKind, obj.GetName())

	return exist
}

// GetDefaultColumns returns default set of columns to be displayed
func (policyData *PolicyData) GetDefaultColumns() []string {
	return []string{"Policy Version"}
}

// AsColumns returns PolicyData representation as columns
func (policyData *PolicyData) AsColumns() map[string]string {
	result := make(map[string]string)
	result["Policy Version"] = policyData.GetGeneration().String()
	return result
}
<|endoftext|>


<commit_before>// ClawIO - Scalable Distributed High-Performance Synchronisation and Sharing Service
//
// Copyright (C) 2015  Hugo González Labrador <clawio@hugo.labkode.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. See file COPYNG.

package api

import (
	"errors"
	"fmt"
	"github.com/clawio/clawiod/Godeps/_workspace/src/github.com/gorilla/handlers"
	"github.com/clawio/clawiod/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/clawio/clawiod/pkg/config"
	"github.com/clawio/clawiod/pkg/httpserver"
	"io"
	"os"
	"runtime"

	"net/http"
	"time"

	apidisp "github.com/clawio/clawiod/pkg/api/dispatcher"
	authdisp "github.com/clawio/clawiod/pkg/auth/dispatcher"
	storagedisp "github.com/clawio/clawiod/pkg/storage/dispatcher"

	logger "github.com/clawio/clawiod/pkg/logger/logrus"

	"github.com/clawio/clawiod/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid"
	"github.com/clawio/clawiod/Godeps/_workspace/src/github.com/tylerb/graceful"
)

type apiServer struct {
	appLogWriter io.Writer
	reqLogWriter io.Writer
	apidisp      apidisp.Dispatcher
	adisp        authdisp.Dispatcher
	sdisp        storagedisp.Dispatcher
	srv          *graceful.Server
	cfg          config.Config
}

// New returns a new HTTPServer
func New(appLogWriter io.Writer, reqLogWriter io.Writer, apidisp apidisp.Dispatcher, adisp authdisp.Dispatcher, sdisp storagedisp.Dispatcher, cfg config.Config) (httpserver.HTTPServer, error) {
	directives, err := cfg.GetDirectives()
	if err != nil {
		return nil, err
	}
	srv := &graceful.Server{
		NoSignalHandling: true,
		Timeout:          time.Duration(directives.ShutdownTimeout) * time.Second,
		Server: &http.Server{
			Addr: fmt.Sprintf(":%d", directives.Port),
		},
	}
	return &apiServer{appLogWriter: appLogWriter, reqLogWriter: reqLogWriter, apidisp: apidisp, adisp: adisp, sdisp: sdisp, srv: srv, cfg: cfg}, nil
}

func (s *apiServer) Start() error {
	directives, err := s.cfg.GetDirectives()
	if err != nil {
		return err
	}
	s.srv.Server.Handler = s.HandleRequest()
	if directives.TLSEnabled == true {
		return s.srv.ListenAndServeTLS(directives.TLSCertificate, directives.TLSCertificatePrivateKey)
	}
	return s.srv.ListenAndServe()
}
func (s *apiServer) StopChan() <-chan struct{} {
	return s.srv.StopChan()
}
func (s *apiServer) Stop() {
	s.srv.Stop(10 * time.Second)
}

func (s *apiServer) HandleRequest() http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		/******************************************
		 ** 1. Create logger for request    *******
		 ******************************************/
		log, err := logger.New(s.appLogWriter, "api-"+uuid.New(), s.cfg)
		if err != nil {
			// At this point we don't have a logger, so the output go to stderr
			fmt.Fprintln(os.Stderr, err)
		}
		log.Info("Request started:" + r.Method + " " + r.RequestURI)
		defer func() {
			log.Info("Request finished")

			// Catch panic and return 500
			var err error
			r := recover()
			if r != nil {
				switch t := r.(type) {
				case string:
					err = errors.New(t)
				case error:
					err = t
				default:
					err = errors.New(fmt.Sprintln(r))
				}
				trace := make([]byte, 2048)
				count := runtime.Stack(trace, true)
				log.Err(fmt.Sprintf("Recover from panic: %s\nStack of %d bytes: %s\n", err.Error(), count, trace))
				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
				return
			}
		}()

		directives, err := s.cfg.GetDirectives()
		if err != nil {
			log.Err(err.Error())
			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
			return
		}
		// Check the server is not in maintenance mode
		if directives.Maintenance == true {
			http.Error(w, directives.MaintenanceMessage, http.StatusServiceUnavailable)
			return
		}

		rootCtx := context.Background()
		ctx := context.WithValue(rootCtx, "log", log)
		ctx = context.WithValue(ctx, "adisp", s.adisp)
		ctx = context.WithValue(ctx, "sdisp", s.sdisp)

		s.apidisp.HandleRequest(ctx, w, r)

	}

	fn500 := func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	directives, err := s.cfg.GetDirectives()
	if err != nil {
		fmt.Fprintln(os.Stderr, "error at apiServer.HandleRequest():", err.Error())
		return http.HandlerFunc(fn500)
	}

	if directives.LogRequests == true {
		return handlers.CombinedLoggingHandler(s.reqLogWriter, http.HandlerFunc(fn))
	}
	return http.HandlerFunc(fn)
}
<commit_msg>Use configuration to enable OCWebDAV API<commit_after>// ClawIO - Scalable Distributed High-Performance Synchronisation and Sharing Service
//
// Copyright (C) 2015  Hugo González Labrador <clawio@hugo.labkode.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. See file COPYNG.

package api

import (
	"errors"
	"fmt"
	"github.com/clawio/clawiod/Godeps/_workspace/src/github.com/gorilla/handlers"
	"github.com/clawio/clawiod/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/clawio/clawiod/pkg/config"
	"github.com/clawio/clawiod/pkg/httpserver"
	"io"
	"os"
	"runtime"

	"net/http"
	"time"

	apidisp "github.com/clawio/clawiod/pkg/api/dispatcher"
	authdisp "github.com/clawio/clawiod/pkg/auth/dispatcher"
	storagedisp "github.com/clawio/clawiod/pkg/storage/dispatcher"

	logger "github.com/clawio/clawiod/pkg/logger/logrus"

	"github.com/clawio/clawiod/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid"
	"github.com/clawio/clawiod/Godeps/_workspace/src/github.com/tylerb/graceful"
)

type apiServer struct {
	appLogWriter io.Writer
	reqLogWriter io.Writer
	apidisp      apidisp.Dispatcher
	adisp        authdisp.Dispatcher
	sdisp        storagedisp.Dispatcher
	srv          *graceful.Server
	cfg          config.Config
}

// New returns a new HTTPServer
func New(appLogWriter io.Writer, reqLogWriter io.Writer, apidisp apidisp.Dispatcher, adisp authdisp.Dispatcher, sdisp storagedisp.Dispatcher, cfg config.Config) (httpserver.HTTPServer, error) {
	directives, err := cfg.GetDirectives()
	if err != nil {
		return nil, err
	}
	srv := &graceful.Server{
		NoSignalHandling: true,
		Timeout:          time.Duration(directives.ShutdownTimeout) * time.Second,
		Server: &http.Server{
			Addr: fmt.Sprintf(":%d", directives.Port),
		},
	}
	return &apiServer{appLogWriter: appLogWriter, reqLogWriter: reqLogWriter, apidisp: apidisp, adisp: adisp, sdisp: sdisp, srv: srv, cfg: cfg}, nil
}

func (s *apiServer) Start() error {
	directives, err := s.cfg.GetDirectives()
	if err != nil {
		return err
	}
	s.srv.Server.Handler = s.HandleRequest()
	if directives.TLSEnabled == true {
		return s.srv.ListenAndServeTLS(directives.TLSCertificate, directives.TLSCertificatePrivateKey)
	}
	return s.srv.ListenAndServe()
}
func (s *apiServer) StopChan() <-chan struct{} {
	return s.srv.StopChan()
}
func (s *apiServer) Stop() {
	s.srv.Stop(10 * time.Second)
}

func (s *apiServer) HandleRequest() http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		/******************************************
		 ** 1. Create logger for request    *******
		 ******************************************/
		log, err := logger.New(s.appLogWriter, "api-"+uuid.New(), s.cfg)
		if err != nil {
			// At this point we don't have a logger, so the output go to stderr
			fmt.Fprintln(os.Stderr, err)
		}
		log.Info("Request started: " + r.Method + " " + r.RequestURI)
		defer func() {
			log.Info("Request finished")

			// Catch panic and return 500
			var err error
			r := recover()
			if r != nil {
				switch t := r.(type) {
				case string:
					err = errors.New(t)
				case error:
					err = t
				default:
					err = errors.New(fmt.Sprintln(r))
				}
				trace := make([]byte, 2048)
				count := runtime.Stack(trace, true)
				log.Err(fmt.Sprintf("Recover from panic: %s\nStack of %d bytes: %s\n", err.Error(), count, trace))
				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
				return
			}
		}()

		directives, err := s.cfg.GetDirectives()
		if err != nil {
			log.Err(err.Error())
			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
			return
		}
		// Check the server is not in maintenance mode
		if directives.Maintenance == true {
			http.Error(w, directives.MaintenanceMessage, http.StatusServiceUnavailable)
			return
		}

		rootCtx := context.Background()
		ctx := context.WithValue(rootCtx, "log", log)
		ctx = context.WithValue(ctx, "adisp", s.adisp)
		ctx = context.WithValue(ctx, "sdisp", s.sdisp)

		s.apidisp.HandleRequest(ctx, w, r)

	}

	fn500 := func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	directives, err := s.cfg.GetDirectives()
	if err != nil {
		fmt.Fprintln(os.Stderr, "error at apiServer.HandleRequest():", err.Error())
		return http.HandlerFunc(fn500)
	}

	if directives.LogRequests == true {
		return handlers.CombinedLoggingHandler(s.reqLogWriter, http.HandlerFunc(fn))
	}
	return http.HandlerFunc(fn)
}
<|endoftext|>


<commit_before>package normalize

import (
	"fmt"
	"net"
	"strings"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/StackExchange/dnscontrol/pkg/transform"
	"github.com/StackExchange/dnscontrol/providers"
	"github.com/miekg/dns"
	"github.com/miekg/dns/dnsutil"
)

// Returns false if target does not validate.
func checkIPv4(label string) error {
	if net.ParseIP(label).To4() == nil {
		return fmt.Errorf("WARNING: target (%v) is not an IPv4 address", label)
	}
	return nil
}

// Returns false if target does not validate.
func checkIPv6(label string) error {
	if net.ParseIP(label).To16() == nil {
		return fmt.Errorf("WARNING: target (%v) is not an IPv6 address", label)
	}
	return nil
}

// make sure target is valid reference for cnames, mx, etc.
func checkTarget(target string) error {
	if target == "@" {
		return nil
	}
	if len(target) < 1 {
		return fmt.Errorf("empty target")
	}
	// If it containts a ".", it must end in a ".".
	if strings.ContainsRune(target, '.') && target[len(target)-1] != '.' {
		return fmt.Errorf("target (%v) must end with a (.) [Required if target is not single label]", target)
	}
	return nil
}

// validateRecordTypes list of valid rec.Type values. Returns true if this is a real DNS record type, false means it is a pseudo-type used internally.
func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []string) error {
	var validTypes = map[string]bool{
		"A":                true,
		"AAAA":             true,
		"CNAME":            true,
		"IMPORT_TRANSFORM": false,
		"MX":               true,
		"TXT":              true,
		"NS":               true,
		"ALIAS":            false,
	}
	_, ok := validTypes[rec.Type]
	if !ok {
		cType := providers.GetCustomRecordType(rec.Type)
		if cType == nil {
			return fmt.Errorf("Unsupported record type (%v) domain=%v name=%v", rec.Type, domain, rec.Name)
		}
		for _, providerType := range pTypes {
			if providerType != cType.Provider {
				return fmt.Errorf("Custom record type %s is not compatible with provider type %s", rec.Type, providerType)
			}
		}
		//it is ok. Lets replace the type with real type and add metadata to say we checked it
		rec.Metadata["orig_custom_type"] = rec.Type
		if cType.RealType != "" {
			rec.Type = cType.RealType
		}
	}
	return nil
}

// underscores in names are often used erroneously. They are valid for dns records, but invalid for urls.
// here we list common records expected to have underscores. Anything else containing an underscore will print a warning.
var expectedUnderscores = []string{"_domainkey", "_dmarc"}

func checkLabel(label string, rType string, domain string) error {
	if label == "@" {
		return nil
	}
	if len(label) < 1 {
		return fmt.Errorf("empty %s label in %s", rType, domain)
	}
	if label[len(label)-1] == '.' {
		return fmt.Errorf("label %s.%s ends with a (.)", label, domain)
	}

	//underscores are warnings
	if strings.ContainsRune(label, '_') {
		//unless it is in our exclusion list
		ok := false
		for _, ex := range expectedUnderscores {
			if strings.Contains(label, ex) {
				ok = true
				break
			}
		}
		if !ok {
			return Warning{fmt.Errorf("label %s.%s contains an underscore", label, domain)}
		}
	}
	return nil
}

// checkTargets returns true if rec.Target is valid for the rec.Type.
func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
	label := rec.Name
	target := rec.Target
	check := func(e error) {
		if e != nil {
			err := fmt.Errorf("In %s %s.%s: %s", rec.Type, rec.Name, domain, e.Error())
			if _, ok := e.(Warning); ok {
				err = Warning{err}
			}
			errs = append(errs, err)
		}
	}
	switch rec.Type {
	case "A":
		check(checkIPv4(target))
	case "AAAA":
		check(checkIPv6(target))
	case "CNAME":
		check(checkTarget(target))
		if label == "@" {
			check(fmt.Errorf("cannot create CNAME record for bare domain"))
		}
	case "MX":
		check(checkTarget(target))
	case "NS":
		check(checkTarget(target))
		if label == "@" {
			check(fmt.Errorf("cannot create NS record for bare domain. Use NAMESERVER instead"))
		}
	case "ALIAS":
		check(checkTarget(target))
	case "TXT", "IMPORT_TRANSFORM":
	default:
		if rec.Metadata["orig_custom_type"] != "" {
			//it is a valid custom type. We perform no validation on target
			return
		}
		errs = append(errs, fmt.Errorf("Unimplemented record type (%v) domain=%v name=%v",
			rec.Type, domain, rec.Name))
	}
	return
}

func transformCNAME(target, oldDomain, newDomain string) string {
	// Canonicalize. If it isn't a FQDN, add the newDomain.
	result := dnsutil.AddOrigin(target, oldDomain)
	if dns.IsFqdn(result) {
		result = result[:len(result)-1]
	}
	return dnsutil.AddOrigin(result, newDomain) + "."
}

// import_transform imports the records of one zone into another, modifying records along the way.
func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []transform.IpConversion, ttl uint32) error {
	// Read srcDomain.Records, transform, and append to dstDomain.Records:
	// 1. Skip any that aren't A or CNAMEs.
	// 2. Append destDomainname to the end of the label.
	// 3. For CNAMEs, append destDomainname to the end of the target.
	// 4. For As, change the target as described the transforms.

	for _, rec := range srcDomain.Records {
		if dstDomain.HasRecordTypeName(rec.Type, rec.NameFQDN) {
			continue
		}
		newRec := func() *models.RecordConfig {
			rec2, _ := rec.Copy()
			rec2.Name = rec2.NameFQDN
			rec2.NameFQDN = dnsutil.AddOrigin(rec2.Name, dstDomain.Name)
			if ttl != 0 {
				rec2.TTL = ttl
			}
			return rec2
		}
		switch rec.Type {
		case "A":
			trs, err := transform.TransformIPToList(net.ParseIP(rec.Target), transforms)
			if err != nil {
				return fmt.Errorf("import_transform: TransformIP(%v, %v) returned err=%s", rec.Target, transforms, err)
			}
			for _, tr := range trs {
				r := newRec()
				r.Target = tr.String()
				dstDomain.Records = append(dstDomain.Records, r)
			}
		case "CNAME":
			r := newRec()
			r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name)
			dstDomain.Records = append(dstDomain.Records, r)
		case "MX", "NS", "TXT":
			// Not imported.
			continue
		default:
			return fmt.Errorf("import_transform: Unimplemented record type %v (%v)",
				rec.Type, rec.Name)
		}
	}
	return nil
}

// deleteImportTransformRecords deletes any IMPORT_TRANSFORM records from a domain.
func deleteImportTransformRecords(domain *models.DomainConfig) {
	for i := len(domain.Records) - 1; i >= 0; i-- {
		rec := domain.Records[i]
		if rec.Type == "IMPORT_TRANSFORM" {
			domain.Records = append(domain.Records[:i], domain.Records[i+1:]...)
		}
	}
}

// Warning is a wrapper around error that can be used to indicate it should not
// stop execution, but is still likely a problem.
type Warning struct {
	error
}

func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
	ptypeMap := map[string]string{}
	for _, p := range config.DNSProviders {
		ptypeMap[p.Name] = p.Type
	}

	for _, domain := range config.Domains {
		pTypes := []string{}
		for p := range domain.DNSProviders {
			pType, ok := ptypeMap[p]
			if !ok {
				errs = append(errs, fmt.Errorf("%s uses undefined DNS provider %s", domain.Name, p))
			} else {
				pTypes = append(pTypes, pType)
			}
		}

		// Normalize Nameservers.
		for _, ns := range domain.Nameservers {
			ns.Name = dnsutil.AddOrigin(ns.Name, domain.Name)
			ns.Name = strings.TrimRight(ns.Name, ".")
		}
		// Normalize Records.
		for _, rec := range domain.Records {
			if rec.TTL == 0 {
				rec.TTL = models.DefaultTTL
			}
			// Validate the unmodified inputs:
			if err := validateRecordTypes(rec, domain.Name, pTypes); err != nil {
				errs = append(errs, err)
			}
			if err := checkLabel(rec.Name, rec.Type, domain.Name); err != nil {
				errs = append(errs, err)
			}
			if errs2 := checkTargets(rec, domain.Name); errs2 != nil {
				errs = append(errs, errs2...)
			}

			// Canonicalize Targets.
			if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NS" {
				rec.Target = dnsutil.AddOrigin(rec.Target, domain.Name+".")
			} else if rec.Type == "A" || rec.Type == "AAAA" {
				rec.Target = net.ParseIP(rec.Target).String()
			}
			// Populate FQDN:
			rec.NameFQDN = dnsutil.AddOrigin(rec.Name, domain.Name)
		}
	}

	// Process any pseudo-records:
	for _, domain := range config.Domains {
		for _, rec := range domain.Records {
			if rec.Type == "IMPORT_TRANSFORM" {
				table, err := transform.DecodeTransformTable(rec.Metadata["transform_table"])
				if err != nil {
					errs = append(errs, err)
					continue
				}
				err = importTransform(config.FindDomain(rec.Target), domain, table, rec.TTL)
				if err != nil {
					errs = append(errs, err)
				}
			}
		}
	}
	// Clean up:
	for _, domain := range config.Domains {
		deleteImportTransformRecords(domain)
	}

	// Run record transforms
	for _, domain := range config.Domains {
		if err := applyRecordTransforms(domain); err != nil {
			errs = append(errs, err)
		}
	}

	//Check that CNAMES don't have to co-exist with any other records
	for _, d := range config.Domains {
		errs = append(errs, checkCNAMEs(d)...)
	}

	//Check that if any aliases are used in a domain, every provider for that domain supports them
	for _, d := range config.Domains {
		err := checkALIASes(d, config.DNSProviders)
		if err != nil {
			errs = append(errs, err)
		}
	}

	return errs
}

func checkCNAMEs(dc *models.DomainConfig) (errs []error) {
	cnames := map[string]bool{}
	for _, r := range dc.Records {
		if r.Type == "CNAME" {
			if cnames[r.Name] {
				errs = append(errs, fmt.Errorf("Cannot have multiple CNAMEs with same name: %s", r.NameFQDN))
			}
			cnames[r.Name] = true
		}
	}
	for _, r := range dc.Records {
		if cnames[r.Name] && r.Type != "CNAME" {
			errs = append(errs, fmt.Errorf("Cannot have CNAME and %s record with same name: %s", r.Type, r.NameFQDN))
		}
	}
	return
}

func checkALIASes(dc *models.DomainConfig, pList []*models.DNSProviderConfig) error {
	hasAlias := false
	for _, r := range dc.Records {
		if r.Type == "ALIAS" {
			hasAlias = true
			break
		}
	}
	if !hasAlias {
		return nil
	}
	for pName := range dc.DNSProviders {
		for _, p := range pList {
			if p.Name == pName {
				if !providers.ProviderHasCabability(p.Type, providers.CanUseAlias) {
					return fmt.Errorf("Domain %s uses ALIAS records, but DNS provider type %s does not support them", dc.Name, p.Type)
				}
				break
			}
		}
	}
	return nil
}

func applyRecordTransforms(domain *models.DomainConfig) error {
	for _, rec := range domain.Records {
		if rec.Type != "A" {
			continue
		}
		tt, ok := rec.Metadata["transform"]
		if !ok {
			continue
		}
		table, err := transform.DecodeTransformTable(tt)
		if err != nil {
			return err
		}
		ip := net.ParseIP(rec.Target) //ip already validated above
		newIPs, err := transform.TransformIPToList(net.ParseIP(rec.Target), table)
		if err != nil {
			return err
		}
		for i, newIP := range newIPs {
			if i == 0 && !newIP.Equal(ip) {
				rec.Target = newIP.String() //replace target of first record if different
			} else if i > 0 {
				// any additional ips need identical records with the alternate ip added to the domain
				copy, err := rec.Copy()
				if err != nil {
					return err
				}
				copy.Target = newIP.String()
				domain.Records = append(domain.Records, copy)
			}
		}
	}
	return nil
}
<commit_msg>Added _amazonses to list of allowed name with underscore (#133)<commit_after>package normalize

import (
	"fmt"
	"net"
	"strings"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/StackExchange/dnscontrol/pkg/transform"
	"github.com/StackExchange/dnscontrol/providers"
	"github.com/miekg/dns"
	"github.com/miekg/dns/dnsutil"
)

// Returns false if target does not validate.
func checkIPv4(label string) error {
	if net.ParseIP(label).To4() == nil {
		return fmt.Errorf("WARNING: target (%v) is not an IPv4 address", label)
	}
	return nil
}

// Returns false if target does not validate.
func checkIPv6(label string) error {
	if net.ParseIP(label).To16() == nil {
		return fmt.Errorf("WARNING: target (%v) is not an IPv6 address", label)
	}
	return nil
}

// make sure target is valid reference for cnames, mx, etc.
func checkTarget(target string) error {
	if target == "@" {
		return nil
	}
	if len(target) < 1 {
		return fmt.Errorf("empty target")
	}
	// If it containts a ".", it must end in a ".".
	if strings.ContainsRune(target, '.') && target[len(target)-1] != '.' {
		return fmt.Errorf("target (%v) must end with a (.) [Required if target is not single label]", target)
	}
	return nil
}

// validateRecordTypes list of valid rec.Type values. Returns true if this is a real DNS record type, false means it is a pseudo-type used internally.
func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []string) error {
	var validTypes = map[string]bool{
		"A":                true,
		"AAAA":             true,
		"CNAME":            true,
		"IMPORT_TRANSFORM": false,
		"MX":               true,
		"TXT":              true,
		"NS":               true,
		"ALIAS":            false,
	}
	_, ok := validTypes[rec.Type]
	if !ok {
		cType := providers.GetCustomRecordType(rec.Type)
		if cType == nil {
			return fmt.Errorf("Unsupported record type (%v) domain=%v name=%v", rec.Type, domain, rec.Name)
		}
		for _, providerType := range pTypes {
			if providerType != cType.Provider {
				return fmt.Errorf("Custom record type %s is not compatible with provider type %s", rec.Type, providerType)
			}
		}
		//it is ok. Lets replace the type with real type and add metadata to say we checked it
		rec.Metadata["orig_custom_type"] = rec.Type
		if cType.RealType != "" {
			rec.Type = cType.RealType
		}
	}
	return nil
}

// underscores in names are often used erroneously. They are valid for dns records, but invalid for urls.
// here we list common records expected to have underscores. Anything else containing an underscore will print a warning.
var expectedUnderscores = []string{"_domainkey", "_dmarc", "_amazonses"}

func checkLabel(label string, rType string, domain string) error {
	if label == "@" {
		return nil
	}
	if len(label) < 1 {
		return fmt.Errorf("empty %s label in %s", rType, domain)
	}
	if label[len(label)-1] == '.' {
		return fmt.Errorf("label %s.%s ends with a (.)", label, domain)
	}

	//underscores are warnings
	if strings.ContainsRune(label, '_') {
		//unless it is in our exclusion list
		ok := false
		for _, ex := range expectedUnderscores {
			if strings.Contains(label, ex) {
				ok = true
				break
			}
		}
		if !ok {
			return Warning{fmt.Errorf("label %s.%s contains an underscore", label, domain)}
		}
	}
	return nil
}

// checkTargets returns true if rec.Target is valid for the rec.Type.
func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
	label := rec.Name
	target := rec.Target
	check := func(e error) {
		if e != nil {
			err := fmt.Errorf("In %s %s.%s: %s", rec.Type, rec.Name, domain, e.Error())
			if _, ok := e.(Warning); ok {
				err = Warning{err}
			}
			errs = append(errs, err)
		}
	}
	switch rec.Type {
	case "A":
		check(checkIPv4(target))
	case "AAAA":
		check(checkIPv6(target))
	case "CNAME":
		check(checkTarget(target))
		if label == "@" {
			check(fmt.Errorf("cannot create CNAME record for bare domain"))
		}
	case "MX":
		check(checkTarget(target))
	case "NS":
		check(checkTarget(target))
		if label == "@" {
			check(fmt.Errorf("cannot create NS record for bare domain. Use NAMESERVER instead"))
		}
	case "ALIAS":
		check(checkTarget(target))
	case "TXT", "IMPORT_TRANSFORM":
	default:
		if rec.Metadata["orig_custom_type"] != "" {
			//it is a valid custom type. We perform no validation on target
			return
		}
		errs = append(errs, fmt.Errorf("Unimplemented record type (%v) domain=%v name=%v",
			rec.Type, domain, rec.Name))
	}
	return
}

func transformCNAME(target, oldDomain, newDomain string) string {
	// Canonicalize. If it isn't a FQDN, add the newDomain.
	result := dnsutil.AddOrigin(target, oldDomain)
	if dns.IsFqdn(result) {
		result = result[:len(result)-1]
	}
	return dnsutil.AddOrigin(result, newDomain) + "."
}

// import_transform imports the records of one zone into another, modifying records along the way.
func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []transform.IpConversion, ttl uint32) error {
	// Read srcDomain.Records, transform, and append to dstDomain.Records:
	// 1. Skip any that aren't A or CNAMEs.
	// 2. Append destDomainname to the end of the label.
	// 3. For CNAMEs, append destDomainname to the end of the target.
	// 4. For As, change the target as described the transforms.

	for _, rec := range srcDomain.Records {
		if dstDomain.HasRecordTypeName(rec.Type, rec.NameFQDN) {
			continue
		}
		newRec := func() *models.RecordConfig {
			rec2, _ := rec.Copy()
			rec2.Name = rec2.NameFQDN
			rec2.NameFQDN = dnsutil.AddOrigin(rec2.Name, dstDomain.Name)
			if ttl != 0 {
				rec2.TTL = ttl
			}
			return rec2
		}
		switch rec.Type {
		case "A":
			trs, err := transform.TransformIPToList(net.ParseIP(rec.Target), transforms)
			if err != nil {
				return fmt.Errorf("import_transform: TransformIP(%v, %v) returned err=%s", rec.Target, transforms, err)
			}
			for _, tr := range trs {
				r := newRec()
				r.Target = tr.String()
				dstDomain.Records = append(dstDomain.Records, r)
			}
		case "CNAME":
			r := newRec()
			r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name)
			dstDomain.Records = append(dstDomain.Records, r)
		case "MX", "NS", "TXT":
			// Not imported.
			continue
		default:
			return fmt.Errorf("import_transform: Unimplemented record type %v (%v)",
				rec.Type, rec.Name)
		}
	}
	return nil
}

// deleteImportTransformRecords deletes any IMPORT_TRANSFORM records from a domain.
func deleteImportTransformRecords(domain *models.DomainConfig) {
	for i := len(domain.Records) - 1; i >= 0; i-- {
		rec := domain.Records[i]
		if rec.Type == "IMPORT_TRANSFORM" {
			domain.Records = append(domain.Records[:i], domain.Records[i+1:]...)
		}
	}
}

// Warning is a wrapper around error that can be used to indicate it should not
// stop execution, but is still likely a problem.
type Warning struct {
	error
}

func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
	ptypeMap := map[string]string{}
	for _, p := range config.DNSProviders {
		ptypeMap[p.Name] = p.Type
	}

	for _, domain := range config.Domains {
		pTypes := []string{}
		for p := range domain.DNSProviders {
			pType, ok := ptypeMap[p]
			if !ok {
				errs = append(errs, fmt.Errorf("%s uses undefined DNS provider %s", domain.Name, p))
			} else {
				pTypes = append(pTypes, pType)
			}
		}

		// Normalize Nameservers.
		for _, ns := range domain.Nameservers {
			ns.Name = dnsutil.AddOrigin(ns.Name, domain.Name)
			ns.Name = strings.TrimRight(ns.Name, ".")
		}
		// Normalize Records.
		for _, rec := range domain.Records {
			if rec.TTL == 0 {
				rec.TTL = models.DefaultTTL
			}
			// Validate the unmodified inputs:
			if err := validateRecordTypes(rec, domain.Name, pTypes); err != nil {
				errs = append(errs, err)
			}
			if err := checkLabel(rec.Name, rec.Type, domain.Name); err != nil {
				errs = append(errs, err)
			}
			if errs2 := checkTargets(rec, domain.Name); errs2 != nil {
				errs = append(errs, errs2...)
			}

			// Canonicalize Targets.
			if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NS" {
				rec.Target = dnsutil.AddOrigin(rec.Target, domain.Name+".")
			} else if rec.Type == "A" || rec.Type == "AAAA" {
				rec.Target = net.ParseIP(rec.Target).String()
			}
			// Populate FQDN:
			rec.NameFQDN = dnsutil.AddOrigin(rec.Name, domain.Name)
		}
	}

	// Process any pseudo-records:
	for _, domain := range config.Domains {
		for _, rec := range domain.Records {
			if rec.Type == "IMPORT_TRANSFORM" {
				table, err := transform.DecodeTransformTable(rec.Metadata["transform_table"])
				if err != nil {
					errs = append(errs, err)
					continue
				}
				err = importTransform(config.FindDomain(rec.Target), domain, table, rec.TTL)
				if err != nil {
					errs = append(errs, err)
				}
			}
		}
	}
	// Clean up:
	for _, domain := range config.Domains {
		deleteImportTransformRecords(domain)
	}

	// Run record transforms
	for _, domain := range config.Domains {
		if err := applyRecordTransforms(domain); err != nil {
			errs = append(errs, err)
		}
	}

	//Check that CNAMES don't have to co-exist with any other records
	for _, d := range config.Domains {
		errs = append(errs, checkCNAMEs(d)...)
	}

	//Check that if any aliases are used in a domain, every provider for that domain supports them
	for _, d := range config.Domains {
		err := checkALIASes(d, config.DNSProviders)
		if err != nil {
			errs = append(errs, err)
		}
	}

	return errs
}

func checkCNAMEs(dc *models.DomainConfig) (errs []error) {
	cnames := map[string]bool{}
	for _, r := range dc.Records {
		if r.Type == "CNAME" {
			if cnames[r.Name] {
				errs = append(errs, fmt.Errorf("Cannot have multiple CNAMEs with same name: %s", r.NameFQDN))
			}
			cnames[r.Name] = true
		}
	}
	for _, r := range dc.Records {
		if cnames[r.Name] && r.Type != "CNAME" {
			errs = append(errs, fmt.Errorf("Cannot have CNAME and %s record with same name: %s", r.Type, r.NameFQDN))
		}
	}
	return
}

func checkALIASes(dc *models.DomainConfig, pList []*models.DNSProviderConfig) error {
	hasAlias := false
	for _, r := range dc.Records {
		if r.Type == "ALIAS" {
			hasAlias = true
			break
		}
	}
	if !hasAlias {
		return nil
	}
	for pName := range dc.DNSProviders {
		for _, p := range pList {
			if p.Name == pName {
				if !providers.ProviderHasCabability(p.Type, providers.CanUseAlias) {
					return fmt.Errorf("Domain %s uses ALIAS records, but DNS provider type %s does not support them", dc.Name, p.Type)
				}
				break
			}
		}
	}
	return nil
}

func applyRecordTransforms(domain *models.DomainConfig) error {
	for _, rec := range domain.Records {
		if rec.Type != "A" {
			continue
		}
		tt, ok := rec.Metadata["transform"]
		if !ok {
			continue
		}
		table, err := transform.DecodeTransformTable(tt)
		if err != nil {
			return err
		}
		ip := net.ParseIP(rec.Target) //ip already validated above
		newIPs, err := transform.TransformIPToList(net.ParseIP(rec.Target), table)
		if err != nil {
			return err
		}
		for i, newIP := range newIPs {
			if i == 0 && !newIP.Equal(ip) {
				rec.Target = newIP.String() //replace target of first record if different
			} else if i > 0 {
				// any additional ips need identical records with the alternate ip added to the domain
				copy, err := rec.Copy()
				if err != nil {
					return err
				}
				copy.Target = newIP.String()
				domain.Records = append(domain.Records, copy)
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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

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

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

package options

import (
	"reflect"
	"testing"
)

func TestResourceSetSet(t *testing.T) {
	tests := []struct {
		Desc        string
		Value       string
		Wanted      ResourceSet
		WantedError bool
	}{
		{
			Desc:        "empty resources",
			Value:       "",
			Wanted:      ResourceSet{},
			WantedError: false,
		},
		{
			Desc:  "normal resources",
			Value: "configmaps,cronjobs,daemonsets,deployments",
			Wanted: ResourceSet(map[string]struct{}{
				"configmaps":  {},
				"cronjobs":    {},
				"daemonsets":  {},
				"deployments": {},
			}),
			WantedError: false,
		},
	}

	for _, test := range tests {
		cs := &ResourceSet{}
		gotError := cs.Set(test.Value)
		if !(((gotError == nil && !test.WantedError) || (gotError != nil && test.WantedError)) && reflect.DeepEqual(*cs, test.Wanted)) {
			t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v. Wanted Error: %v, Got Error: %v", test.Desc, test.Wanted, *cs, test.WantedError, gotError)
		}
	}
}

func TestNamespaceListSet(t *testing.T) {
	tests := []struct {
		Desc   string
		Value  string
		Wanted NamespaceList
	}{
		{
			Desc:   "empty namespacelist",
			Value:  "",
			Wanted: NamespaceList{},
		},
		{
			Desc:  "normal namespacelist",
			Value: "default, kube-system",
			Wanted: NamespaceList([]string{
				"default",
				"kube-system",
			}),
		},
	}

	for _, test := range tests {
		ns := &NamespaceList{}
		gotError := ns.Set(test.Value)
		if gotError != nil || !reflect.DeepEqual(*ns, test.Wanted) {
			t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v. Got Error: %v", test.Desc, test.Wanted, *ns, gotError)
		}
	}
}

func TestMetricSetSet(t *testing.T) {
	tests := []struct {
		Desc   string
		Value  string
		Wanted MetricSet
	}{
		{
			Desc:   "empty metrics",
			Value:  "",
			Wanted: MetricSet{},
		},
		{
			Desc:  "normal metrics",
			Value: "kube_cronjob_info, kube_cronjob_labels, kube_daemonset_labels",
			Wanted: MetricSet(map[string]struct{}{
				"kube_cronjob_info":     {},
				"kube_cronjob_labels":   {},
				"kube_daemonset_labels": {},
			}),
		},
	}

	for _, test := range tests {
		ms := &MetricSet{}
		gotError := ms.Set(test.Value)
		if gotError != nil || !reflect.DeepEqual(*ms, test.Wanted) {
			t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v. Got Error: %v", test.Desc, test.Wanted, *ms, gotError)
		}
	}
}
<commit_msg>Assert that newlines in coma separated arguments are ignored<commit_after>/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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

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

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

package options

import (
	"reflect"
	"testing"
)

func TestResourceSetSet(t *testing.T) {
	tests := []struct {
		Desc        string
		Value       string
		Wanted      ResourceSet
		WantedError bool
	}{
		{
			Desc:        "empty resources",
			Value:       "",
			Wanted:      ResourceSet{},
			WantedError: false,
		},
		{
			Desc:  "normal resources",
			Value: "configmaps,cronjobs,daemonsets,deployments",
			Wanted: ResourceSet(map[string]struct{}{
				"configmaps":  {},
				"cronjobs":    {},
				"daemonsets":  {},
				"deployments": {},
			}),
			WantedError: false,
		},
	}

	for _, test := range tests {
		cs := &ResourceSet{}
		gotError := cs.Set(test.Value)
		if !(((gotError == nil && !test.WantedError) || (gotError != nil && test.WantedError)) && reflect.DeepEqual(*cs, test.Wanted)) {
			t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v. Wanted Error: %v, Got Error: %v", test.Desc, test.Wanted, *cs, test.WantedError, gotError)
		}
	}
}

func TestNamespaceListSet(t *testing.T) {
	tests := []struct {
		Desc   string
		Value  string
		Wanted NamespaceList
	}{
		{
			Desc:   "empty namespacelist",
			Value:  "",
			Wanted: NamespaceList{},
		},
		{
			Desc:  "normal namespacelist",
			Value: "default, kube-system",
			Wanted: NamespaceList([]string{
				"default",
				"kube-system",
			}),
		},
	}

	for _, test := range tests {
		ns := &NamespaceList{}
		gotError := ns.Set(test.Value)
		if gotError != nil || !reflect.DeepEqual(*ns, test.Wanted) {
			t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v. Got Error: %v", test.Desc, test.Wanted, *ns, gotError)
		}
	}
}

func TestMetricSetSet(t *testing.T) {
	tests := []struct {
		Desc   string
		Value  string
		Wanted MetricSet
	}{
		{
			Desc:   "empty metrics",
			Value:  "",
			Wanted: MetricSet{},
		},
		{
			Desc:  "normal metrics",
			Value: "kube_cronjob_info, kube_cronjob_labels, kube_daemonset_labels",
			Wanted: MetricSet(map[string]struct{}{
				"kube_cronjob_info":     {},
				"kube_cronjob_labels":   {},
				"kube_daemonset_labels": {},
			}),
		},
		{
			Desc:  "newlines are ignored",
			Value: "\n^kube_.+_annotations$,\n   ^kube_secret_labels$\n",
			Wanted: MetricSet{
				"^kube_secret_labels$":  struct{}{},
				"^kube_.+_annotations$": struct{}{},
			},
		},
	}

	for _, test := range tests {
		ms := &MetricSet{}
		gotError := ms.Set(test.Value)
		if gotError != nil || !reflect.DeepEqual(*ms, test.Wanted) {
			t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v. Got Error: %v", test.Desc, test.Wanted, *ms, gotError)
		}
	}
}
<|endoftext|>


<commit_before>package status

import (
	"sync"

	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	v1 "kubevirt.io/api/core/v1"
	"kubevirt.io/client-go/kubecli"
)

const unknownObj = "Unknown object"

// updater transparently switches for status updates between /status and the main entrypoint for resource,
// allowing CRDs to enable or disable the status subresource support anytime.
type updater struct {
	lock        sync.Mutex
	subresource bool
	cli         kubecli.KubevirtClient
}

func (u *updater) update(obj runtime.Object) (err error) {
	if u.getSubresource() {
		return u.updateWithSubresource(obj)
	} else {
		return u.updateWithoutSubresource(obj)
	}
}

func (u *updater) patch(obj runtime.Object, pt types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (err error) {
	if u.getSubresource() {
		return u.patchWithSubresource(obj, pt, data, patchOptions)
	} else {
		return u.patchWithoutSubresource(obj, pt, data, patchOptions)
	}
}

// updateWithoutSubresource will try to update the  status via PUT sent to the main REST endpoint.
// If status of the returned object did not change, it knows that it should have used the /status subresource
// and will switch the updater itself over to permanently use the /status subresource.
func (u *updater) updateWithoutSubresource(obj runtime.Object) (err error) {
	oldStatus, newStatus, err := u.updateUnstructured(obj)
	if err != nil {
		return err
	}
	if !equality.Semantic.DeepEqual(oldStatus, newStatus) {
		u.setSubresource(true)
		return u.updateStatusUnstructured(obj)
	}
	return nil
}

// updateWithSubresource will try to update the  status via PUT sent to the /status subresource.
// If a 404 error is returned, it will try the main rest entrypoint instead. In case that this
// call succeeds, it will switch the updater to permanently use the main entrypoint.
func (u *updater) updateWithSubresource(obj runtime.Object) (updateStatusErr error) {
	updateStatusErr = u.updateStatusUnstructured(obj)
	if updateStatusErr != nil && !errors.IsNotFound(updateStatusErr) {
		return updateStatusErr
	}
	if errors.IsNotFound(updateStatusErr) {
		oldStatus, newStatus, err := u.updateUnstructured(obj)
		if errors.IsNotFound(err) {
			// object does not exist
			return err
		}
		if err == nil && equality.Semantic.DeepEqual(oldStatus, newStatus) {
			u.setSubresource(false)
		} else if err == nil {
			return updateStatusErr
		}
		return err
	} else {
		return updateStatusErr
	}
}

// patchWithoutSubresource will try to update the  status via PATCH sent to the main REST endpoint.
// If the resource version of the returned object did not change, it knows that it should have used the /status subresource
// and will switch the updater itself over to permanently use the /status subresource.
func (u *updater) patchWithoutSubresource(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (err error) {
	oldResourceVersion, newResourceVersion, err := u.patchUnstructured(obj, patchType, data, patchOptions)
	if err != nil {
		return err
	}
	if oldResourceVersion == newResourceVersion {
		u.setSubresource(true)
		return u.patchStatusUnstructured(obj, patchType, data, patchOptions)
	}
	return nil
}

// patchWithSubresource will try to update the  status via PATCH sent to the /status subresource.
// If a 404 error is returned, it will try the main rest entrypoint instead. In case that this
// call succeeds, it will switch the updater to permanently use the main entrypoint.
func (u *updater) patchWithSubresource(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (patchStatusErr error) {
	patchStatusErr = u.patchStatusUnstructured(obj, patchType, data, patchOptions)
	if patchStatusErr != nil && !errors.IsNotFound(patchStatusErr) {
		return patchStatusErr
	}
	if errors.IsNotFound(patchStatusErr) {
		oldResourceVersion, newResourceVersions, err := u.patchUnstructured(obj, patchType, data, patchOptions)
		if errors.IsNotFound(err) {
			// object does not exist
			return err
		}
		if err == nil && oldResourceVersion != newResourceVersions {
			u.setSubresource(false)
		} else if err == nil {
			return patchStatusErr
		}
		return err
	} else {
		return patchStatusErr
	}
}

func (u *updater) patchUnstructured(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (oldResourceVersion, newResourceVerions string, err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return "", "", err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		oldObj := obj.(*v1.VirtualMachine)
		newObj, err := u.cli.VirtualMachine(a.GetNamespace()).Patch(a.GetName(), patchType, data, patchOptions)
		if err != nil {
			return "", "", err
		}
		return oldObj.ResourceVersion, newObj.ResourceVersion, nil
	case *v1.KubeVirt:
		oldObj := obj.(*v1.KubeVirt)
		newObj, err := u.cli.KubeVirt(a.GetNamespace()).Patch(a.GetName(), patchType, data, patchOptions)
		if err != nil {
			return "", "", err
		}
		return oldObj.ResourceVersion, newObj.ResourceVersion, nil
	default:
		panic(unknownObj)
	}
}

func (u *updater) patchStatusUnstructured(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		_, err = u.cli.VirtualMachine(a.GetNamespace()).PatchStatus(a.GetName(), patchType, data, patchOptions)
		return err
	case *v1.KubeVirt:
		_, err = u.cli.KubeVirt(a.GetNamespace()).PatchStatus(a.GetName(), patchType, data, patchOptions)
		return err
	default:
		panic(unknownObj)
	}
}

func (u *updater) updateUnstructured(obj runtime.Object) (oldStatus interface{}, newStatus interface{}, err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return nil, nil, err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		oldObj := obj.(*v1.VirtualMachine)
		newObj, err := u.cli.VirtualMachine(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	case *v1.VirtualMachineInstanceReplicaSet:
		oldObj := obj.(*v1.VirtualMachineInstanceReplicaSet)
		newObj, err := u.cli.ReplicaSet(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	case *v1.VirtualMachineInstanceMigration:
		oldObj := obj.(*v1.VirtualMachineInstanceMigration)
		newObj, err := u.cli.VirtualMachineInstanceMigration(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	case *v1.KubeVirt:
		oldObj := obj.(*v1.KubeVirt)
		newObj, err := u.cli.KubeVirt(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	default:
		panic(unknownObj)
	}
}

func (u *updater) updateStatusUnstructured(obj runtime.Object) (err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		oldObj := obj.(*v1.VirtualMachine)
		_, err = u.cli.VirtualMachine(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	case *v1.VirtualMachineInstanceReplicaSet:
		oldObj := obj.(*v1.VirtualMachineInstanceReplicaSet)
		_, err = u.cli.ReplicaSet(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	case *v1.VirtualMachineInstanceMigration:
		oldObj := obj.(*v1.VirtualMachineInstanceMigration)
		_, err = u.cli.VirtualMachineInstanceMigration(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	case *v1.KubeVirt:
		oldObj := obj.(*v1.KubeVirt)
		_, err = u.cli.KubeVirt(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	default:
		panic(unknownObj)
	}
}

func (u *updater) setSubresource(exists bool) {
	u.lock.Lock()
	defer u.lock.Unlock()
	u.subresource = exists
}

func (u *updater) getSubresource() bool {
	u.lock.Lock()
	defer u.lock.Unlock()
	return u.subresource
}

type VMStatusUpdater struct {
	updater updater
}

func (v *VMStatusUpdater) UpdateStatus(vm *v1.VirtualMachine) error {
	return v.updater.update(vm)
}

func (v *VMStatusUpdater) PatchStatus(vm *v1.VirtualMachine, pt types.PatchType, data []byte, patchOptions *metav1.PatchOptions) error {
	return v.updater.patch(vm, pt, data, patchOptions)
}

func NewVMStatusUpdater(cli kubecli.KubevirtClient) *VMStatusUpdater {
	return &VMStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}

type VMIRSStatusUpdater struct {
	updater updater
}

func (v *VMIRSStatusUpdater) UpdateStatus(vmirs *v1.VirtualMachineInstanceReplicaSet) error {
	return v.updater.update(vmirs)
}

func NewVMIRSStatusUpdater(cli kubecli.KubevirtClient) *VMIRSStatusUpdater {
	return &VMIRSStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}

type KVStatusUpdater struct {
	updater updater
}

func (v *KVStatusUpdater) UpdateStatus(kv *v1.KubeVirt) error {
	return v.updater.update(kv)
}

func (v *KVStatusUpdater) PatchStatus(kv *v1.KubeVirt, pt types.PatchType, data []byte) error {
	return v.updater.patch(kv, pt, data, &metav1.PatchOptions{})
}

func NewKubeVirtStatusUpdater(cli kubecli.KubevirtClient) *KVStatusUpdater {
	return &KVStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}

type MigrationStatusUpdater struct {
	updater updater
}

func (v *MigrationStatusUpdater) UpdateStatus(migration *v1.VirtualMachineInstanceMigration) error {
	return v.updater.update(migration)
}

func NewMigrationStatusUpdater(cli kubecli.KubevirtClient) *MigrationStatusUpdater {
	return &MigrationStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}
<commit_msg>Remove redundant IsNotFound calls<commit_after>package status

import (
	"sync"

	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	v1 "kubevirt.io/api/core/v1"
	"kubevirt.io/client-go/kubecli"
)

const unknownObj = "Unknown object"

// updater transparently switches for status updates between /status and the main entrypoint for resource,
// allowing CRDs to enable or disable the status subresource support anytime.
type updater struct {
	lock        sync.Mutex
	subresource bool
	cli         kubecli.KubevirtClient
}

func (u *updater) update(obj runtime.Object) (err error) {
	if u.getSubresource() {
		return u.updateWithSubresource(obj)
	} else {
		return u.updateWithoutSubresource(obj)
	}
}

func (u *updater) patch(obj runtime.Object, pt types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (err error) {
	if u.getSubresource() {
		return u.patchWithSubresource(obj, pt, data, patchOptions)
	} else {
		return u.patchWithoutSubresource(obj, pt, data, patchOptions)
	}
}

// updateWithoutSubresource will try to update the  status via PUT sent to the main REST endpoint.
// If status of the returned object did not change, it knows that it should have used the /status subresource
// and will switch the updater itself over to permanently use the /status subresource.
func (u *updater) updateWithoutSubresource(obj runtime.Object) (err error) {
	oldStatus, newStatus, err := u.updateUnstructured(obj)
	if err != nil {
		return err
	}
	if !equality.Semantic.DeepEqual(oldStatus, newStatus) {
		u.setSubresource(true)
		return u.updateStatusUnstructured(obj)
	}
	return nil
}

// updateWithSubresource will try to update the  status via PUT sent to the /status subresource.
// If a 404 error is returned, it will try the main rest entrypoint instead. In case that this
// call succeeds, it will switch the updater to permanently use the main entrypoint.
func (u *updater) updateWithSubresource(obj runtime.Object) (updateStatusErr error) {
	updateStatusErr = u.updateStatusUnstructured(obj)
	if !errors.IsNotFound(updateStatusErr) {
		return updateStatusErr
	}
	oldStatus, newStatus, err := u.updateUnstructured(obj)
	if err != nil {
		return err
	}
	if !equality.Semantic.DeepEqual(oldStatus, newStatus) {
		return updateStatusErr
	}
	u.setSubresource(false)
	return nil
}

// patchWithoutSubresource will try to update the  status via PATCH sent to the main REST endpoint.
// If the resource version of the returned object did not change, it knows that it should have used the /status subresource
// and will switch the updater itself over to permanently use the /status subresource.
func (u *updater) patchWithoutSubresource(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (err error) {
	oldResourceVersion, newResourceVersion, err := u.patchUnstructured(obj, patchType, data, patchOptions)
	if err != nil {
		return err
	}
	if oldResourceVersion == newResourceVersion {
		u.setSubresource(true)
		return u.patchStatusUnstructured(obj, patchType, data, patchOptions)
	}
	return nil
}

// patchWithSubresource will try to update the  status via PATCH sent to the /status subresource.
// If a 404 error is returned, it will try the main rest entrypoint instead. In case that this
// call succeeds, it will switch the updater to permanently use the main entrypoint.
func (u *updater) patchWithSubresource(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (patchStatusErr error) {
	patchStatusErr = u.patchStatusUnstructured(obj, patchType, data, patchOptions)
	if !errors.IsNotFound(patchStatusErr) {
		return patchStatusErr
	}
	oldResourceVersion, newResourceVersions, err := u.patchUnstructured(obj, patchType, data, patchOptions)
	if err != nil {
		return err
	}
	if oldResourceVersion == newResourceVersions {
		return patchStatusErr
	}
	u.setSubresource(false)
	return nil
}

func (u *updater) patchUnstructured(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (oldResourceVersion, newResourceVerions string, err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return "", "", err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		oldObj := obj.(*v1.VirtualMachine)
		newObj, err := u.cli.VirtualMachine(a.GetNamespace()).Patch(a.GetName(), patchType, data, patchOptions)
		if err != nil {
			return "", "", err
		}
		return oldObj.ResourceVersion, newObj.ResourceVersion, nil
	case *v1.KubeVirt:
		oldObj := obj.(*v1.KubeVirt)
		newObj, err := u.cli.KubeVirt(a.GetNamespace()).Patch(a.GetName(), patchType, data, patchOptions)
		if err != nil {
			return "", "", err
		}
		return oldObj.ResourceVersion, newObj.ResourceVersion, nil
	default:
		panic(unknownObj)
	}
}

func (u *updater) patchStatusUnstructured(obj runtime.Object, patchType types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		_, err = u.cli.VirtualMachine(a.GetNamespace()).PatchStatus(a.GetName(), patchType, data, patchOptions)
		return err
	case *v1.KubeVirt:
		_, err = u.cli.KubeVirt(a.GetNamespace()).PatchStatus(a.GetName(), patchType, data, patchOptions)
		return err
	default:
		panic(unknownObj)
	}
}

func (u *updater) updateUnstructured(obj runtime.Object) (oldStatus interface{}, newStatus interface{}, err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return nil, nil, err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		oldObj := obj.(*v1.VirtualMachine)
		newObj, err := u.cli.VirtualMachine(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	case *v1.VirtualMachineInstanceReplicaSet:
		oldObj := obj.(*v1.VirtualMachineInstanceReplicaSet)
		newObj, err := u.cli.ReplicaSet(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	case *v1.VirtualMachineInstanceMigration:
		oldObj := obj.(*v1.VirtualMachineInstanceMigration)
		newObj, err := u.cli.VirtualMachineInstanceMigration(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	case *v1.KubeVirt:
		oldObj := obj.(*v1.KubeVirt)
		newObj, err := u.cli.KubeVirt(a.GetNamespace()).Update(oldObj)
		if err != nil {
			return nil, nil, err
		}
		return oldObj.Status, newObj.Status, nil
	default:
		panic(unknownObj)
	}
}

func (u *updater) updateStatusUnstructured(obj runtime.Object) (err error) {
	a, err := meta.Accessor(obj)
	if err != nil {
		return err
	}
	switch obj.(type) {
	case *v1.VirtualMachine:
		oldObj := obj.(*v1.VirtualMachine)
		_, err = u.cli.VirtualMachine(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	case *v1.VirtualMachineInstanceReplicaSet:
		oldObj := obj.(*v1.VirtualMachineInstanceReplicaSet)
		_, err = u.cli.ReplicaSet(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	case *v1.VirtualMachineInstanceMigration:
		oldObj := obj.(*v1.VirtualMachineInstanceMigration)
		_, err = u.cli.VirtualMachineInstanceMigration(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	case *v1.KubeVirt:
		oldObj := obj.(*v1.KubeVirt)
		_, err = u.cli.KubeVirt(a.GetNamespace()).UpdateStatus(oldObj)
		return err
	default:
		panic(unknownObj)
	}
}

func (u *updater) setSubresource(exists bool) {
	u.lock.Lock()
	defer u.lock.Unlock()
	u.subresource = exists
}

func (u *updater) getSubresource() bool {
	u.lock.Lock()
	defer u.lock.Unlock()
	return u.subresource
}

type VMStatusUpdater struct {
	updater updater
}

func (v *VMStatusUpdater) UpdateStatus(vm *v1.VirtualMachine) error {
	return v.updater.update(vm)
}

func (v *VMStatusUpdater) PatchStatus(vm *v1.VirtualMachine, pt types.PatchType, data []byte, patchOptions *metav1.PatchOptions) error {
	return v.updater.patch(vm, pt, data, patchOptions)
}

func NewVMStatusUpdater(cli kubecli.KubevirtClient) *VMStatusUpdater {
	return &VMStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}

type VMIRSStatusUpdater struct {
	updater updater
}

func (v *VMIRSStatusUpdater) UpdateStatus(vmirs *v1.VirtualMachineInstanceReplicaSet) error {
	return v.updater.update(vmirs)
}

func NewVMIRSStatusUpdater(cli kubecli.KubevirtClient) *VMIRSStatusUpdater {
	return &VMIRSStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}

type KVStatusUpdater struct {
	updater updater
}

func (v *KVStatusUpdater) UpdateStatus(kv *v1.KubeVirt) error {
	return v.updater.update(kv)
}

func (v *KVStatusUpdater) PatchStatus(kv *v1.KubeVirt, pt types.PatchType, data []byte) error {
	return v.updater.patch(kv, pt, data, &metav1.PatchOptions{})
}

func NewKubeVirtStatusUpdater(cli kubecli.KubevirtClient) *KVStatusUpdater {
	return &KVStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}

type MigrationStatusUpdater struct {
	updater updater
}

func (v *MigrationStatusUpdater) UpdateStatus(migration *v1.VirtualMachineInstanceMigration) error {
	return v.updater.update(migration)
}

func NewMigrationStatusUpdater(cli kubecli.KubevirtClient) *MigrationStatusUpdater {
	return &MigrationStatusUpdater{
		updater: updater{
			lock:        sync.Mutex{},
			subresource: true,
			cli:         cli,
		},
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"strings"

	"flag"

	"github.com/mitchellh/cli"
	"github.com/tleyden/awsutil"
)

func main() {

	c := cli.NewCLI("awsutil", "1.0.0")
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"cloudformation stop-instances": func() (cli.Command, error) {
			return &cloudformationStopInstancesCommand{}, nil
		},
	}

	exitStatus, err := c.Run()
	if err != nil {
		log.Println(err)
	}

	os.Exit(exitStatus)
}

type cloudformationStopInstancesCommand struct {
	args []string
}

func (c *cloudformationStopInstancesCommand) Run(args []string) int {

	// Parse args + get the name of the stack
	cmdFlags := flag.NewFlagSet("cloudformation", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Help() }
	stackname := cmdFlags.String("stackname", "", "")
	region := cmdFlags.String("region", "", "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if *region == "" {
		log.Fatalf("You must pass an AWS region")
		return 1
	}

	if *stackname == "" {
		log.Fatalf("You must pass a CloudFormation stack name")
		return 1
	}

	// create a session
	cfnUtil, err := awsutil.NewCloudformationUtilFromRegion(*region)
	if err != nil {
		log.Fatalf("Error creating cnf: %v", err)
		return 1
	}

	// Stop all instances in stack
	err = cfnUtil.StopEC2Instances(*stackname)
	if err != nil {
		log.Fatalf("Error stopping instances for stack: %v.  Err: %v", *stackname, err)
		return 1
	}

	log.Printf("Stopped all instances in stack: %v", *stackname)
	return 0

}

func (c *cloudformationStopInstancesCommand) Synopsis() string {
	return "Stop all instances in given cloudformation stack"
}

func (c *cloudformationStopInstancesCommand) Help() string {

	helpText := `
Usage: cloudformation stop-instances [options]

  Starts the Consul agent and runs until an interrupt is received. The
  agent represents a single node in a cluster.

Options:

  -stack=stackname          The name of the cloudformation stack
  -region=region            The AWS region

 `
	return strings.TrimSpace(helpText)

}
<commit_msg>Fix help text<commit_after>package main

import (
	"log"
	"os"
	"strings"

	"flag"

	"github.com/mitchellh/cli"
	"github.com/tleyden/awsutil"
)

func main() {

	c := cli.NewCLI("awsutil", "1.0.0")
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"cloudformation stop-instances": func() (cli.Command, error) {
			return &cloudformationStopInstancesCommand{}, nil
		},
	}

	exitStatus, err := c.Run()
	if err != nil {
		log.Println(err)
	}

	os.Exit(exitStatus)
}

type cloudformationStopInstancesCommand struct {
	args []string
}

func (c *cloudformationStopInstancesCommand) Run(args []string) int {

	// Parse args + get the name of the stack
	cmdFlags := flag.NewFlagSet("cloudformation", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Help() }
	stackname := cmdFlags.String("stackname", "", "")
	region := cmdFlags.String("region", "", "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if *region == "" {
		log.Fatalf("You must pass an AWS region")
		return 1
	}

	if *stackname == "" {
		log.Fatalf("You must pass a CloudFormation stack name")
		return 1
	}

	// create a session
	cfnUtil, err := awsutil.NewCloudformationUtilFromRegion(*region)
	if err != nil {
		log.Fatalf("Error creating cnf: %v", err)
		return 1
	}

	// Stop all instances in stack
	err = cfnUtil.StopEC2Instances(*stackname)
	if err != nil {
		log.Fatalf("Error stopping instances for stack: %v.  Err: %v", *stackname, err)
		return 1
	}

	log.Printf("Stopped all instances in stack: %v", *stackname)
	return 0

}

func (c *cloudformationStopInstancesCommand) Synopsis() string {
	return "Stop all instances in given cloudformation stack"
}

func (c *cloudformationStopInstancesCommand) Help() string {

	helpText := `
Usage: cloudformation stop-instances [options]

  Stops all instances in the given cloudformation stack

Options:

  -stackname=stackname      The name of the cloudformation stack
  -region=region            The AWS region

 `
	return strings.TrimSpace(helpText)

}
<|endoftext|>