Merge pull request #72276 from vithati/users/vithati/kubectlissue503

Allow setting images for 'InitContainers' through kubectl set command.
pull/564/head
Kubernetes Prow Robot 2019-01-17 05:18:04 -08:00 committed by GitHub
commit af31a202ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 225 additions and 31 deletions

View File

@ -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"

View File

@ -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)
})
}
}