mirror of https://github.com/k3s-io/k3s
Merge pull request #72276 from vithati/users/vithati/kubectlissue503
Allow setting images for 'InitContainers' through kubectl set command.pull/564/head
commit
af31a202ea
|
@ -216,38 +216,20 @@ func (o *SetImageOptions) Run() error {
|
||||||
allErrs := []error{}
|
allErrs := []error{}
|
||||||
|
|
||||||
patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
|
patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
|
||||||
transformed := false
|
|
||||||
_, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
|
_, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
|
||||||
for name, image := range o.ContainerImages {
|
for name, image := range o.ContainerImages {
|
||||||
var (
|
resolvedImageName, err := o.ResolveImage(image)
|
||||||
containerFound bool
|
if err != nil {
|
||||||
err error
|
allErrs = append(allErrs, fmt.Errorf("error: unable to resolve image %q for container %q: %v", image, name, err))
|
||||||
resolved string
|
if name == "*" {
|
||||||
)
|
break
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !transformed {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// record this change (for rollout history)
|
// record this change (for rollout history)
|
||||||
if err := o.Recorder.Record(obj); err != nil {
|
if err := o.Recorder.Record(obj); err != nil {
|
||||||
klog.V(4).Infof("error recording current command: %v", err)
|
klog.V(4).Infof("error recording current command: %v", err)
|
||||||
|
@ -300,6 +279,18 @@ func (o *SetImageOptions) Run() error {
|
||||||
return utilerrors.NewAggregate(allErrs)
|
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
|
// getResourcesAndImages retrieves resources and container name:images pair from given args
|
||||||
func getResourcesAndImages(args []string) (resources []string, containerImages map[string]string, err error) {
|
func getResourcesAndImages(args []string) (resources []string, containerImages map[string]string, err error) {
|
||||||
pairType := "image"
|
pairType := "image"
|
||||||
|
|
|
@ -221,6 +221,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -242,6 +248,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -263,6 +275,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -284,6 +302,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -305,6 +329,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -326,6 +356,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -347,6 +383,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -368,6 +410,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -389,6 +437,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -410,6 +464,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -431,6 +491,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -452,6 +518,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -473,6 +545,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -494,6 +572,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
Image: "nginx",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
InitContainers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -515,6 +599,12 @@ func TestSetImageRemote(t *testing.T) {
|
||||||
Image: "nginx",
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue