diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index 5fe8b0516f..225d784735 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -216,38 +216,20 @@ func (o *SetImageOptions) Run() error { allErrs := []error{} patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) { - transformed := false _, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error { for name, image := range o.ContainerImages { - var ( - containerFound bool - err error - resolved string - ) - // Find the container to update, and update its image - for i, c := range spec.Containers { - if c.Name == name || name == "*" { - containerFound = true - if len(resolved) == 0 { - if resolved, err = o.ResolveImage(image); err != nil { - allErrs = append(allErrs, fmt.Errorf("error: unable to resolve image %q for container %q: %v", image, name, err)) - // Do not loop again if the image resolving failed for wildcard case as we - // will report the same error again for the next container. - if name == "*" { - break - } - continue - } - } - if spec.Containers[i].Image != resolved { - spec.Containers[i].Image = resolved - // Perform updates - transformed = true - } + resolvedImageName, err := o.ResolveImage(image) + if err != nil { + allErrs = append(allErrs, fmt.Errorf("error: unable to resolve image %q for container %q: %v", image, name, err)) + if name == "*" { + break } + continue } - // Add a new container if not found - if !containerFound { + + initContainerFound := setImage(spec.InitContainers, name, resolvedImageName) + containerFound := setImage(spec.Containers, name, resolvedImageName) + if !containerFound && !initContainerFound { allErrs = append(allErrs, fmt.Errorf("error: unable to find container named %q", name)) } } @@ -256,9 +238,6 @@ func (o *SetImageOptions) Run() error { if err != nil { return nil, err } - if !transformed { - return nil, nil - } // record this change (for rollout history) if err := o.Recorder.Record(obj); err != nil { klog.V(4).Infof("error recording current command: %v", err) @@ -300,6 +279,18 @@ func (o *SetImageOptions) Run() error { return utilerrors.NewAggregate(allErrs) } +func setImage(containers []v1.Container, containerName string, image string) bool { + containerFound := false + // Find the container to update, and update its image + for i, c := range containers { + if c.Name == containerName || containerName == "*" { + containerFound = true + containers[i].Image = image + } + } + return containerFound +} + // getResourcesAndImages retrieves resources and container name:images pair from given args func getResourcesAndImages(args []string) (resources []string, containerImages map[string]string, err error) { pairType := "image" diff --git a/pkg/kubectl/cmd/set/set_image_test.go b/pkg/kubectl/cmd/set/set_image_test.go index 4cf50247ea..a87150c991 100644 --- a/pkg/kubectl/cmd/set/set_image_test.go +++ b/pkg/kubectl/cmd/set/set_image_test.go @@ -221,6 +221,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -242,6 +248,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -263,6 +275,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -284,6 +302,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -305,6 +329,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -326,6 +356,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -347,6 +383,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -368,6 +410,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -389,6 +437,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -410,6 +464,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -431,6 +491,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -452,6 +518,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -473,6 +545,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -494,6 +572,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -515,6 +599,12 @@ func TestSetImageRemote(t *testing.T) { Image: "nginx", }, }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, }, }, }, @@ -572,3 +662,116 @@ func TestSetImageRemote(t *testing.T) { }) } } + +func TestSetImageRemoteWithSpecificContainers(t *testing.T) { + inputs := []struct { + name string + object runtime.Object + groupVersion schema.GroupVersion + path string + args []string + }{ + { + name: "set container image only", + object: &extensionsv1beta1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: extensionsv1beta1.ReplicaSetSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + InitContainers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, + }, + }, + }, + }, + groupVersion: extensionsv1beta1.SchemeGroupVersion, + path: "/namespaces/test/replicasets/nginx", + args: []string{"replicaset", "nginx", "nginx=thingy"}, + }, + { + name: "set initContainer image only", + object: &appsv1beta2.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, + Spec: appsv1beta2.ReplicaSetSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + }, + }, + InitContainers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + groupVersion: appsv1beta2.SchemeGroupVersion, + path: "/namespaces/test/replicasets/nginx", + args: []string{"replicaset", "nginx", "nginx=thingy"}, + }, + } + for _, input := range inputs { + t.Run(input.name, func(t *testing.T) { + tf := cmdtesting.NewTestFactory().WithNamespace("test") + defer tf.Cleanup() + + tf.Client = &fake.RESTClient{ + GroupVersion: input.groupVersion, + NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == input.path && m == http.MethodGet: + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil + case p == input.path && 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"+`","name":`+`"nginx"`, fmt.Sprintf("image not updated for %#v", input.object)) + assert.NotContains(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"busybox"`, fmt.Sprintf("image updated for %#v", input.object)) + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(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") + } + }), + } + + outputFormat := "yaml" + + streams := genericclioptions.NewTestIOStreamsDiscard() + cmd := NewCmdImage(tf, streams) + cmd.Flags().Set("output", outputFormat) + opts := SetImageOptions{ + PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme), + + Local: false, + IOStreams: streams, + } + err := opts.Complete(tf, cmd, input.args) + assert.NoError(t, err) + err = opts.Run() + assert.NoError(t, err) + }) + } +}