From dc312142ab05ddf5738d44ebfd7784a89c273e0e Mon Sep 17 00:00:00 2001 From: zhengjiajin Date: Sat, 11 Nov 2017 02:36:12 +0800 Subject: [PATCH] remove todo: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely --- pkg/kubectl/cmd/set/BUILD | 10 +- pkg/kubectl/cmd/set/helper.go | 36 +- pkg/kubectl/cmd/set/set_env.go | 61 +-- pkg/kubectl/cmd/set/set_env_test.go | 380 ++++++++++++++++- pkg/kubectl/cmd/set/set_image.go | 24 +- pkg/kubectl/cmd/set/set_image_test.go | 380 ++++++++++++++++- pkg/kubectl/cmd/set/set_resources.go | 33 +- pkg/kubectl/cmd/set/set_resources_test.go | 387 +++++++++++++++++- pkg/kubectl/cmd/set/set_serviceaccount.go | 30 +- .../cmd/set/set_serviceaccount_test.go | 253 +++++++++--- pkg/kubectl/cmd/testing/BUILD | 1 + pkg/kubectl/cmd/testing/fake.go | 3 +- pkg/kubectl/cmd/util/BUILD | 4 + pkg/kubectl/cmd/util/env/BUILD | 6 +- pkg/kubectl/cmd/util/env/env_parse.go | 16 +- pkg/kubectl/cmd/util/env/env_parse_test.go | 2 +- pkg/kubectl/cmd/util/env/env_resolve.go | 26 +- pkg/kubectl/cmd/util/factory.go | 2 +- pkg/kubectl/cmd/util/factory_builder.go | 7 +- pkg/kubectl/cmd/util/factory_client_access.go | 49 ++- pkg/kubectl/run.go | 43 -- 21 files changed, 1525 insertions(+), 228 deletions(-) diff --git a/pkg/kubectl/cmd/set/BUILD b/pkg/kubectl/cmd/set/BUILD index ad8b0649c73..03b043b09fc 100644 --- a/pkg/kubectl/cmd/set/BUILD +++ b/pkg/kubectl/cmd/set/BUILD @@ -29,6 +29,7 @@ go_library( "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", @@ -38,6 +39,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) @@ -61,7 +63,6 @@ go_test( deps = [ "//pkg/api/legacyscheme:go_default_library", "//pkg/api/testapi:go_default_library", - "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", @@ -69,9 +70,16 @@ go_test( "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/resource:go_default_library", + "//pkg/kubectl/scheme:go_default_library", "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/pkg/kubectl/cmd/set/helper.go b/pkg/kubectl/cmd/set/helper.go index d67ee3f216b..6f8bdfee804 100644 --- a/pkg/kubectl/cmd/set/helper.go +++ b/pkg/kubectl/cmd/set/helper.go @@ -21,20 +21,19 @@ import ( "io" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) // selectContainers allows one or more containers to be matched against a string or wildcard -func selectContainers(containers []api.Container, spec string) ([]*api.Container, []*api.Container) { - out := []*api.Container{} - skipped := []*api.Container{} +func selectContainers(containers []v1.Container, spec string) ([]*v1.Container, []*v1.Container) { + out := []*v1.Container{} + skipped := []*v1.Container{} for i, c := range containers { if selectString(c.Name, spec) { out = append(out, &containers[i]) @@ -127,10 +126,14 @@ type patchFn func(*resource.Info) ([]byte, error) // the changes in the object. Encoder must be able to encode the info into the appropriate destination type. // This function returns whether the mutation function made any change in the original object. func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) bool { - versionedEncoder := legacyscheme.Codecs.EncoderForVersion(encoder, patch.Info.Mapping.GroupVersionKind.GroupVersion()) - - patch.Before, patch.Err = runtime.Encode(versionedEncoder, patch.Info.Object) + versioned, err := patch.Info.Mapping.ConvertToVersion(patch.Info.Object, patch.Info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + patch.Err = err + return true + } + patch.Info.VersionedObject = versioned + patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.VersionedObject) patch.After, patch.Err = mutateFn(patch.Info) if patch.Err != nil { return true @@ -139,14 +142,7 @@ func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) boo return false } - // TODO: should be via New - versioned, err := patch.Info.Mapping.ConvertToVersion(patch.Info.Object, patch.Info.Mapping.GroupVersionKind.GroupVersion()) - if err != nil { - patch.Err = err - return true - } - - patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned) + patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, patch.Info.VersionedObject) return true } @@ -163,17 +159,17 @@ func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn return patches } -func findEnv(env []api.EnvVar, name string) (api.EnvVar, bool) { +func findEnv(env []v1.EnvVar, name string) (v1.EnvVar, bool) { for _, e := range env { if e.Name == name { return e, true } } - return api.EnvVar{}, false + return v1.EnvVar{}, false } -func updateEnv(existing []api.EnvVar, env []api.EnvVar, remove []string) []api.EnvVar { - out := []api.EnvVar{} +func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar { + out := []v1.EnvVar{} covered := sets.NewString(remove...) for _, e := range existing { if covered.Has(e.Name) { diff --git a/pkg/kubectl/cmd/set/set_env.go b/pkg/kubectl/cmd/set/set_env.go index 142bf63f077..303ccc009d3 100644 --- a/pkg/kubectl/cmd/set/set_env.go +++ b/pkg/kubectl/cmd/set/set_env.go @@ -25,11 +25,11 @@ import ( "strings" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" envutil "k8s.io/kubernetes/pkg/kubectl/cmd/util/env" @@ -116,14 +116,13 @@ type EnvOptions struct { Prefix string Mapper meta.RESTMapper - Typer runtime.ObjectTyper Builder *resource.Builder Infos []*resource.Info Encoder runtime.Encoder Cmd *cobra.Command - UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error } @@ -163,7 +162,7 @@ func NewCmdEnv(f cmdutil.Factory, in io.Reader, out, errout io.Writer) *cobra.Co return cmd } -func validateNoOverwrites(existing []api.EnvVar, env []api.EnvVar) error { +func validateNoOverwrites(existing []v1.EnvVar, env []v1.EnvVar) error { for _, e := range env { if current, exists := findEnv(existing, e.Name); exists && current.Value != e.Value { return fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", current.Name, current.Value) @@ -186,7 +185,7 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri return cmdutil.UsageErrorf(cmd, "one or more resources must be specified as or /") } - o.Mapper, o.Typer = f.Object() + o.Mapper, _ = f.Object() o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.Encoder = f.JSONEncoder() o.ContainerSelector = cmdutil.GetFlagString(cmd, "containers") @@ -216,9 +215,13 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri // RunEnv contains all the necessary functionality for the OpenShift cli env command func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { - kubeClient, err := f.ClientSet() - if err != nil { - return err + var kubeClient *kubernetes.Clientset + if o.List { + client, err := f.KubernetesClientSet() + if err != nil { + return err + } + kubeClient = client } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() @@ -253,14 +256,18 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } for _, info := range infos { - switch from := info.Object.(type) { - case *api.Secret: + versionedObject, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + switch from := versionedObject.(type) { + case *v1.Secret: for key := range from.Data { - envVar := api.EnvVar{ + envVar := v1.EnvVar{ Name: keyToEnvName(key), - ValueFrom: &api.EnvVarSource{ - SecretKeyRef: &api.SecretKeySelector{ - LocalObjectReference: api.LocalObjectReference{ + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ Name: from.Name, }, Key: key, @@ -269,13 +276,13 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } env = append(env, envVar) } - case *api.ConfigMap: + case *v1.ConfigMap: for key := range from.Data { - envVar := api.EnvVar{ + envVar := v1.EnvVar{ Name: keyToEnvName(key), - ValueFrom: &api.EnvVarSource{ - ConfigMapKeyRef: &api.ConfigMapKeySelector{ - LocalObjectReference: api.LocalObjectReference{ + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ Name: from.Name, }, Key: key, @@ -316,7 +323,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { return err } patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { - _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { + _, err := o.UpdatePodSpecForObject(info.VersionedObject, func(spec *v1.PodSpec) error { resolutionErrorsEncountered := false containers, _ := selectContainers(spec.Containers, o.ContainerSelector) if len(containers) == 0 { @@ -382,9 +389,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { }) if err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := legacyscheme.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + return runtime.Encode(o.Encoder, info.VersionedObject) } return nil, err }) @@ -408,7 +413,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } if o.PrintObject != nil && (o.Local || o.DryRun) { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out); err != nil { + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, patch.Info.VersionedObject, o.Out); err != nil { return err } continue @@ -428,7 +433,11 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error { } if len(o.Output) > 0 { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, obj, o.Out); err != nil { + versionedObject, err := patch.Info.Mapping.ConvertToVersion(obj, patch.Info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, versionedObject, o.Out); err != nil { return err } continue diff --git a/pkg/kubectl/cmd/set/set_env_test.go b/pkg/kubectl/cmd/set/set_env_test.go index db20e81aeb7..2c4d1455b1f 100644 --- a/pkg/kubectl/cmd/set/set_env_test.go +++ b/pkg/kubectl/cmd/set/set_env_test.go @@ -18,25 +18,37 @@ package set import ( "bytes" + "fmt" + "io/ioutil" "net/http" "os" + "path" "strings" "testing" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/api/testapi" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) func TestSetEnvLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -44,7 +56,7 @@ func TestSetEnvLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdEnv(f, os.Stdin, buf, buf) @@ -73,7 +85,7 @@ func TestSetEnvLocal(t *testing.T) { func TestSetMultiResourcesEnvLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -81,7 +93,7 @@ func TestSetMultiResourcesEnvLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdEnv(f, os.Stdin, buf, buf) @@ -108,3 +120,359 @@ func TestSetMultiResourcesEnvLocal(t *testing.T) { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } } + +func TestSetEnvRemote(t *testing.T) { + inputs := []struct { + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string + }{ + { + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"replicaset", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx", "env=prod"}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx", "env=prod"}, + }, + { + object: &extensionsv1beta1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"daemonset", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx", "env=prod"}, + }, + { + object: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx", "env=prod"}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx", "env=prod"}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx", "env=prod"}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx", "env=prod"}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx", "env=prod"}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", + args: []string{"job", "nginx", "env=prod"}, + }, + { + object: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx", "env=prod"}, + }, + } + for _, input := range inputs { + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) + tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) + tf.Namespace = "test" + tf.CategoryExpander = resource.LegacyCategoryExpander + tf.Client = &fake.RESTClient{ + GroupVersion: groupVersion, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) + switch p, m := req.URL.Path, req.Method; { + case p == resourcePath && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + case p == resourcePath && m == http.MethodPatch: + stream, err := req.GetBody() + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadAll(stream) + if err != nil { + return nil, err + } + assert.Contains(t, string(bytes), `"value":`+`"`+"prod"+`"`, fmt.Sprintf("env not updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + default: + t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), + } + out := new(bytes.Buffer) + cmd := NewCmdEnv(f, out, out, out) + cmd.SetOutput(out) + cmd.Flags().Set("output", "yaml") + opts := EnvOptions{ + Out: out, + Local: false} + err := opts.Complete(f, cmd, input.args) + assert.NoError(t, err) + err = opts.RunEnv(f) + assert.NoError(t, err) + } +} diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index df4015ae655..bad1c7ac24a 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -21,12 +21,11 @@ import ( "io" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -39,9 +38,9 @@ type ImageOptions struct { resource.FilenameOptions Mapper meta.RESTMapper - Typer runtime.ObjectTyper Infos []*resource.Info Encoder runtime.Encoder + Decoder runtime.Decoder Selector string Out io.Writer Err io.Writer @@ -56,7 +55,7 @@ type ImageOptions struct { ResolveImage func(in string) (string, error) PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error - UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) Resources []string ContainerImages map[string]string } @@ -116,9 +115,10 @@ func NewCmdImage(f cmdutil.Factory, out, err io.Writer) *cobra.Command { } func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - o.Mapper, o.Typer = f.Object() + o.Mapper, _ = f.Object() o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.Encoder = f.JSONEncoder() + o.Decoder = f.Decoder(true) o.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name" o.Record = cmdutil.GetRecordFlag(cmd) o.ChangeCause = f.Command(cmd, false) @@ -189,7 +189,7 @@ func (o *ImageOptions) Run() error { patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { transformed := false - _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { + _, err := o.UpdatePodSpecForObject(info.VersionedObject, func(spec *v1.PodSpec) error { for name, image := range o.ContainerImages { var ( containerFound bool @@ -226,9 +226,7 @@ func (o *ImageOptions) Run() error { return nil }) if transformed && err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := legacyscheme.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + return runtime.Encode(o.Encoder, info.VersionedObject) } return nil, err }) @@ -246,7 +244,7 @@ func (o *ImageOptions) Run() error { } if o.PrintObject != nil && (o.Local || o.DryRun) { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out); err != nil { + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, patch.Info.VersionedObject, o.Out); err != nil { return err } continue @@ -272,7 +270,11 @@ func (o *ImageOptions) Run() error { info.Refresh(obj, true) if len(o.Output) > 0 { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, obj, o.Out); err != nil { + versionedObject, err := patch.Info.Mapping.ConvertToVersion(obj, patch.Info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, versionedObject, o.Out); err != nil { return err } continue diff --git a/pkg/kubectl/cmd/set/set_image_test.go b/pkg/kubectl/cmd/set/set_image_test.go index 6012d6422f6..144d92184d2 100644 --- a/pkg/kubectl/cmd/set/set_image_test.go +++ b/pkg/kubectl/cmd/set/set_image_test.go @@ -18,24 +18,36 @@ package set import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path" "strings" "testing" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/api/testapi" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) func TestImageLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -43,7 +55,7 @@ func TestImageLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdImage(f, buf, buf) @@ -138,7 +150,7 @@ func TestSetImageValidation(t *testing.T) { func TestSetMultiResourcesImageLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -146,7 +158,7 @@ func TestSetMultiResourcesImageLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdImage(f, buf, buf) @@ -175,3 +187,359 @@ func TestSetMultiResourcesImageLocal(t *testing.T) { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } } + +func TestSetImageRemote(t *testing.T) { + inputs := []struct { + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string + }{ + { + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"replicaset", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx", "*=thingy"}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx", "*=thingy"}, + }, + { + object: &extensionsv1beta1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"daemonset", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx", "*=thingy"}, + }, + { + object: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx", "*=thingy"}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx", "*=thingy"}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx", "*=thingy"}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx", "*=thingy"}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", + args: []string{"job", "nginx", "*=thingy"}, + }, + { + object: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx", "*=thingy"}, + }, + } + for _, input := range inputs { + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) + tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) + tf.Namespace = "test" + tf.CategoryExpander = resource.LegacyCategoryExpander + tf.Client = &fake.RESTClient{ + GroupVersion: groupVersion, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) + switch p, m := req.URL.Path, req.Method; { + case p == resourcePath && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + case p == resourcePath && m == http.MethodPatch: + stream, err := req.GetBody() + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadAll(stream) + if err != nil { + return nil, err + } + assert.Contains(t, string(bytes), `"image":`+`"`+"thingy"+`"`, fmt.Sprintf("image not updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + default: + t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), + } + out := new(bytes.Buffer) + cmd := NewCmdImage(f, out, out) + cmd.SetOutput(out) + cmd.Flags().Set("output", "yaml") + opts := ImageOptions{ + Out: out, + Local: false} + err := opts.Complete(f, cmd, input.args) + assert.NoError(t, err) + err = opts.Run() + assert.NoError(t, err) + } +} diff --git a/pkg/kubectl/cmd/set/set_resources.go b/pkg/kubectl/cmd/set/set_resources.go index 4d1dc7b46b5..5b6a1ac60e2 100644 --- a/pkg/kubectl/cmd/set/set_resources.go +++ b/pkg/kubectl/cmd/set/set_resources.go @@ -22,13 +22,12 @@ import ( "strings" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" - api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -64,7 +63,6 @@ type ResourcesOptions struct { resource.FilenameOptions Mapper meta.RESTMapper - Typer runtime.ObjectTyper Infos []*resource.Info Encoder runtime.Encoder Out io.Writer @@ -80,10 +78,10 @@ type ResourcesOptions struct { Limits string Requests string - ResourceRequirements api.ResourceRequirements + ResourceRequirements v1.ResourceRequirements PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error - UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) Resources []string } @@ -128,7 +126,7 @@ func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra. } func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { - o.Mapper, o.Typer = f.Object() + o.Mapper, _ = f.Object() o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.Encoder = f.JSONEncoder() o.Output = cmdutil.GetFlagString(cmd, "output") @@ -180,7 +178,7 @@ func (o *ResourcesOptions) Validate() error { return fmt.Errorf("you must specify an update to requests or limits (in the form of --requests/--limits)") } - o.ResourceRequirements, err = kubectl.HandleResourceRequirements(map[string]string{"limits": o.Limits, "requests": o.Requests}) + o.ResourceRequirements, err = kubectl.HandleResourceRequirementsV1(map[string]string{"limits": o.Limits, "requests": o.Requests}) if err != nil { return err } @@ -192,19 +190,19 @@ func (o *ResourcesOptions) Run() error { allErrs := []error{} patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { transformed := false - _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { + _, err := o.UpdatePodSpecForObject(info.VersionedObject, func(spec *v1.PodSpec) error { containers, _ := selectContainers(spec.Containers, o.ContainerSelector) if len(containers) != 0 { for i := range containers { if len(o.Limits) != 0 && len(containers[i].Resources.Limits) == 0 { - containers[i].Resources.Limits = make(api.ResourceList) + containers[i].Resources.Limits = make(v1.ResourceList) } for key, value := range o.ResourceRequirements.Limits { containers[i].Resources.Limits[key] = value } if len(o.Requests) != 0 && len(containers[i].Resources.Requests) == 0 { - containers[i].Resources.Requests = make(api.ResourceList) + containers[i].Resources.Requests = make(v1.ResourceList) } for key, value := range o.ResourceRequirements.Requests { containers[i].Resources.Requests[key] = value @@ -217,9 +215,7 @@ func (o *ResourcesOptions) Run() error { return nil }) if transformed && err == nil { - // TODO: switch UpdatePodSpecForObject to work on v1.PodSpec, use info.VersionedObject, and avoid conversion completely - versionedEncoder := legacyscheme.Codecs.EncoderForVersion(o.Encoder, info.Mapping.GroupVersionKind.GroupVersion()) - return runtime.Encode(versionedEncoder, info.Object) + return runtime.Encode(o.Encoder, info.VersionedObject) } return nil, err }) @@ -238,7 +234,7 @@ func (o *ResourcesOptions) Run() error { } if o.Local || cmdutil.GetDryRunFlag(o.Cmd) { - if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out); err != nil { + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, patch.Info.VersionedObject, o.Out); err != nil { return err } continue @@ -263,7 +259,14 @@ func (o *ResourcesOptions) Run() error { shortOutput := o.Output == "name" if len(o.Output) > 0 && !shortOutput { - return o.PrintObject(o.Cmd, o.Local, o.Mapper, info.Object, o.Out) + versionedObject, err := patch.Info.Mapping.ConvertToVersion(obj, patch.Info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + if err := o.PrintObject(o.Cmd, o.Local, o.Mapper, versionedObject, o.Out); err != nil { + return err + } + continue } cmdutil.PrintSuccess(o.Mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, false, "resource requirements updated") } diff --git a/pkg/kubectl/cmd/set/set_resources_test.go b/pkg/kubectl/cmd/set/set_resources_test.go index a968e634b2a..dcfd89bd4d0 100644 --- a/pkg/kubectl/cmd/set/set_resources_test.go +++ b/pkg/kubectl/cmd/set/set_resources_test.go @@ -18,24 +18,36 @@ package set import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path" "strings" "testing" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/api/testapi" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) func TestResourcesLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -43,7 +55,7 @@ func TestResourcesLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdResources(f, buf, buf) @@ -79,7 +91,7 @@ func TestResourcesLocal(t *testing.T) { func TestSetMultiResourcesLimitsLocal(t *testing.T) { f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: ""}, NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) @@ -87,7 +99,7 @@ func TestSetMultiResourcesLimitsLocal(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdResources(f, buf, buf) @@ -120,3 +132,366 @@ func TestSetMultiResourcesLimitsLocal(t *testing.T) { t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) } } + +func TestSetResourcesRemote(t *testing.T) { + inputs := []struct { + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string + }{ + { + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"replicaset", "nginx"}, + }, + { + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx"}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx"}, + }, + { + object: &extensionsv1beta1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"daemonset", "nginx"}, + }, + { + object: &appsv1beta2.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx"}, + }, + { + object: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DaemonSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx"}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx"}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx"}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx"}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx"}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", + args: []string{"job", "nginx"}, + }, + { + object: &v1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: v1.ReplicationControllerSpec{ + Template: &v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx"}, + }, + } + for _, input := range inputs { + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{testapi.Default.Codec()}, Typer: typer, Mapper: mapper} + tf.Namespace = "test" + tf.CategoryExpander = resource.LegacyCategoryExpander + tf.Client = &fake.RESTClient{ + GroupVersion: groupVersion, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) + switch p, m := req.URL.Path, req.Method; { + case p == resourcePath && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + case p == resourcePath && m == http.MethodPatch: + stream, err := req.GetBody() + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadAll(stream) + if err != nil { + return nil, err + } + assert.Contains(t, string(bytes), "200m", fmt.Sprintf("resources not updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil + default: + t.Errorf("%s: unexpected request: %s %#v\n%#v", "resources", req.Method, req.URL, req) + return nil, fmt.Errorf("unexpected request") + } + }), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), + } + buf := new(bytes.Buffer) + cmd := NewCmdResources(f, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "yaml") + opts := ResourcesOptions{ + Out: buf, + Local: true, + Limits: "cpu=200m,memory=512Mi", + ContainerSelector: "*"} + err := opts.Complete(f, cmd, input.args) + if err == nil { + err = opts.Validate() + } + if err == nil { + err = opts.Run() + } + assert.NoError(t, err) + } +} diff --git a/pkg/kubectl/cmd/set/set_serviceaccount.go b/pkg/kubectl/cmd/set/set_serviceaccount.go index 7a69c2fcf6e..db17d0ebcf1 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount.go @@ -23,11 +23,11 @@ import ( "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -61,14 +61,15 @@ type serviceAccountConfig struct { out io.Writer err io.Writer dryRun bool + cmd *cobra.Command shortOutput bool all bool record bool output string changeCause string local bool - saPrint func(obj runtime.Object) error - updatePodSpecForObject func(runtime.Object, func(*api.PodSpec) error) (bool, error) + PrintObject func(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error + updatePodSpecForObject func(runtime.Object, func(*v1.PodSpec) error) (bool, error) infos []*resource.Info serviceAccountName string } @@ -113,9 +114,9 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com saConfig.dryRun = cmdutil.GetDryRunFlag(cmd) saConfig.output = cmdutil.GetFlagString(cmd, "output") saConfig.updatePodSpecForObject = f.UpdatePodSpecForObject - saConfig.saPrint = func(obj runtime.Object) error { - return f.PrintObject(cmd, saConfig.local, saConfig.mapper, obj, saConfig.out) - } + saConfig.PrintObject = f.PrintObject + saConfig.cmd = cmd + cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -148,11 +149,11 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com func (saConfig *serviceAccountConfig) Run() error { patchErrs := []error{} patchFn := func(info *resource.Info) ([]byte, error) { - saConfig.updatePodSpecForObject(info.Object, func(podSpec *api.PodSpec) error { + saConfig.updatePodSpecForObject(info.VersionedObject, func(podSpec *v1.PodSpec) error { podSpec.ServiceAccountName = saConfig.serviceAccountName return nil }) - return runtime.Encode(saConfig.encoder, info.Object) + return runtime.Encode(saConfig.encoder, info.VersionedObject) } patches := CalculatePatches(saConfig.infos, saConfig.encoder, patchFn) for _, patch := range patches { @@ -162,7 +163,9 @@ func (saConfig *serviceAccountConfig) Run() error { continue } if saConfig.local || saConfig.dryRun { - saConfig.saPrint(patch.Info.Object) + if err := saConfig.PrintObject(saConfig.cmd, saConfig.local, saConfig.mapper, patch.Info.VersionedObject, saConfig.out); err != nil { + return err + } continue } patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) @@ -179,7 +182,14 @@ func (saConfig *serviceAccountConfig) Run() error { } } if len(saConfig.output) > 0 { - saConfig.saPrint(patched) + versionedObject, err := patch.Info.Mapping.ConvertToVersion(patched, patch.Info.Mapping.GroupVersionKind.GroupVersion()) + if err != nil { + return err + } + if err := saConfig.PrintObject(saConfig.cmd, saConfig.local, saConfig.mapper, versionedObject, saConfig.out); err != nil { + return err + } + continue } cmdutil.PrintSuccess(saConfig.mapper, saConfig.shortOutput, saConfig.out, info.Mapping.Resource, info.Name, saConfig.dryRun, "serviceaccount updated") } diff --git a/pkg/kubectl/cmd/set/set_serviceaccount_test.go b/pkg/kubectl/cmd/set/set_serviceaccount_test.go index 4b16b21d88f..c162d618c3a 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount_test.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount_test.go @@ -26,19 +26,21 @@ import ( "testing" "github.com/stretchr/testify/assert" - + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/batch" - api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/apis/extensions" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/printers" ) @@ -51,22 +53,21 @@ Example resource specifications include: ' ' ''` -func TestServiceAccountLocal(t *testing.T) { +func TestSetServiceAccountLocal(t *testing.T) { inputs := []struct { yaml string apiGroup string }{ - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replication.yaml", apiGroup: api.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/admin/daemon.yaml", apiGroup: extensions.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replicaset/redis-slave.yaml", apiGroup: extensions.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/job.yaml", apiGroup: batch.GroupName}, - {yaml: "../../../../test/fixtures/doc-yaml/user-guide/deployment.yaml", apiGroup: extensions.GroupName}, - {yaml: "../../../../examples/storage/minio/minio-distributed-statefulset.yaml", apiGroup: apps.GroupName}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replication.yaml", apiGroup: ""}, + {yaml: "../../../../test/fixtures/doc-yaml/admin/daemon.yaml", apiGroup: "extensions"}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replicaset/redis-slave.yaml", apiGroup: "extensions"}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/job.yaml", apiGroup: "batch"}, + {yaml: "../../../../test/fixtures/doc-yaml/user-guide/deployment.yaml", apiGroup: "extensions"}, } f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: schema.GroupVersion{Version: "v1"}, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) return nil, nil @@ -93,71 +94,232 @@ func TestServiceAccountLocal(t *testing.T) { } } -func TestServiceAccountRemote(t *testing.T) { +func TestSetServiceAccountMultiLocal(t *testing.T) { + testapi.Default = testapi.Groups[""] + f, tf, codec, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: ""}, + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) + return nil, nil + }), + } + tf.Namespace = "test" + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}} + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdServiceAccount(f, buf, buf) + cmd.SetOutput(buf) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("local", "true") + mapper, typer := f.Object() + tf.Printer = &printers.NamePrinter{Decoders: []runtime.Decoder{codec}, Typer: typer, Mapper: mapper} + opts := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{ + Filenames: []string{"../../../../test/fixtures/pkg/kubectl/cmd/set/multi-resource-yaml.yaml"}}, + out: buf, + local: true} + + err := opts.Complete(f, cmd, []string{serviceAccount}) + if err == nil { + err = opts.Run() + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedOut := "replicationcontrollers/first-rc\nreplicationcontrollers/second-rc\n" + if buf.String() != expectedOut { + t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String()) + } +} + +func TestSetServiceAccountRemote(t *testing.T) { inputs := []struct { - object runtime.Object - apiPrefix, apiGroup string - args []string + object runtime.Object + apiPrefix, apiGroup, apiVersion string + testAPIGroup string + args []string }{ { - object: &extensions.ReplicaSet{ - TypeMeta: metav1.TypeMeta{Kind: "ReplicaSet", APIVersion: legacyscheme.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + object: &extensionsv1beta1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: extensions.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", args: []string{"replicaset", "nginx", serviceAccount}, }, { - object: &extensions.DaemonSet{ - TypeMeta: metav1.TypeMeta{Kind: "DaemonSet", APIVersion: legacyscheme.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"replicaset", "nginx", serviceAccount}, + }, + { + object: &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.ReplicaSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"replicaset", "nginx", serviceAccount}, + }, + { + object: &extensionsv1beta1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: extensions.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", args: []string{"daemonset", "nginx", serviceAccount}, }, { - object: &api.ReplicationController{ - TypeMeta: metav1.TypeMeta{Kind: "ReplicationController", APIVersion: legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.String()}, + object: &appsv1beta2.DaemonSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/api", apiGroup: api.GroupName, - args: []string{"replicationcontroller", "nginx", serviceAccount}}, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"daemonset", "nginx", serviceAccount}, + }, { - object: &extensions.Deployment{ - TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: legacyscheme.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, + object: &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: extensions.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"daemonset", "nginx", serviceAccount}, + }, + { + object: &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "extensions", apiVersion: "v1beta1", args: []string{"deployment", "nginx", serviceAccount}, }, { - object: &batch.Job{ - TypeMeta: metav1.TypeMeta{Kind: "Job", APIVersion: legacyscheme.Registry.GroupOrDie(batch.GroupName).GroupVersion.String()}, + object: &appsv1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: batch.GroupName, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"deployment", "nginx", serviceAccount}, + }, + { + object: &appsv1beta2.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"deployment", "nginx", serviceAccount}, + }, + { + object: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "extensions", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"deployment", "nginx", serviceAccount}, + }, + { + object: &appsv1beta1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta1", + args: []string{"statefulset", "nginx", serviceAccount}, + }, + { + object: &appsv1beta2.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1beta2", + args: []string{"statefulset", "nginx", serviceAccount}, + }, + { + object: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1.StatefulSetSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + testAPIGroup: "apps", + apiPrefix: "/apis", apiGroup: "apps", apiVersion: "v1", + args: []string{"statefulset", "nginx", serviceAccount}, + }, + { + object: &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + }, + testAPIGroup: "batch", + apiPrefix: "/apis", apiGroup: "batch", apiVersion: "v1", args: []string{"job", "nginx", serviceAccount}, }, { - object: &apps.StatefulSet{ - TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: legacyscheme.Registry.GroupOrDie(apps.GroupName).GroupVersion.String()}, + object: &v1.ReplicationController{ ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, - apiPrefix: "/apis", apiGroup: apps.GroupName, - args: []string{"statefulset", "nginx", serviceAccount}, + testAPIGroup: "", + apiPrefix: "/api", apiGroup: "", apiVersion: "v1", + args: []string{"replicationcontroller", "nginx", serviceAccount}, }, } for _, input := range inputs { - - groupVersion := legacyscheme.Registry.GroupOrDie(input.apiGroup).GroupVersion - testapi.Default = testapi.Groups[input.apiGroup] - f, tf, codec, _ := cmdtesting.NewAPIFactory() + groupVersion := schema.GroupVersion{Group: input.apiGroup, Version: input.apiVersion} + testapi.Default = testapi.Groups[input.testAPIGroup] + f, tf, _, ns := cmdtesting.NewAPIFactory() + codec := scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(groupVersion), scheme.Codecs.UniversalDecoder(groupVersion), groupVersion, groupVersion) tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) tf.Namespace = "test" tf.CategoryExpander = resource.LegacyCategoryExpander tf.Client = &fake.RESTClient{ - GroupVersion: legacyscheme.Registry.GroupOrDie(input.apiGroup).GroupVersion, - NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), + GroupVersion: groupVersion, + NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) switch p, m := req.URL.Path, req.Method; { @@ -179,13 +341,12 @@ func TestServiceAccountRemote(t *testing.T) { return nil, fmt.Errorf("unexpected request") } }), - VersionedAPIPath: path.Join(input.apiPrefix, groupVersion.String()), + VersionedAPIPath: path.Join(input.apiPrefix, testapi.Default.GroupVersion().String()), } out := new(bytes.Buffer) cmd := NewCmdServiceAccount(f, out, out) cmd.SetOutput(out) cmd.Flags().Set("output", "yaml") - saConfig := serviceAccountConfig{ out: out, local: false} diff --git a/pkg/kubectl/cmd/testing/BUILD b/pkg/kubectl/cmd/testing/BUILD index bb63c6567b4..7a6db8df5ff 100644 --- a/pkg/kubectl/cmd/testing/BUILD +++ b/pkg/kubectl/cmd/testing/BUILD @@ -27,6 +27,7 @@ go_library( "//pkg/printers:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index ddda6aa15d9..68ccf915afd 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -448,7 +449,7 @@ func (f *FakeFactory) ApproximatePodTemplateForObject(obj runtime.Object) (*api. return f.ApproximatePodTemplateForObject(obj) } -func (f *FakeFactory) UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) { +func (f *FakeFactory) UpdatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) { return false, nil } diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index 9c1165e87f1..e6fcfeead8b 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -48,9 +48,13 @@ go_library( "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", "//vendor/k8s.io/api/batch/v2alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/kubectl/cmd/util/env/BUILD b/pkg/kubectl/cmd/util/env/BUILD index a3820a1c183..c6084cfea45 100644 --- a/pkg/kubectl/cmd/util/env/BUILD +++ b/pkg/kubectl/cmd/util/env/BUILD @@ -10,13 +10,13 @@ go_library( importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/env", visibility = ["//visibility:public"], deps = [ - "//pkg/api/resource:go_default_library", - "//pkg/apis/core:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/api/v1/resource:go_default_library", "//pkg/fieldpath:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/env/env_parse.go b/pkg/kubectl/cmd/util/env/env_parse.go index 06bd388069d..1d1686af647 100644 --- a/pkg/kubectl/cmd/util/env/env_parse.go +++ b/pkg/kubectl/cmd/util/env/env_parse.go @@ -24,8 +24,8 @@ import ( "regexp" "strings" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" - api "k8s.io/kubernetes/pkg/apis/core" ) // Env returns an environment variable if not nil, or a default value. @@ -83,8 +83,8 @@ func SplitEnvironmentFromResources(args []string) (resources, envArgs []string, // parseIntoEnvVar parses the list of key-value pairs into kubernetes EnvVar. // envVarType is for making errors more specific to user intentions. -func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]api.EnvVar, []string, error) { - env := []api.EnvVar{} +func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]v1.EnvVar, []string, error) { + env := []v1.EnvVar{} exists := sets.NewString() var remove []string for _, envSpec := range spec { @@ -106,7 +106,7 @@ func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) return nil, nil, fmt.Errorf("invalid %s: %v", envVarType, envSpec) } exists.Insert(parts[0]) - env = append(env, api.EnvVar{ + env = append(env, v1.EnvVar{ Name: parts[0], Value: parts[1], }) @@ -126,12 +126,12 @@ func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) // ParseEnv parses the elements of the first argument looking for environment variables in key=value form and, if one of those values is "-", it also scans the reader. // The same environment variable cannot be both modified and removed in the same command. -func ParseEnv(spec []string, defaultReader io.Reader) ([]api.EnvVar, []string, error) { +func ParseEnv(spec []string, defaultReader io.Reader) ([]v1.EnvVar, []string, error) { return parseIntoEnvVar(spec, defaultReader, "environment variable") } -func readEnv(r io.Reader, envVarType string) ([]api.EnvVar, error) { - env := []api.EnvVar{} +func readEnv(r io.Reader, envVarType string) ([]v1.EnvVar, error) { + env := []v1.EnvVar{} scanner := bufio.NewScanner(r) for scanner.Scan() { envSpec := scanner.Text() @@ -143,7 +143,7 @@ func readEnv(r io.Reader, envVarType string) ([]api.EnvVar, error) { if len(parts) != 2 { return nil, fmt.Errorf("invalid %s: %v", envVarType, envSpec) } - env = append(env, api.EnvVar{ + env = append(env, v1.EnvVar{ Name: parts[0], Value: parts[1], }) diff --git a/pkg/kubectl/cmd/util/env/env_parse_test.go b/pkg/kubectl/cmd/util/env/env_parse_test.go index f5e780f4d41..32be9833c76 100644 --- a/pkg/kubectl/cmd/util/env/env_parse_test.go +++ b/pkg/kubectl/cmd/util/env/env_parse_test.go @@ -84,7 +84,7 @@ func ExampleParseEnv_good() { ss := []string{"ENV=VARIABLE", "AND=ANOTHER", "REMOVE-", "-"} fmt.Println(ParseEnv(ss, r)) // Output: - // [{ENV VARIABLE } {AND ANOTHER } {FROM READER }] [REMOVE] + // [{ENV VARIABLE nil} {AND ANOTHER nil} {FROM READER nil}] [REMOVE] } func ExampleParseEnv_bad() { diff --git a/pkg/kubectl/cmd/util/env/env_resolve.go b/pkg/kubectl/cmd/util/env/env_resolve.go index 92717924e50..d663f730471 100644 --- a/pkg/kubectl/cmd/util/env/env_resolve.go +++ b/pkg/kubectl/cmd/util/env/env_resolve.go @@ -19,30 +19,30 @@ package env import ( "fmt" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api/resource" - api "k8s.io/kubernetes/pkg/apis/core" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/api/v1/resource" "k8s.io/kubernetes/pkg/fieldpath" ) // ResourceStore defines a new resource store data structure. type ResourceStore struct { - SecretStore map[string]*api.Secret - ConfigMapStore map[string]*api.ConfigMap + SecretStore map[string]*v1.Secret + ConfigMapStore map[string]*v1.ConfigMap } // NewResourceStore returns a pointer to a new resource store data structure. func NewResourceStore() *ResourceStore { return &ResourceStore{ - SecretStore: make(map[string]*api.Secret), - ConfigMapStore: make(map[string]*api.ConfigMap), + SecretStore: make(map[string]*v1.Secret), + ConfigMapStore: make(map[string]*v1.ConfigMap), } } // getSecretRefValue returns the value of a secret in the supplied namespace -func getSecretRefValue(client clientset.Interface, namespace string, store *ResourceStore, secretSelector *api.SecretKeySelector) (string, error) { +func getSecretRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, secretSelector *v1.SecretKeySelector) (string, error) { secret, ok := store.SecretStore[secretSelector.Name] if !ok { var err error @@ -60,7 +60,7 @@ func getSecretRefValue(client clientset.Interface, namespace string, store *Reso } // getConfigMapRefValue returns the value of a configmap in the supplied namespace -func getConfigMapRefValue(client clientset.Interface, namespace string, store *ResourceStore, configMapSelector *api.ConfigMapKeySelector) (string, error) { +func getConfigMapRefValue(client kubernetes.Interface, namespace string, store *ResourceStore, configMapSelector *v1.ConfigMapKeySelector) (string, error) { configMap, ok := store.ConfigMapStore[configMapSelector.Name] if !ok { var err error @@ -77,17 +77,17 @@ func getConfigMapRefValue(client clientset.Interface, namespace string, store *R } // getFieldRef returns the value of the supplied path in the given object -func getFieldRef(obj runtime.Object, from *api.EnvVarSource) (string, error) { +func getFieldRef(obj runtime.Object, from *v1.EnvVarSource) (string, error) { return fieldpath.ExtractFieldPathAsString(obj, from.FieldRef.FieldPath) } // getResourceFieldRef returns the value of a resource in the given container -func getResourceFieldRef(from *api.EnvVarSource, c *api.Container) (string, error) { +func getResourceFieldRef(from *v1.EnvVarSource, c *v1.Container) (string, error) { return resource.ExtractContainerResourceValue(from.ResourceFieldRef, c) } // GetEnvVarRefValue returns the value referenced by the supplied EnvVarSource given the other supplied information. -func GetEnvVarRefValue(kc clientset.Interface, ns string, store *ResourceStore, from *api.EnvVarSource, obj runtime.Object, c *api.Container) (string, error) { +func GetEnvVarRefValue(kc kubernetes.Interface, ns string, store *ResourceStore, from *v1.EnvVarSource, obj runtime.Object, c *v1.Container) (string, error) { if from.SecretKeyRef != nil { return getSecretRefValue(kc, ns, store, from.SecretKeyRef) } @@ -108,7 +108,7 @@ func GetEnvVarRefValue(kc clientset.Interface, ns string, store *ResourceStore, } // GetEnvVarRefString returns a text description of whichever field is set within the supplied EnvVarSource argument. -func GetEnvVarRefString(from *api.EnvVarSource) string { +func GetEnvVarRefString(from *v1.EnvVarSource) string { if from.ConfigMapKeyRef != nil { return fmt.Sprintf("configmap %s, key %s", from.ConfigMapKeyRef.Name, from.ConfigMapKeyRef.Key) } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 77972bdf687..4fefd3b5d7a 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -116,7 +116,7 @@ type ClientAccessFactory interface { // UpdatePodSpecForObject will call the provided function on the pod spec this object supports, // return false if no pod spec is supported, or return an error. - UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) + UpdatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) // MapBasedSelectorForObject returns the map-based selector associated with the provided object. If a // new set-based selector is provided, an error is returned if the selector cannot be converted to a diff --git a/pkg/kubectl/cmd/util/factory_builder.go b/pkg/kubectl/cmd/util/factory_builder.go index e62280a7358..97b2cb14b41 100644 --- a/pkg/kubectl/cmd/util/factory_builder.go +++ b/pkg/kubectl/cmd/util/factory_builder.go @@ -125,8 +125,13 @@ func (f *ring2Factory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta if err != nil { return err } + // Prefer the existing external version if specified + var preferredVersion []string + if gvks[0].Version != "" && gvks[0].Version != runtime.APIVersionInternal { + preferredVersion = []string{gvks[0].Version} + } - mapping, err := mapper.RESTMapping(gvks[0].GroupKind()) + mapping, err := mapper.RESTMapping(gvks[0].GroupKind(), preferredVersion...) if err != nil { return err } diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index 98915185fcf..5c02200f33b 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -29,11 +29,17 @@ import ( "strings" "time" + "k8s.io/api/core/v1" + "github.com/spf13/cobra" "github.com/spf13/pflag" + appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" batchv2alpha1 "k8s.io/api/batch/v2alpha1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -47,7 +53,6 @@ import ( "k8s.io/client-go/util/homedir" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" @@ -226,25 +231,49 @@ func (f *ring0Factory) JSONEncoder() runtime.Encoder { return legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...) } -func (f *ring0Factory) UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) { +func (f *ring0Factory) UpdatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) { // TODO: replace with a swagger schema based approach (identify pod template via schema introspection) switch t := obj.(type) { - case *api.Pod: + case *v1.Pod: return true, fn(&t.Spec) - case *api.ReplicationController: + // ReplicationController + case *v1.ReplicationController: if t.Spec.Template == nil { - t.Spec.Template = &api.PodTemplateSpec{} + t.Spec.Template = &v1.PodTemplateSpec{} } return true, fn(&t.Spec.Template.Spec) - case *extensions.Deployment: + // Deployment + case *extensionsv1beta1.Deployment: return true, fn(&t.Spec.Template.Spec) - case *extensions.DaemonSet: + case *appsv1beta1.Deployment: return true, fn(&t.Spec.Template.Spec) - case *extensions.ReplicaSet: + case *appsv1beta2.Deployment: return true, fn(&t.Spec.Template.Spec) - case *apps.StatefulSet: + case *appsv1.Deployment: return true, fn(&t.Spec.Template.Spec) - case *batch.Job: + // DaemonSet + case *extensionsv1beta1.DaemonSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1beta2.DaemonSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1.DaemonSet: + return true, fn(&t.Spec.Template.Spec) + // ReplicaSet + case *extensionsv1beta1.ReplicaSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1beta2.ReplicaSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1.ReplicaSet: + return true, fn(&t.Spec.Template.Spec) + // StatefulSet + case *appsv1beta1.StatefulSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1beta2.StatefulSet: + return true, fn(&t.Spec.Template.Spec) + case *appsv1.StatefulSet: + return true, fn(&t.Spec.Template.Spec) + // Job + case *batchv1.Job: return true, fn(&t.Spec.Template.Spec) default: return false, fmt.Errorf("the object is not a pod or does not have a pod template") diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 5aadc0e53d2..a6656e86f12 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -31,7 +31,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation" - api "k8s.io/kubernetes/pkg/apis/core" ) type DeploymentV1Beta1 struct{} @@ -608,31 +607,6 @@ 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 == "" { - return nil, nil - } - - result := api.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 := api.ResourceName(parts[0]) - resourceQuantity, err := resource.ParseQuantity(parts[1]) - if err != nil { - return nil, err - } - result[resourceName] = resourceQuantity - } - return result, nil -} - // populateResourceListV1 takes strings of form =,= // and returns ResourceList. func populateResourceListV1(spec string) (v1.ResourceList, error) { @@ -658,23 +632,6 @@ func populateResourceListV1(spec string) (v1.ResourceList, error) { 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"]) - if err != nil { - return result, err - } - result.Limits = limits - requests, err := populateResourceList(params["requests"]) - if err != nil { - return result, err - } - result.Requests = requests - return result, nil -} - // HandleResourceRequirementsV1 parses the limits and requests parameters if specified // and returns ResourceRequirements. func HandleResourceRequirementsV1(params map[string]string) (v1.ResourceRequirements, error) {