diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index 59b86209d65..e57398c4e14 100644 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -1046,11 +1046,23 @@ run_kubectl_run_tests() { # Pre-Condition: no Deployment exists kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" '' # Command - kubectl run nginx "--image=$IMAGE_NGINX" --generator=deployment/v1beta1 "${kube_flags[@]}" + kubectl run nginx-extensions "--image=$IMAGE_NGINX" "${kube_flags[@]}" # Post-Condition: Deployment "nginx" is created - kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx:' + kube::test::get_object_assert deployment.extensions "{{range.items}}{{$id_field}}:{{end}}" 'nginx-extensions:' + # and old generator was used, iow. old defaults are applied + output_message=$(kubectl get deployment.extensions/nginx-extensions -o jsonpath='{.spec.revisionHistoryLimit}') + kube::test::if_has_not_string "${output_message}" '2' # Clean up - kubectl delete deployment nginx "${kube_flags[@]}" + kubectl delete deployment nginx-extensions "${kube_flags[@]}" + # Command + kubectl run nginx-apps "--image=$IMAGE_NGINX" --generator=deployment/apps.v1beta1 "${kube_flags[@]}" + # Post-Condition: Deployment "nginx" is created + kube::test::get_object_assert deployment.apps "{{range.items}}{{$id_field}}:{{end}}" 'nginx-apps:' + # and new generator was used, iow. new defaults are applied + output_message=$(kubectl get deployment/nginx-apps -o jsonpath='{.spec.revisionHistoryLimit}') + kube::test::if_has_string "${output_message}" '2' + # Clean up + kubectl delete deployment nginx-apps "${kube_flags[@]}" } run_kubectl_get_tests() { @@ -2283,17 +2295,35 @@ run_rc_tests() { } run_deployment_tests() { - # Test kubectl create deployment - kubectl create deployment test-nginx --image=gcr.io/google-containers/nginx:test-cmd - # Post-Condition: Deployment has 2 replicas defined in its spec. - kube::test::get_object_assert 'deploy test-nginx' "{{$container_name_field}}" 'nginx' + # Test kubectl create deployment (using default - old generator) + kubectl create deployment test-nginx-extensions --image=gcr.io/google-containers/nginx:test-cmd + # Post-Condition: Deployment "nginx" is created. + kube::test::get_object_assert 'deploy test-nginx-extensions' "{{$container_name_field}}" 'nginx' + # and old generator was used, iow. old defaults are applied + output_message=$(kubectl get deployment.extensions/test-nginx-extensions -o jsonpath='{.spec.revisionHistoryLimit}') + kube::test::if_has_not_string "${output_message}" '2' # Ensure we can interact with deployments through extensions and apps endpoints output_message=$(kubectl get deployment.extensions -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") kube::test::if_has_string "${output_message}" 'extensions/v1beta1' output_message=$(kubectl get deployment.apps -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") kube::test::if_has_string "${output_message}" 'apps/v1beta1' # Clean up - kubectl delete deployment test-nginx "${kube_flags[@]}" + kubectl delete deployment test-nginx-extensions "${kube_flags[@]}" + + # Test kubectl create deployment + kubectl create deployment test-nginx-apps --image=gcr.io/google-containers/nginx:test-cmd --generator=deployment-basic/apps.v1beta1 + # Post-Condition: Deployment "nginx" is created. + kube::test::get_object_assert 'deploy test-nginx-apps' "{{$container_name_field}}" 'nginx' + # and new generator was used, iow. new defaults are applied + output_message=$(kubectl get deployment/test-nginx-apps -o jsonpath='{.spec.revisionHistoryLimit}') + kube::test::if_has_string "${output_message}" '2' + # Ensure we can interact with deployments through extensions and apps endpoints + output_message=$(kubectl get deployment.extensions -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" 'extensions/v1beta1' + output_message=$(kubectl get deployment.apps -o=jsonpath='{.items[0].apiVersion}' 2>&1 "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" 'apps/v1beta1' + # Clean up + kubectl delete deployment test-nginx-apps "${kube_flags[@]}" ### Test cascading deletion ## Test that rs is deleted when deployment is deleted. diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 98923897872..9138c980c93 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -53,8 +53,11 @@ go_library( "//pkg/api/util:go_default_library", "//pkg/api/v1:go_default_library", "//pkg/apis/apps:go_default_library", + "//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/batch/v1:go_default_library", + "//pkg/apis/batch/v2alpha1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/policy:go_default_library", @@ -135,8 +138,12 @@ go_test( "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/api/v1:go_default_library", + "//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/batch/v1:go_default_library", + "//pkg/apis/batch/v2alpha1:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 87d5f084fa5..2223c6ef3d7 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -70,7 +70,9 @@ go_library( "//pkg/api/annotations:go_default_library", "//pkg/api/v1:go_default_library", "//pkg/api/validation:go_default_library", + "//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/batch/v1:go_default_library", + "//pkg/apis/batch/v2alpha1:go_default_library", "//pkg/apis/certificates:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/policy:go_default_library", diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 17d9089c536..e4da50b9468 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -94,7 +94,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { cmd.AddCommand(NewCmdCreateConfigMap(f, out)) cmd.AddCommand(NewCmdCreateServiceAccount(f, out)) cmd.AddCommand(NewCmdCreateService(f, out, errOut)) - cmd.AddCommand(NewCmdCreateDeployment(f, out)) + cmd.AddCommand(NewCmdCreateDeployment(f, out, errOut)) cmd.AddCommand(NewCmdCreateClusterRole(f, out)) cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, out)) cmd.AddCommand(NewCmdCreateRole(f, out)) diff --git a/pkg/kubectl/cmd/create_deployment.go b/pkg/kubectl/cmd/create_deployment.go index 179bdef38c2..fe3c434d998 100644 --- a/pkg/kubectl/cmd/create_deployment.go +++ b/pkg/kubectl/cmd/create_deployment.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -38,7 +39,7 @@ var ( ) // NewCmdCreateDeployment is a macro command to create a new deployment -func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { +func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "deployment NAME --image=image [--dry-run]", Aliases: []string{"deploy"}, @@ -46,7 +47,7 @@ func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command Long: deploymentLong, Example: deploymentExample, Run: func(cmd *cobra.Command, args []string) { - err := CreateDeployment(f, cmdOut, cmd, args) + err := CreateDeployment(f, cmdOut, cmdErr, cmd, args) cmdutil.CheckErr(err) }, } @@ -60,13 +61,33 @@ func NewCmdCreateDeployment(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command } // CreateDeployment implements the behavior to run the create deployment command -func CreateDeployment(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { +func CreateDeployment(f cmdutil.Factory, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { return err } + + clientset, err := f.ClientSet() + if err != nil { + return err + } + resourcesList, err := clientset.Discovery().ServerResources() + // ServerResources ignores errors for old servers do not expose discovery + if err != nil { + return fmt.Errorf("failed to discover supported resources: %v", err) + } + generatorName := cmdutil.GetFlagString(cmd, "generator") + // fallback to the old generator if server does not support apps/v1beta1 deployments + if generatorName == cmdutil.DeploymentBasicAppsV1Beta1GeneratorName && + !contains(resourcesList, appsv1beta1.SchemeGroupVersion.WithResource("deployments")) { + fmt.Fprintf(cmdErr, "WARNING: New deployments generator specified (%s), but apps/v1beta1.Deployments are not available, falling back to the old one (%s).\n", + cmdutil.DeploymentBasicAppsV1Beta1GeneratorName, cmdutil.DeploymentBasicV1Beta1GeneratorName) + generatorName = cmdutil.DeploymentBasicV1Beta1GeneratorName + } var generator kubectl.StructuredGenerator - switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + switch generatorName { + case cmdutil.DeploymentBasicAppsV1Beta1GeneratorName: + generator = &kubectl.DeploymentBasicAppsGeneratorV1{Name: name, Images: cmdutil.GetFlagStringSlice(cmd, "image")} case cmdutil.DeploymentBasicV1Beta1GeneratorName: generator = &kubectl.DeploymentBasicGeneratorV1{Name: name, Images: cmdutil.GetFlagStringSlice(cmd, "image")} default: diff --git a/pkg/kubectl/cmd/create_deployment_test.go b/pkg/kubectl/cmd/create_deployment_test.go index 026d0f55716..067f7e650f1 100644 --- a/pkg/kubectl/cmd/create_deployment_test.go +++ b/pkg/kubectl/cmd/create_deployment_test.go @@ -18,20 +18,37 @@ package cmd import ( "bytes" + "io/ioutil" + "net/http" "testing" "github.com/stretchr/testify/assert" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/rest/fake" + "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) func TestCreateDeployment(t *testing.T) { depName := "jonny-dep" - f, tf, _, _ := cmdtesting.NewAPIFactory() + f, tf, _, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + APIRegistry: api.Registry, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(&bytes.Buffer{}), + }, nil + }), + } + tf.ClientConfig = &restclient.Config{} tf.Printer = &testPrinter{} tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdCreateDeployment(f, buf) + + cmd := NewCmdCreateDeployment(f, buf, buf) cmd.Flags().Set("dry-run", "true") cmd.Flags().Set("output", "name") cmd.Flags().Set("image", "hollywood/jonny.depp:v2") @@ -44,13 +61,25 @@ func TestCreateDeployment(t *testing.T) { func TestCreateDeploymentNoImage(t *testing.T) { depName := "jonny-dep" - f, tf, _, _ := cmdtesting.NewAPIFactory() + f, tf, _, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + APIRegistry: api.Registry, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(&bytes.Buffer{}), + }, nil + }), + } + tf.ClientConfig = &restclient.Config{} tf.Printer = &testPrinter{} tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdCreateDeployment(f, buf) + cmd := NewCmdCreateDeployment(f, buf, buf) cmd.Flags().Set("dry-run", "true") cmd.Flags().Set("output", "name") - err := CreateDeployment(f, buf, cmd, []string{depName}) + err := CreateDeployment(f, buf, buf, cmd, []string{depName}) assert.Error(t, err, "at least one image must be specified") } diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 01e4a01ceb5..4c7a7609c65 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -33,8 +33,10 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" "k8s.io/kubernetes/pkg/api" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" - "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + batchv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" conditions "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" @@ -196,38 +198,55 @@ func Run(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobr return err } + clientset, err := f.ClientSet() + if err != nil { + return err + } + resourcesList, err := clientset.Discovery().ServerResources() + // ServerResources ignores errors for old servers do not expose discovery + if err != nil { + return fmt.Errorf("failed to discover supported resources: %v", err) + } + generatorName := cmdutil.GetFlagString(cmd, "generator") schedule := cmdutil.GetFlagString(cmd, "schedule") if len(schedule) != 0 && len(generatorName) == 0 { - generatorName = "cronjob/v2alpha1" + generatorName = cmdutil.CronJobV2Alpha1GeneratorName } if len(generatorName) == 0 { - clientset, err := f.ClientSet() - if err != nil { - return err - } - resourcesList, err := clientset.Discovery().ServerResources() - // ServerResources ignores errors for old servers do not expose discovery - if err != nil { - return fmt.Errorf("failed to discover supported resources: %v", err) - } switch restartPolicy { case api.RestartPolicyAlways: - if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("deployments")) { - generatorName = "deployment/v1beta1" + // TODO: we need to deprecate this along with extensions/v1beta1.Deployments + // in favor of the new generator for apps/v1beta1.Deployments + if contains(resourcesList, extensionsv1beta1.SchemeGroupVersion.WithResource("deployments")) { + generatorName = cmdutil.DeploymentV1Beta1GeneratorName } else { - generatorName = "run/v1" + generatorName = cmdutil.RunV1GeneratorName } case api.RestartPolicyOnFailure: if contains(resourcesList, batchv1.SchemeGroupVersion.WithResource("jobs")) { - generatorName = "job/v1" + generatorName = cmdutil.JobV1GeneratorName } else { - generatorName = "run-pod/v1" + generatorName = cmdutil.RunPodV1GeneratorName } case api.RestartPolicyNever: - generatorName = "run-pod/v1" + generatorName = cmdutil.RunPodV1GeneratorName } } + + // TODO: this should be removed alongside with extensions/v1beta1 depployments generator + if generatorName == cmdutil.DeploymentAppsV1Beta1GeneratorName && + !contains(resourcesList, appsv1beta1.SchemeGroupVersion.WithResource("deployments")) { + fmt.Fprintf(cmdErr, "WARNING: New deployments generator specified (%s), but apps/v1beta1.Deployments are not available, falling back to the old one (%s).\n", + cmdutil.DeploymentAppsV1Beta1GeneratorName, cmdutil.DeploymentV1Beta1GeneratorName) + generatorName = cmdutil.DeploymentV1Beta1GeneratorName + } + + if generatorName == cmdutil.CronJobV2Alpha1GeneratorName && + !contains(resourcesList, batchv2alpha1.SchemeGroupVersion.WithResource("cronjobs")) { + return fmt.Errorf("CronJob generator specified, but batch/v2alpha1.CronJobs are not available") + } + generators := f.Generators("run") generator, found := generators[generatorName] if !found { diff --git a/pkg/kubectl/cmd/run_test.go b/pkg/kubectl/cmd/run_test.go index 4af6c35454c..eff0e8864c5 100644 --- a/pkg/kubectl/cmd/run_test.go +++ b/pkg/kubectl/cmd/run_test.go @@ -35,6 +35,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -113,7 +114,13 @@ func TestGetEnv(t *testing.T) { } func TestRunArgsFollowDashRules(t *testing.T) { - _, _, rc := testData() + one := int32(1) + rc := &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"}, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, + }, + } tests := []struct { args []string @@ -158,7 +165,14 @@ func TestRunArgsFollowDashRules(t *testing.T) { APIRegistry: api.Registry, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil + if req.URL.Path == "/namespaces/test/replicationcontrollers" { + return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, rc)}, nil + } else { + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(&bytes.Buffer{}), + }, nil + } }), } tf.Namespace = "test" diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index 6338868927c..5d435ab270e 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -456,31 +456,33 @@ func (f *ring0Factory) DefaultNamespace() (string, bool, error) { } const ( - RunV1GeneratorName = "run/v1" - RunPodV1GeneratorName = "run-pod/v1" - ServiceV1GeneratorName = "service/v1" - ServiceV2GeneratorName = "service/v2" - ServiceNodePortGeneratorV1Name = "service-nodeport/v1" - ServiceClusterIPGeneratorV1Name = "service-clusterip/v1" - ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1" - ServiceExternalNameGeneratorV1Name = "service-externalname/v1" - ServiceAccountV1GeneratorName = "serviceaccount/v1" - HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1" - DeploymentV1Beta1GeneratorName = "deployment/v1beta1" - DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1" - JobV1GeneratorName = "job/v1" - CronJobV2Alpha1GeneratorName = "cronjob/v2alpha1" - ScheduledJobV2Alpha1GeneratorName = "scheduledjob/v2alpha1" - NamespaceV1GeneratorName = "namespace/v1" - ResourceQuotaV1GeneratorName = "resourcequotas/v1" - SecretV1GeneratorName = "secret/v1" - SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" - SecretForTLSV1GeneratorName = "secret-for-tls/v1" - ConfigMapV1GeneratorName = "configmap/v1" - ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1" - RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1" - ClusterV1Beta1GeneratorName = "cluster/v1beta1" - PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1" + RunV1GeneratorName = "run/v1" + RunPodV1GeneratorName = "run-pod/v1" + ServiceV1GeneratorName = "service/v1" + ServiceV2GeneratorName = "service/v2" + ServiceNodePortGeneratorV1Name = "service-nodeport/v1" + ServiceClusterIPGeneratorV1Name = "service-clusterip/v1" + ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1" + ServiceExternalNameGeneratorV1Name = "service-externalname/v1" + ServiceAccountV1GeneratorName = "serviceaccount/v1" + HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1" + DeploymentV1Beta1GeneratorName = "deployment/v1beta1" + DeploymentAppsV1Beta1GeneratorName = "deployment/apps.v1beta1" + DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1" + DeploymentBasicAppsV1Beta1GeneratorName = "deployment-basic/apps.v1beta1" + JobV1GeneratorName = "job/v1" + CronJobV2Alpha1GeneratorName = "cronjob/v2alpha1" + ScheduledJobV2Alpha1GeneratorName = "scheduledjob/v2alpha1" + NamespaceV1GeneratorName = "namespace/v1" + ResourceQuotaV1GeneratorName = "resourcequotas/v1" + SecretV1GeneratorName = "secret/v1" + SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" + SecretForTLSV1GeneratorName = "secret-for-tls/v1" + ConfigMapV1GeneratorName = "configmap/v1" + ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1" + RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1" + ClusterV1Beta1GeneratorName = "cluster/v1beta1" + PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1" ) // DefaultGenerators returns the set of default generators for use in Factory instances @@ -506,16 +508,18 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { } case "deployment": generator = map[string]kubectl.Generator{ - DeploymentBasicV1Beta1GeneratorName: kubectl.DeploymentBasicGeneratorV1{}, + DeploymentBasicV1Beta1GeneratorName: kubectl.DeploymentBasicGeneratorV1{}, + DeploymentBasicAppsV1Beta1GeneratorName: kubectl.DeploymentBasicAppsGeneratorV1{}, } case "run": generator = map[string]kubectl.Generator{ - RunV1GeneratorName: kubectl.BasicReplicationController{}, - RunPodV1GeneratorName: kubectl.BasicPod{}, - DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, - JobV1GeneratorName: kubectl.JobV1{}, - ScheduledJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, - CronJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, + RunV1GeneratorName: kubectl.BasicReplicationController{}, + RunPodV1GeneratorName: kubectl.BasicPod{}, + DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, + DeploymentAppsV1Beta1GeneratorName: kubectl.DeploymentAppsV1Beta1{}, + JobV1GeneratorName: kubectl.JobV1{}, + ScheduledJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, + CronJobV2Alpha1GeneratorName: kubectl.CronJobV2Alpha1{}, } case "autoscale": generator = map[string]kubectl.Generator{ diff --git a/pkg/kubectl/deployment.go b/pkg/kubectl/deployment.go index cbe12a410a9..9747155ab8e 100644 --- a/pkg/kubectl/deployment.go +++ b/pkg/kubectl/deployment.go @@ -22,8 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/api/v1" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) // DeploymentBasicGeneratorV1 supports stable generation of a deployment @@ -65,7 +66,7 @@ func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error return nil, err } - podSpec := api.PodSpec{Containers: []api.Container{}} + podSpec := v1.PodSpec{Containers: []v1.Container{}} for _, imageString := range s.Images { // Retain just the image name imageSplit := strings.Split(imageString, "/") @@ -76,22 +77,23 @@ func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error } else if strings.Contains(name, "@") { name = strings.Split(name, "@")[0] } - podSpec.Containers = append(podSpec.Containers, api.Container{Name: name, Image: imageString}) + podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString}) } // setup default label and selector labels := map[string]string{} labels["app"] = s.Name + one := int32(1) selector := metav1.LabelSelector{MatchLabels: labels} - deployment := extensions.Deployment{ + deployment := extensionsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: s.Name, Labels: labels, }, - Spec: extensions.DeploymentSpec{ - Replicas: 1, + Spec: extensionsv1beta1.DeploymentSpec{ + Replicas: &one, Selector: &selector, - Template: api.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, @@ -112,3 +114,91 @@ func (s *DeploymentBasicGeneratorV1) validate() error { } return nil } + +// DeploymentBasicAppsGeneratorV1 supports stable generation of a deployment under apps/v1beta1 endpoint +type DeploymentBasicAppsGeneratorV1 struct { + Name string + Images []string +} + +// Ensure it supports the generator pattern that uses parameters specified during construction +var _ StructuredGenerator = &DeploymentBasicAppsGeneratorV1{} + +func (DeploymentBasicAppsGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"image", true}, + } +} + +func (s DeploymentBasicAppsGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), params) + if err != nil { + return nil, err + } + name, isString := params["name"].(string) + if !isString { + return nil, fmt.Errorf("expected string, saw %v for 'name'", name) + } + imageStrings, isArray := params["image"].([]string) + if !isArray { + return nil, fmt.Errorf("expected []string, found :%v", imageStrings) + } + delegate := &DeploymentBasicAppsGeneratorV1{Name: name, Images: imageStrings} + return delegate.StructuredGenerate() +} + +// StructuredGenerate outputs a deployment object using the configured fields +func (s *DeploymentBasicAppsGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := s.validate(); err != nil { + return nil, err + } + + podSpec := v1.PodSpec{Containers: []v1.Container{}} + for _, imageString := range s.Images { + // Retain just the image name + imageSplit := strings.Split(imageString, "/") + name := imageSplit[len(imageSplit)-1] + // Remove any tag or hash + if strings.Contains(name, ":") { + name = strings.Split(name, ":")[0] + } else if strings.Contains(name, "@") { + name = strings.Split(name, "@")[0] + } + podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString}) + } + + // setup default label and selector + labels := map[string]string{} + labels["app"] = s.Name + one := int32(1) + selector := metav1.LabelSelector{MatchLabels: labels} + deployment := appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name, + Labels: labels, + }, + Spec: appsv1beta1.DeploymentSpec{ + Replicas: &one, + Selector: &selector, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: podSpec, + }, + }, + } + return &deployment, nil +} + +// validate validates required fields are set to support structured generation +func (s *DeploymentBasicAppsGeneratorV1) validate() error { + if len(s.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if len(s.Images) == 0 { + return fmt.Errorf("at least one image must be specified") + } + return nil +} diff --git a/pkg/kubectl/deployment_test.go b/pkg/kubectl/deployment_test.go index db010ca2627..f6dc0a2f46e 100644 --- a/pkg/kubectl/deployment_test.go +++ b/pkg/kubectl/deployment_test.go @@ -21,14 +21,16 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/api/v1" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) func TestDeploymentGenerate(t *testing.T) { + one := int32(1) tests := []struct { params map[string]interface{} - expected *extensions.Deployment + expected *extensionsv1beta1.Deployment expectErr bool }{ { @@ -36,20 +38,20 @@ func TestDeploymentGenerate(t *testing.T) { "name": "foo", "image": []string{"abc/app:v4"}, }, - expected: &extensions.Deployment{ + expected: &extensionsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"app": "foo"}, }, - Spec: extensions.DeploymentSpec{ - Replicas: 1, + Spec: extensionsv1beta1.DeploymentSpec{ + Replicas: &one, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, - Template: api.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"app": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{{Name: "app", Image: "abc/app:v4"}}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "app", Image: "abc/app:v4"}}, }, }, }, @@ -61,20 +63,20 @@ func TestDeploymentGenerate(t *testing.T) { "name": "foo", "image": []string{"abc/app:v4", "zyx/ape"}, }, - expected: &extensions.Deployment{ + expected: &extensionsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"app": "foo"}, }, - Spec: extensions.DeploymentSpec{ - Replicas: 1, + Spec: extensionsv1beta1.DeploymentSpec{ + Replicas: &one, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, - Template: api.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"app": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{{Name: "app", Image: "abc/app:v4"}, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "app", Image: "abc/app:v4"}, {Name: "ape", Image: "zyx/ape"}}, }, }, @@ -128,8 +130,118 @@ func TestDeploymentGenerate(t *testing.T) { case !test.expectErr && err == nil: // do nothing and drop through } - if !reflect.DeepEqual(obj.(*extensions.Deployment), test.expected) { - t.Errorf("expected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensions.Deployment)) + if !reflect.DeepEqual(obj.(*extensionsv1beta1.Deployment), test.expected) { + t.Errorf("expected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensionsv1beta1.Deployment)) + } + } +} + +func TestAppsDeploymentGenerate(t *testing.T) { + one := int32(1) + tests := []struct { + params map[string]interface{} + expected *appsv1beta1.Deployment + expectErr bool + }{ + { + params: map[string]interface{}{ + "name": "foo", + "image": []string{"abc/app:v4"}, + }, + expected: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"app": "foo"}, + }, + Spec: appsv1beta1.DeploymentSpec{ + Replicas: &one, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": "foo"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "app", Image: "abc/app:v4"}}, + }, + }, + }, + }, + expectErr: false, + }, + { + params: map[string]interface{}{ + "name": "foo", + "image": []string{"abc/app:v4", "zyx/ape"}, + }, + expected: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"app": "foo"}, + }, + Spec: appsv1beta1.DeploymentSpec{ + Replicas: &one, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "foo"}}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": "foo"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{{Name: "app", Image: "abc/app:v4"}, + {Name: "ape", Image: "zyx/ape"}}, + }, + }, + }, + }, + expectErr: false, + }, + { + params: map[string]interface{}{}, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": 1, + }, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": nil, + }, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "image": []string{}, + }, + expectErr: true, + }, + { + params: map[string]interface{}{ + "NAME": "some_value", + }, + expectErr: true, + }, + } + generator := DeploymentBasicAppsGeneratorV1{} + for index, test := range tests { + t.Logf("running scenario %d", index) + obj, err := generator.Generate(test.params) + switch { + case test.expectErr && err != nil: + continue // loop, since there's no output to check + case test.expectErr && err == nil: + t.Errorf("expected error and didn't get one") + continue // loop, no expected output object + case !test.expectErr && err != nil: + t.Errorf("unexpected error %v", err) + continue // loop, no output object + case !test.expectErr && err == nil: + // do nothing and drop through + } + if !reflect.DeepEqual(obj.(*appsv1beta1.Deployment), test.expected) { + t.Errorf("expected:\n%#v\nsaw:\n%#v", test.expected, obj.(*appsv1beta1.Deployment)) } } } diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index cb9ec17c9bd..8a25e5e8b9a 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -26,8 +26,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/batch" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/api/v1" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" + batchv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) type DeploymentV1Beta1 struct{} @@ -88,7 +91,7 @@ func (DeploymentV1Beta1) Generate(genericParams map[string]interface{}) (runtime return nil, err } - imagePullPolicy := api.PullPolicy(params["image-pull-policy"]) + imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) if err = updatePodContainers(params, args, envs, imagePullPolicy, podSpec); err != nil { return nil, err } @@ -99,15 +102,16 @@ func (DeploymentV1Beta1) Generate(genericParams map[string]interface{}) (runtime // TODO: use versioned types for generators so that we don't need to // set default values manually (see issue #17384) - deployment := extensions.Deployment{ + count32 := int32(count) + deployment := extensionsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, }, - Spec: extensions.DeploymentSpec{ - Replicas: int32(count), + Spec: extensionsv1beta1.DeploymentSpec{ + Replicas: &count32, Selector: &metav1.LabelSelector{MatchLabels: labels}, - Template: api.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, @@ -118,6 +122,94 @@ func (DeploymentV1Beta1) Generate(genericParams map[string]interface{}) (runtime return &deployment, nil } +type DeploymentAppsV1Beta1 struct{} + +func (DeploymentAppsV1Beta1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"labels", false}, + {"default-name", false}, + {"name", true}, + {"replicas", true}, + {"image", true}, + {"image-pull-policy", false}, + {"port", false}, + {"hostport", false}, + {"stdin", false}, + {"tty", false}, + {"command", false}, + {"args", false}, + {"env", false}, + {"requests", false}, + {"limits", false}, + } +} + +func (DeploymentAppsV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { + args, err := getArgs(genericParams) + if err != nil { + return nil, err + } + + envs, err := getEnvs(genericParams) + if err != nil { + return nil, err + } + + params, err := getParams(genericParams) + if err != nil { + return nil, err + } + + name, err := getName(params) + if err != nil { + return nil, err + } + + labels, err := getLabels(params, true, name) + if err != nil { + return nil, err + } + + count, err := strconv.Atoi(params["replicas"]) + if err != nil { + return nil, err + } + + podSpec, err := makePodSpec(params, name) + if err != nil { + return nil, err + } + + imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) + if err = updatePodContainers(params, args, envs, imagePullPolicy, podSpec); err != nil { + return nil, err + } + + if err := updatePodPorts(params, podSpec); err != nil { + return nil, err + } + + count32 := int32(count) + deployment := appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: labels, + }, + Spec: appsv1beta1.DeploymentSpec{ + Replicas: &count32, + Selector: &metav1.LabelSelector{MatchLabels: labels}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: *podSpec, + }, + }, + } + return &deployment, nil +} + +// getLabels returns map of labels. func getLabels(params map[string]string, defaultRunLabel bool, name string) (map[string]string, error) { labelString, found := params["labels"] var labels map[string]string @@ -135,6 +227,7 @@ func getLabels(params map[string]string, defaultRunLabel bool, name string) (map return labels, nil } +// getName returns the name of newly created resource. func getName(params map[string]string) (string, error) { name, found := params["name"] if !found || len(name) == 0 { @@ -146,6 +239,7 @@ func getName(params map[string]string) (string, error) { return name, nil } +// getParams returns map of generic parameters. func getParams(genericParams map[string]interface{}) (map[string]string, error) { params := map[string]string{} for key, value := range genericParams { @@ -158,6 +252,7 @@ func getParams(genericParams map[string]interface{}) (map[string]string, error) return params, nil } +// getArgs returns arguments for the container command. func getArgs(genericParams map[string]interface{}) ([]string, error) { args := []string{} val, found := genericParams["args"] @@ -172,8 +267,9 @@ func getArgs(genericParams map[string]interface{}) ([]string, error) { return args, nil } -func getEnvs(genericParams map[string]interface{}) ([]api.EnvVar, error) { - var envs []api.EnvVar +// getEnvs returns environment variables. +func getEnvs(genericParams map[string]interface{}) ([]v1.EnvVar, error) { + var envs []v1.EnvVar envStrings, found := genericParams["env"] if found { if envStringArray, isArray := envStrings.([]string); isArray { @@ -244,7 +340,7 @@ func (JobV1) Generate(genericParams map[string]interface{}) (runtime.Object, err return nil, err } - imagePullPolicy := api.PullPolicy(params["image-pull-policy"]) + imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) if err = updatePodContainers(params, args, envs, imagePullPolicy, podSpec); err != nil { return nil, err } @@ -259,19 +355,19 @@ func (JobV1) Generate(genericParams map[string]interface{}) (runtime.Object, err return nil, err } - restartPolicy := api.RestartPolicy(params["restart"]) + restartPolicy := v1.RestartPolicy(params["restart"]) if len(restartPolicy) == 0 { - restartPolicy = api.RestartPolicyNever + restartPolicy = v1.RestartPolicyNever } podSpec.RestartPolicy = restartPolicy - job := batch.Job{ + job := batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, }, - Spec: batch.JobSpec{ - Template: api.PodTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, @@ -338,7 +434,7 @@ func (CronJobV2Alpha1) Generate(genericParams map[string]interface{}) (runtime.O return nil, err } - imagePullPolicy := api.PullPolicy(params["image-pull-policy"]) + imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) if err = updatePodContainers(params, args, envs, imagePullPolicy, podSpec); err != nil { return nil, err } @@ -353,23 +449,23 @@ func (CronJobV2Alpha1) Generate(genericParams map[string]interface{}) (runtime.O return nil, err } - restartPolicy := api.RestartPolicy(params["restart"]) + restartPolicy := v1.RestartPolicy(params["restart"]) if len(restartPolicy) == 0 { - restartPolicy = api.RestartPolicyNever + restartPolicy = v1.RestartPolicyNever } podSpec.RestartPolicy = restartPolicy - cronJob := batch.CronJob{ + cronJob := batchv2alpha1.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, }, - Spec: batch.CronJobSpec{ + Spec: batchv2alpha1.CronJobSpec{ Schedule: params["schedule"], - ConcurrencyPolicy: batch.AllowConcurrent, - JobTemplate: batch.JobTemplateSpec{ - Spec: batch.JobSpec{ - Template: api.PodTemplateSpec{ + ConcurrencyPolicy: batchv2alpha1.AllowConcurrent, + JobTemplate: batchv2alpha1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, @@ -406,6 +502,7 @@ func (BasicReplicationController) ParamNames() []GeneratorParam { } // populateResourceList takes strings of form =,= +// and returns ResourceList. func populateResourceList(spec string) (api.ResourceList, error) { // empty input gets a nil response to preserve generator test expected behaviors if spec == "" { @@ -429,7 +526,33 @@ func populateResourceList(spec string) (api.ResourceList, error) { return result, nil } +// populateResourceListV1 takes strings of form =,= +// and returns ResourceList. +func populateResourceListV1(spec string) (v1.ResourceList, error) { + // empty input gets a nil response to preserve generator test expected behaviors + if spec == "" { + return nil, nil + } + + result := v1.ResourceList{} + resourceStatements := strings.Split(spec, ",") + for _, resourceStatement := range resourceStatements { + parts := strings.Split(resourceStatement, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid argument syntax %v, expected =", resourceStatement) + } + resourceName := v1.ResourceName(parts[0]) + resourceQuantity, err := resource.ParseQuantity(parts[1]) + if err != nil { + return nil, err + } + result[resourceName] = resourceQuantity + } + return result, nil +} + // HandleResourceRequirements parses the limits and requests parameters if specified +// and returns ResourceRequirements. func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) { result := api.ResourceRequirements{} limits, err := populateResourceList(params["limits"]) @@ -445,7 +568,25 @@ func HandleResourceRequirements(params map[string]string) (api.ResourceRequireme return result, nil } -func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { +// HandleResourceRequirementsV1 parses the limits and requests parameters if specified +// and returns ResourceRequirements. +func HandleResourceRequirementsV1(params map[string]string) (v1.ResourceRequirements, error) { + result := v1.ResourceRequirements{} + limits, err := populateResourceListV1(params["limits"]) + if err != nil { + return result, err + } + result.Limits = limits + requests, err := populateResourceListV1(params["requests"]) + if err != nil { + return result, err + } + result.Requests = requests + return result, nil +} + +// makePodSpec returns PodSpec filled with passed parameters. +func makePodSpec(params map[string]string, name string) (*v1.PodSpec, error) { stdin, err := GetBool(params, "stdin", false) if err != nil { return nil, err @@ -456,13 +597,13 @@ func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { return nil, err } - resourceRequirements, err := HandleResourceRequirements(params) + resourceRequirements, err := HandleResourceRequirementsV1(params) if err != nil { return nil, err } - spec := api.PodSpec{ - Containers: []api.Container{ + spec := v1.PodSpec{ + Containers: []v1.Container{ { Name: name, Image: params["image"], @@ -511,7 +652,7 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{}) return nil, err } - imagePullPolicy := api.PullPolicy(params["image-pull-policy"]) + imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) if err = updatePodContainers(params, args, envs, imagePullPolicy, podSpec); err != nil { return nil, err } @@ -520,15 +661,16 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{}) return nil, err } - controller := api.ReplicationController{ + count32 := int32(count) + controller := v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, }, - Spec: api.ReplicationControllerSpec{ - Replicas: int32(count), + Spec: v1.ReplicationControllerSpec{ + Replicas: &count32, Selector: labels, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, @@ -539,7 +681,8 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{}) return &controller, nil } -func updatePodContainers(params map[string]string, args []string, envs []api.EnvVar, imagePullPolicy api.PullPolicy, podSpec *api.PodSpec) error { +// updatePodContainers updates PodSpec.Containers with passed parameters. +func updatePodContainers(params map[string]string, args []string, envs []v1.EnvVar, imagePullPolicy v1.PullPolicy, podSpec *v1.PodSpec) error { if len(args) > 0 { command, err := GetBool(params, "command", false) if err != nil { @@ -563,7 +706,8 @@ func updatePodContainers(params map[string]string, args []string, envs []api.Env return nil } -func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) { +// updatePodContainers updates PodSpec.Containers.Ports with passed parameters. +func updatePodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) { port := -1 hostPort := -1 if len(params["port"]) > 0 { @@ -585,7 +729,7 @@ func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) // Don't include the port if it was not specified. if len(params["port"]) > 0 { - podSpec.Containers[0].Ports = []api.ContainerPort{ + podSpec.Containers[0].Ports = []v1.ContainerPort{ { ContainerPort: int32(port), }, @@ -660,39 +804,39 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, return nil, err } - resourceRequirements, err := HandleResourceRequirements(params) + resourceRequirements, err := HandleResourceRequirementsV1(params) if err != nil { return nil, err } - restartPolicy := api.RestartPolicy(params["restart"]) + restartPolicy := v1.RestartPolicy(params["restart"]) if len(restartPolicy) == 0 { - restartPolicy = api.RestartPolicyAlways + restartPolicy = v1.RestartPolicyAlways } // TODO: Figure out why we set ImagePullPolicy here, whether we can make it // consistent with the other places imagePullPolicy is set using flag. - pod := api.Pod{ + pod := v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: labels, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: name, Image: params["image"], - ImagePullPolicy: api.PullIfNotPresent, + ImagePullPolicy: v1.PullIfNotPresent, Stdin: stdin, StdinOnce: !leaveStdinOpen && stdin, TTY: tty, Resources: resourceRequirements, }, }, - DNSPolicy: api.DNSClusterFirst, + DNSPolicy: v1.DNSClusterFirst, RestartPolicy: restartPolicy, }, } - imagePullPolicy := api.PullPolicy(params["image-pull-policy"]) + imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) if err = updatePodContainers(params, args, envs, imagePullPolicy, &pod.Spec); err != nil { return nil, err } @@ -703,8 +847,9 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, return &pod, nil } -func parseEnvs(envArray []string) ([]api.EnvVar, error) { - envs := make([]api.EnvVar, 0, len(envArray)) +// parseEnvs converts string into EnvVar objects. +func parseEnvs(envArray []string) ([]v1.EnvVar, error) { + envs := make([]v1.EnvVar, 0, len(envArray)) for _, env := range envArray { pos := strings.Index(env, "=") if pos == -1 { @@ -718,7 +863,7 @@ func parseEnvs(envArray []string) ([]api.EnvVar, error) { if len(validation.IsCIdentifier(name)) != 0 { return nil, fmt.Errorf("invalid env: %v", env) } - envVar := api.EnvVar{Name: name, Value: value} + envVar := v1.EnvVar{Name: name, Value: value} envs = append(envs, envVar) } return envs, nil diff --git a/pkg/kubectl/run_test.go b/pkg/kubectl/run_test.go index 258f2f95b79..b3301758fd9 100644 --- a/pkg/kubectl/run_test.go +++ b/pkg/kubectl/run_test.go @@ -22,15 +22,18 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/batch" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/api/v1" + appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" + batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" + batchv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) func TestGenerate(t *testing.T) { + one := int32(1) tests := []struct { params map[string]interface{} - expected *api.ReplicationController + expected *v1.ReplicationController expectErr bool }{ { @@ -41,24 +44,24 @@ func TestGenerate(t *testing.T) { "replicas": "1", "port": "", }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"run": "foo"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"run": "foo"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"run": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullAlways, + ImagePullPolicy: v1.PullAlways, }, }, }, @@ -75,24 +78,24 @@ func TestGenerate(t *testing.T) { "port": "", "env": []string{"a=b", "c=d"}, }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"run": "foo"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"run": "foo"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"run": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - Env: []api.EnvVar{ + Env: []v1.EnvVar{ { Name: "a", Value: "b", @@ -119,24 +122,24 @@ func TestGenerate(t *testing.T) { "port": "", "args": []string{"bar", "baz", "blah"}, }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"run": "foo"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"run": "foo"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"run": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullNever, + ImagePullPolicy: v1.PullNever, Args: []string{"bar", "baz", "blah"}, }, }, @@ -154,20 +157,20 @@ func TestGenerate(t *testing.T) { "args": []string{"bar", "baz", "blah"}, "command": "true", }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"run": "foo"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"run": "foo"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"run": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", @@ -186,24 +189,24 @@ func TestGenerate(t *testing.T) { "replicas": "1", "port": "80", }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"run": "foo"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"run": "foo"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"run": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - Ports: []api.ContainerPort{ + Ports: []v1.ContainerPort{ { ContainerPort: 80, }, @@ -224,25 +227,25 @@ func TestGenerate(t *testing.T) { "port": "80", "hostport": "80", }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"run": "foo"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"run": "foo"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"run": "foo"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, - Ports: []api.ContainerPort{ + ImagePullPolicy: v1.PullIfNotPresent, + Ports: []v1.ContainerPort{ { ContainerPort: 80, HostPort: 80, @@ -272,20 +275,20 @@ func TestGenerate(t *testing.T) { "replicas": "1", "labels": "foo=bar,baz=blah", }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"foo": "bar", "baz": "blah"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", @@ -348,31 +351,31 @@ func TestGenerate(t *testing.T) { "requests": "cpu=100m,memory=100Mi", "limits": "cpu=400m,memory=200Mi", }, - expected: &api.ReplicationController{ + expected: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Spec: v1.ReplicationControllerSpec{ + Replicas: &one, Selector: map[string]string{"foo": "bar", "baz": "blah"}, - Template: &api.PodTemplateSpec{ + Template: &v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100m"), - api.ResourceMemory: resource.MustParse("100Mi"), + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), }, - Limits: api.ResourceList{ - api.ResourceCPU: resource.MustParse("400m"), - api.ResourceMemory: resource.MustParse("200Mi"), + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("400m"), + v1.ResourceMemory: resource.MustParse("200Mi"), }, }, }, @@ -394,8 +397,8 @@ func TestGenerate(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.ReplicationController).Spec.Template, test.expected.Spec.Template) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected.Spec.Template, obj.(*api.ReplicationController).Spec.Template) + if !reflect.DeepEqual(obj.(*v1.ReplicationController).Spec.Template, test.expected.Spec.Template) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected.Spec.Template, obj.(*v1.ReplicationController).Spec.Template) } } } @@ -403,7 +406,7 @@ func TestGenerate(t *testing.T) { func TestGeneratePod(t *testing.T) { tests := []struct { params map[string]interface{} - expected *api.Pod + expected *v1.Pod expectErr bool }{ { @@ -412,20 +415,20 @@ func TestGeneratePod(t *testing.T) { "image": "someimage", "port": "", }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, + ImagePullPolicy: v1.PullIfNotPresent, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -446,17 +449,17 @@ func TestGeneratePod(t *testing.T) { "image-pull-policy": "Always", "env": []string{"a=b", "c=d"}, }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullAlways, - Env: []api.EnvVar{ + ImagePullPolicy: v1.PullAlways, + Env: []v1.EnvVar{ { Name: "a", Value: "b", @@ -468,8 +471,8 @@ func TestGeneratePod(t *testing.T) { }, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -479,25 +482,25 @@ func TestGeneratePod(t *testing.T) { "image": "someimage", "port": "80", }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, - Ports: []api.ContainerPort{ + ImagePullPolicy: v1.PullIfNotPresent, + Ports: []v1.ContainerPort{ { ContainerPort: 80, }, }, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -508,17 +511,17 @@ func TestGeneratePod(t *testing.T) { "port": "80", "hostport": "80", }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, - Ports: []api.ContainerPort{ + ImagePullPolicy: v1.PullIfNotPresent, + Ports: []v1.ContainerPort{ { ContainerPort: 80, HostPort: 80, @@ -526,8 +529,8 @@ func TestGeneratePod(t *testing.T) { }, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -547,21 +550,21 @@ func TestGeneratePod(t *testing.T) { "replicas": "1", "labels": "foo=bar,baz=blah", }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, + ImagePullPolicy: v1.PullIfNotPresent, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -573,23 +576,23 @@ func TestGeneratePod(t *testing.T) { "labels": "foo=bar,baz=blah", "stdin": "true", }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, + ImagePullPolicy: v1.PullIfNotPresent, Stdin: true, StdinOnce: true, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -602,23 +605,23 @@ func TestGeneratePod(t *testing.T) { "stdin": "true", "leave-stdin-open": "true", }, - expected: &api.Pod{ + expected: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullIfNotPresent, + ImagePullPolicy: v1.PullIfNotPresent, Stdin: true, StdinOnce: false, }, }, - DNSPolicy: api.DNSClusterFirst, - RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, }, }, }, @@ -632,16 +635,17 @@ func TestGeneratePod(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*api.Pod), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.Pod)) + if !reflect.DeepEqual(obj.(*v1.Pod), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*v1.Pod)) } } } func TestGenerateDeployment(t *testing.T) { + three := int32(3) tests := []struct { params map[string]interface{} - expected *extensions.Deployment + expected *extensionsv1beta1.Deployment expectErr bool }{ { @@ -660,33 +664,33 @@ func TestGenerateDeployment(t *testing.T) { "requests": "cpu=100m,memory=100Mi", "limits": "cpu=400m,memory=200Mi", }, - expected: &extensions.Deployment{ + expected: &extensionsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: extensions.DeploymentSpec{ - Replicas: 3, + Spec: extensionsv1beta1.DeploymentSpec{ + Replicas: &three, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar", "baz": "blah"}}, - Template: api.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - Containers: []api.Container{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ { Name: "foo", Image: "someimage", - ImagePullPolicy: api.PullAlways, + ImagePullPolicy: v1.PullAlways, Stdin: true, - Ports: []api.ContainerPort{ + Ports: []v1.ContainerPort{ { ContainerPort: 80, HostPort: 80, }, }, Command: []string{"bar", "baz", "blah"}, - Env: []api.EnvVar{ + Env: []v1.EnvVar{ { Name: "a", Value: "b", @@ -696,14 +700,14 @@ func TestGenerateDeployment(t *testing.T) { Value: "d", }, }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100m"), - api.ResourceMemory: resource.MustParse("100Mi"), + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), }, - Limits: api.ResourceList{ - api.ResourceCPU: resource.MustParse("400m"), - api.ResourceMemory: resource.MustParse("200Mi"), + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("400m"), + v1.ResourceMemory: resource.MustParse("200Mi"), }, }, }, @@ -724,8 +728,101 @@ func TestGenerateDeployment(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*extensions.Deployment), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensions.Deployment)) + if !reflect.DeepEqual(obj.(*extensionsv1beta1.Deployment), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensionsv1beta1.Deployment)) + } + } +} + +func TestGenerateAppsDeployment(t *testing.T) { + three := int32(3) + tests := []struct { + params map[string]interface{} + expected *appsv1beta1.Deployment + expectErr bool + }{ + { + params: map[string]interface{}{ + "labels": "foo=bar,baz=blah", + "name": "foo", + "replicas": "3", + "image": "someimage", + "image-pull-policy": "Always", + "port": "80", + "hostport": "80", + "stdin": "true", + "command": "true", + "args": []string{"bar", "baz", "blah"}, + "env": []string{"a=b", "c=d"}, + "requests": "cpu=100m,memory=100Mi", + "limits": "cpu=400m,memory=200Mi", + }, + expected: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "bar", "baz": "blah"}, + }, + Spec: appsv1beta1.DeploymentSpec{ + Replicas: &three, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar", "baz": "blah"}}, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar", "baz": "blah"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foo", + Image: "someimage", + ImagePullPolicy: v1.PullAlways, + Stdin: true, + Ports: []v1.ContainerPort{ + { + ContainerPort: 80, + HostPort: 80, + }, + }, + Command: []string{"bar", "baz", "blah"}, + Env: []v1.EnvVar{ + { + Name: "a", + Value: "b", + }, + { + Name: "c", + Value: "d", + }, + }, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("400m"), + v1.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + generator := DeploymentAppsV1Beta1{} + for _, test := range tests { + obj, err := generator.Generate(test.params) + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + if test.expectErr && err != nil { + continue + } + if !reflect.DeepEqual(obj.(*appsv1beta1.Deployment), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*appsv1beta1.Deployment)) } } } @@ -733,7 +830,7 @@ func TestGenerateDeployment(t *testing.T) { func TestGenerateJob(t *testing.T) { tests := []struct { params map[string]interface{} - expected *batch.Job + expected *batchv1.Job expectErr bool }{ { @@ -752,32 +849,32 @@ func TestGenerateJob(t *testing.T) { "limits": "cpu=400m,memory=200Mi", "restart": "OnFailure", }, - expected: &batch.Job{ + expected: &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: batch.JobSpec{ - Template: api.PodTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - Containers: []api.Container{ + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyOnFailure, + Containers: []v1.Container{ { Name: "foo", Image: "someimage", Stdin: true, StdinOnce: false, - Ports: []api.ContainerPort{ + Ports: []v1.ContainerPort{ { ContainerPort: 80, HostPort: 80, }, }, Command: []string{"bar", "baz", "blah"}, - Env: []api.EnvVar{ + Env: []v1.EnvVar{ { Name: "a", Value: "b", @@ -787,14 +884,14 @@ func TestGenerateJob(t *testing.T) { Value: "d", }, }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100m"), - api.ResourceMemory: resource.MustParse("100Mi"), + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), }, - Limits: api.ResourceList{ - api.ResourceCPU: resource.MustParse("400m"), - api.ResourceMemory: resource.MustParse("200Mi"), + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("400m"), + v1.ResourceMemory: resource.MustParse("200Mi"), }, }, }, @@ -815,8 +912,8 @@ func TestGenerateJob(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*batch.Job), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*batch.Job)) + if !reflect.DeepEqual(obj.(*batchv1.Job), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*batchv1.Job)) } } } @@ -824,7 +921,7 @@ func TestGenerateJob(t *testing.T) { func TestGenerateCronJob(t *testing.T) { tests := []struct { params map[string]interface{} - expected *batch.CronJob + expected *batchv2alpha1.CronJob expectErr bool }{ { @@ -844,36 +941,36 @@ func TestGenerateCronJob(t *testing.T) { "restart": "OnFailure", "schedule": "0/5 * * * ?", }, - expected: &batch.CronJob{ + expected: &batchv2alpha1.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: batch.CronJobSpec{ + Spec: batchv2alpha1.CronJobSpec{ Schedule: "0/5 * * * ?", - ConcurrencyPolicy: batch.AllowConcurrent, - JobTemplate: batch.JobTemplateSpec{ - Spec: batch.JobSpec{ - Template: api.PodTemplateSpec{ + ConcurrencyPolicy: batchv2alpha1.AllowConcurrent, + JobTemplate: batchv2alpha1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - Containers: []api.Container{ + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyOnFailure, + Containers: []v1.Container{ { Name: "foo", Image: "someimage", Stdin: true, StdinOnce: false, - Ports: []api.ContainerPort{ + Ports: []v1.ContainerPort{ { ContainerPort: 80, HostPort: 80, }, }, Command: []string{"bar", "baz", "blah"}, - Env: []api.EnvVar{ + Env: []v1.EnvVar{ { Name: "a", Value: "b", @@ -883,14 +980,14 @@ func TestGenerateCronJob(t *testing.T) { Value: "d", }, }, - Resources: api.ResourceRequirements{ - Requests: api.ResourceList{ - api.ResourceCPU: resource.MustParse("100m"), - api.ResourceMemory: resource.MustParse("100Mi"), + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100m"), + v1.ResourceMemory: resource.MustParse("100Mi"), }, - Limits: api.ResourceList{ - api.ResourceCPU: resource.MustParse("400m"), - api.ResourceMemory: resource.MustParse("200Mi"), + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("400m"), + v1.ResourceMemory: resource.MustParse("200Mi"), }, }, }, @@ -913,8 +1010,8 @@ func TestGenerateCronJob(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*batch.CronJob), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*batch.CronJob)) + if !reflect.DeepEqual(obj.(*batchv2alpha1.CronJob), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*batchv2alpha1.CronJob)) } } } @@ -922,7 +1019,7 @@ func TestGenerateCronJob(t *testing.T) { func TestParseEnv(t *testing.T) { tests := []struct { envArray []string - expected []api.EnvVar + expected []v1.EnvVar expectErr bool test string }{ @@ -932,7 +1029,7 @@ func TestParseEnv(t *testing.T) { "HAS_COMMAS=foo,bar", "HAS_EQUALS=jJnro54iUu75xNy==", }, - expected: []api.EnvVar{ + expected: []v1.EnvVar{ { Name: "THIS_ENV", Value: "isOK", @@ -953,7 +1050,7 @@ func TestParseEnv(t *testing.T) { envArray: []string{ "WITH_OUT_EQUALS", }, - expected: []api.EnvVar{}, + expected: []v1.EnvVar{}, expectErr: true, test: "test case 2", }, @@ -961,7 +1058,7 @@ func TestParseEnv(t *testing.T) { envArray: []string{ "WITH_OUT_VALUES=", }, - expected: []api.EnvVar{ + expected: []v1.EnvVar{ { Name: "WITH_OUT_VALUES", Value: "", @@ -974,7 +1071,7 @@ func TestParseEnv(t *testing.T) { envArray: []string{ "=WITH_OUT_NAME", }, - expected: []api.EnvVar{}, + expected: []v1.EnvVar{}, expectErr: true, test: "test case 4", },