...

Source file src/k8s.io/kubectl/pkg/cmd/set/set_env_test.go

Documentation: k8s.io/kubectl/pkg/cmd/set

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package set
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	appsv1 "k8s.io/api/apps/v1"
    28  	appsv1beta1 "k8s.io/api/apps/v1beta1"
    29  	appsv1beta2 "k8s.io/api/apps/v1beta2"
    30  	batchv1 "k8s.io/api/batch/v1"
    31  	corev1 "k8s.io/api/core/v1"
    32  	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/cli-runtime/pkg/genericclioptions"
    37  	"k8s.io/cli-runtime/pkg/genericiooptions"
    38  	"k8s.io/cli-runtime/pkg/resource"
    39  	restclient "k8s.io/client-go/rest"
    40  	"k8s.io/client-go/rest/fake"
    41  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    42  	"k8s.io/kubectl/pkg/scheme"
    43  )
    44  
    45  func TestSetEnvLocal(t *testing.T) {
    46  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
    47  	defer tf.Cleanup()
    48  
    49  	tf.Client = &fake.RESTClient{
    50  		GroupVersion:         schema.GroupVersion{Version: ""},
    51  		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
    52  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
    53  			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
    54  			return nil, nil
    55  		}),
    56  	}
    57  	tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
    58  	outputFormat := "name"
    59  
    60  	streams, _, buf, bufErr := genericiooptions.NewTestIOStreams()
    61  	opts := NewEnvOptions(streams)
    62  	opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
    63  	opts.FilenameOptions = resource.FilenameOptions{
    64  		Filenames: []string{"../../../testdata/controller.yaml"},
    65  	}
    66  	opts.Local = true
    67  
    68  	err := opts.Complete(tf, NewCmdEnv(tf, streams), []string{"env=prod"})
    69  	assert.NoError(t, err)
    70  	err = opts.Validate()
    71  	assert.NoError(t, err)
    72  	err = opts.RunEnv()
    73  	assert.NoError(t, err)
    74  	if bufErr.Len() > 0 {
    75  		t.Errorf("unexpected error: %s", bufErr.String())
    76  	}
    77  	if !strings.Contains(buf.String(), "replicationcontroller/cassandra") {
    78  		t.Errorf("did not set env: %s", buf.String())
    79  	}
    80  }
    81  
    82  func TestSetEnvLocalNamespace(t *testing.T) {
    83  	tf := cmdtesting.NewTestFactory()
    84  	defer tf.Cleanup()
    85  
    86  	tf.Client = &fake.RESTClient{
    87  		GroupVersion:         schema.GroupVersion{Version: ""},
    88  		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
    89  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
    90  			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
    91  			return nil, nil
    92  		}),
    93  	}
    94  	tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
    95  	outputFormat := "yaml"
    96  
    97  	streams, _, buf, bufErr := genericiooptions.NewTestIOStreams()
    98  	opts := NewEnvOptions(streams)
    99  	opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
   100  	opts.FilenameOptions = resource.FilenameOptions{
   101  		Filenames: []string{"../../../testdata/set/namespaced-resource.yaml"},
   102  	}
   103  	opts.Local = true
   104  
   105  	err := opts.Complete(tf, NewCmdEnv(tf, streams), []string{"env=prod"})
   106  	assert.NoError(t, err)
   107  	err = opts.Validate()
   108  	assert.NoError(t, err)
   109  	err = opts.RunEnv()
   110  	assert.NoError(t, err)
   111  	if bufErr.Len() > 0 {
   112  		t.Errorf("unexpected error: %s", bufErr.String())
   113  	}
   114  	if !strings.Contains(buf.String(), "namespace: existing-ns") {
   115  		t.Errorf("did not set env: %s", buf.String())
   116  	}
   117  }
   118  
   119  func TestSetMultiResourcesEnvLocal(t *testing.T) {
   120  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   121  	defer tf.Cleanup()
   122  
   123  	tf.Client = &fake.RESTClient{
   124  		GroupVersion:         schema.GroupVersion{Version: ""},
   125  		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   126  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   127  			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   128  			return nil, nil
   129  		}),
   130  	}
   131  	tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
   132  
   133  	outputFormat := "name"
   134  	streams, _, buf, bufErr := genericiooptions.NewTestIOStreams()
   135  	opts := NewEnvOptions(streams)
   136  	opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
   137  	opts.FilenameOptions = resource.FilenameOptions{
   138  		Filenames: []string{"../../../testdata/set/multi-resource-yaml.yaml"},
   139  	}
   140  	opts.Local = true
   141  
   142  	err := opts.Complete(tf, NewCmdEnv(tf, streams), []string{"env=prod"})
   143  	assert.NoError(t, err)
   144  	err = opts.Validate()
   145  	assert.NoError(t, err)
   146  	err = opts.RunEnv()
   147  	assert.NoError(t, err)
   148  	if bufErr.Len() > 0 {
   149  		t.Errorf("unexpected error: %s", bufErr.String())
   150  	}
   151  	expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n"
   152  	if buf.String() != expectedOut {
   153  		t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String())
   154  	}
   155  }
   156  
   157  func TestSetEnvRemote(t *testing.T) {
   158  	inputs := []struct {
   159  		name         string
   160  		object       runtime.Object
   161  		groupVersion schema.GroupVersion
   162  		path         string
   163  		args         []string
   164  	}{
   165  		{
   166  			name: "test extensions.v1beta1 replicaset",
   167  			object: &extensionsv1beta1.ReplicaSet{
   168  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   169  				Spec: extensionsv1beta1.ReplicaSetSpec{
   170  					Template: corev1.PodTemplateSpec{
   171  						Spec: corev1.PodSpec{
   172  							Containers: []corev1.Container{
   173  								{
   174  									Name:  "nginx",
   175  									Image: "nginx",
   176  								},
   177  							},
   178  						},
   179  					},
   180  				},
   181  			},
   182  			groupVersion: extensionsv1beta1.SchemeGroupVersion,
   183  			path:         "/namespaces/test/replicasets/nginx",
   184  			args:         []string{"replicaset", "nginx", "env=prod"},
   185  		},
   186  		{
   187  			name: "test apps.v1beta2 replicaset",
   188  			object: &appsv1beta2.ReplicaSet{
   189  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   190  				Spec: appsv1beta2.ReplicaSetSpec{
   191  					Template: corev1.PodTemplateSpec{
   192  						Spec: corev1.PodSpec{
   193  							Containers: []corev1.Container{
   194  								{
   195  									Name:  "nginx",
   196  									Image: "nginx",
   197  								},
   198  							},
   199  						},
   200  					},
   201  				},
   202  			},
   203  			groupVersion: appsv1beta2.SchemeGroupVersion,
   204  			path:         "/namespaces/test/replicasets/nginx",
   205  			args:         []string{"replicaset", "nginx", "env=prod"},
   206  		},
   207  		{
   208  			name: "test appsv1 replicaset",
   209  			object: &appsv1.ReplicaSet{
   210  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   211  				Spec: appsv1.ReplicaSetSpec{
   212  					Template: corev1.PodTemplateSpec{
   213  						Spec: corev1.PodSpec{
   214  							Containers: []corev1.Container{
   215  								{
   216  									Name:  "nginx",
   217  									Image: "nginx",
   218  								},
   219  							},
   220  						},
   221  					},
   222  				},
   223  			},
   224  			groupVersion: appsv1.SchemeGroupVersion,
   225  			path:         "/namespaces/test/replicasets/nginx",
   226  			args:         []string{"replicaset", "nginx", "env=prod"},
   227  		},
   228  		{
   229  			name: "test extensions.v1beta1 daemonset",
   230  			object: &extensionsv1beta1.DaemonSet{
   231  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   232  				Spec: extensionsv1beta1.DaemonSetSpec{
   233  					Template: corev1.PodTemplateSpec{
   234  						Spec: corev1.PodSpec{
   235  							Containers: []corev1.Container{
   236  								{
   237  									Name:  "nginx",
   238  									Image: "nginx",
   239  								},
   240  							},
   241  						},
   242  					},
   243  				},
   244  			},
   245  			groupVersion: extensionsv1beta1.SchemeGroupVersion,
   246  			path:         "/namespaces/test/daemonsets/nginx",
   247  			args:         []string{"daemonset", "nginx", "env=prod"},
   248  		},
   249  		{
   250  			name: "test appsv1beta2 daemonset",
   251  			object: &appsv1beta2.DaemonSet{
   252  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   253  				Spec: appsv1beta2.DaemonSetSpec{
   254  					Template: corev1.PodTemplateSpec{
   255  						Spec: corev1.PodSpec{
   256  							Containers: []corev1.Container{
   257  								{
   258  									Name:  "nginx",
   259  									Image: "nginx",
   260  								},
   261  							},
   262  						},
   263  					},
   264  				},
   265  			},
   266  			groupVersion: appsv1beta2.SchemeGroupVersion,
   267  			path:         "/namespaces/test/daemonsets/nginx",
   268  			args:         []string{"daemonset", "nginx", "env=prod"},
   269  		},
   270  		{
   271  			name: "test appsv1 daemonset",
   272  			object: &appsv1.DaemonSet{
   273  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   274  				Spec: appsv1.DaemonSetSpec{
   275  					Template: corev1.PodTemplateSpec{
   276  						Spec: corev1.PodSpec{
   277  							Containers: []corev1.Container{
   278  								{
   279  									Name:  "nginx",
   280  									Image: "nginx",
   281  								},
   282  							},
   283  						},
   284  					},
   285  				},
   286  			},
   287  			groupVersion: appsv1.SchemeGroupVersion,
   288  			path:         "/namespaces/test/daemonsets/nginx",
   289  			args:         []string{"daemonset", "nginx", "env=prod"},
   290  		},
   291  		{
   292  			name: "test extensions.v1beta1 deployment",
   293  			object: &extensionsv1beta1.Deployment{
   294  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   295  				Spec: extensionsv1beta1.DeploymentSpec{
   296  					Template: corev1.PodTemplateSpec{
   297  						Spec: corev1.PodSpec{
   298  							Containers: []corev1.Container{
   299  								{
   300  									Name:  "nginx",
   301  									Image: "nginx",
   302  								},
   303  							},
   304  						},
   305  					},
   306  				},
   307  			},
   308  			groupVersion: extensionsv1beta1.SchemeGroupVersion,
   309  			path:         "/namespaces/test/deployments/nginx",
   310  			args:         []string{"deployment", "nginx", "env=prod"},
   311  		},
   312  		{
   313  			name: "test appsv1beta1 deployment",
   314  			object: &appsv1beta1.Deployment{
   315  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   316  				Spec: appsv1beta1.DeploymentSpec{
   317  					Template: corev1.PodTemplateSpec{
   318  						Spec: corev1.PodSpec{
   319  							Containers: []corev1.Container{
   320  								{
   321  									Name:  "nginx",
   322  									Image: "nginx",
   323  								},
   324  							},
   325  						},
   326  					},
   327  				},
   328  			},
   329  			groupVersion: appsv1beta1.SchemeGroupVersion,
   330  			path:         "/namespaces/test/deployments/nginx",
   331  			args:         []string{"deployment", "nginx", "env=prod"},
   332  		},
   333  		{
   334  			name: "test appsv1beta2 deployment",
   335  			object: &appsv1beta2.Deployment{
   336  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   337  				Spec: appsv1beta2.DeploymentSpec{
   338  					Template: corev1.PodTemplateSpec{
   339  						Spec: corev1.PodSpec{
   340  							Containers: []corev1.Container{
   341  								{
   342  									Name:  "nginx",
   343  									Image: "nginx",
   344  								},
   345  							},
   346  						},
   347  					},
   348  				},
   349  			},
   350  			groupVersion: appsv1beta2.SchemeGroupVersion,
   351  			path:         "/namespaces/test/deployments/nginx",
   352  			args:         []string{"deployment", "nginx", "env=prod"},
   353  		},
   354  		{
   355  			name: "test appsv1 deployment",
   356  			object: &appsv1.Deployment{
   357  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   358  				Spec: appsv1.DeploymentSpec{
   359  					Template: corev1.PodTemplateSpec{
   360  						Spec: corev1.PodSpec{
   361  							Containers: []corev1.Container{
   362  								{
   363  									Name:  "nginx",
   364  									Image: "nginx",
   365  								},
   366  							},
   367  						},
   368  					},
   369  				},
   370  			},
   371  			groupVersion: appsv1.SchemeGroupVersion,
   372  			path:         "/namespaces/test/deployments/nginx",
   373  			args:         []string{"deployment", "nginx", "env=prod"},
   374  		},
   375  		{
   376  			name: "test appsv1beta1 statefulset",
   377  			object: &appsv1beta1.StatefulSet{
   378  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   379  				Spec: appsv1beta1.StatefulSetSpec{
   380  					Template: corev1.PodTemplateSpec{
   381  						Spec: corev1.PodSpec{
   382  							Containers: []corev1.Container{
   383  								{
   384  									Name:  "nginx",
   385  									Image: "nginx",
   386  								},
   387  							},
   388  						},
   389  					},
   390  				},
   391  			},
   392  			groupVersion: appsv1beta1.SchemeGroupVersion,
   393  			path:         "/namespaces/test/statefulsets/nginx",
   394  			args:         []string{"statefulset", "nginx", "env=prod"},
   395  		},
   396  		{
   397  			name: "test appsv1beta2 statefulset",
   398  			object: &appsv1beta2.StatefulSet{
   399  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   400  				Spec: appsv1beta2.StatefulSetSpec{
   401  					Template: corev1.PodTemplateSpec{
   402  						Spec: corev1.PodSpec{
   403  							Containers: []corev1.Container{
   404  								{
   405  									Name:  "nginx",
   406  									Image: "nginx",
   407  								},
   408  							},
   409  						},
   410  					},
   411  				},
   412  			},
   413  			groupVersion: appsv1beta2.SchemeGroupVersion,
   414  			path:         "/namespaces/test/statefulsets/nginx",
   415  			args:         []string{"statefulset", "nginx", "env=prod"},
   416  		},
   417  		{
   418  			name: "test appsv1 statefulset",
   419  			object: &appsv1.StatefulSet{
   420  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   421  				Spec: appsv1.StatefulSetSpec{
   422  					Template: corev1.PodTemplateSpec{
   423  						Spec: corev1.PodSpec{
   424  							Containers: []corev1.Container{
   425  								{
   426  									Name:  "nginx",
   427  									Image: "nginx",
   428  								},
   429  							},
   430  						},
   431  					},
   432  				},
   433  			},
   434  			groupVersion: appsv1.SchemeGroupVersion,
   435  			path:         "/namespaces/test/statefulsets/nginx",
   436  			args:         []string{"statefulset", "nginx", "env=prod"},
   437  		},
   438  		{
   439  			name: "set image batchv1 CronJob",
   440  			object: &batchv1.CronJob{
   441  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   442  				Spec: batchv1.CronJobSpec{
   443  					JobTemplate: batchv1.JobTemplateSpec{
   444  						Spec: batchv1.JobSpec{
   445  							Template: corev1.PodTemplateSpec{
   446  								Spec: corev1.PodSpec{
   447  									Containers: []corev1.Container{
   448  										{
   449  											Name:  "nginx",
   450  											Image: "nginx",
   451  										},
   452  									},
   453  								},
   454  							},
   455  						},
   456  					},
   457  				},
   458  			},
   459  			groupVersion: batchv1.SchemeGroupVersion,
   460  			path:         "/namespaces/test/cronjobs/nginx",
   461  			args:         []string{"cronjob", "nginx", "env=prod"},
   462  		},
   463  		{
   464  			name: "test corev1 replication controller",
   465  			object: &corev1.ReplicationController{
   466  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   467  				Spec: corev1.ReplicationControllerSpec{
   468  					Template: &corev1.PodTemplateSpec{
   469  						Spec: corev1.PodSpec{
   470  							Containers: []corev1.Container{
   471  								{
   472  									Name:  "nginx",
   473  									Image: "nginx",
   474  								},
   475  							},
   476  						},
   477  					},
   478  				},
   479  			},
   480  			groupVersion: corev1.SchemeGroupVersion,
   481  			path:         "/namespaces/test/replicationcontrollers/nginx",
   482  			args:         []string{"replicationcontroller", "nginx", "env=prod"},
   483  		},
   484  	}
   485  	for _, input := range inputs {
   486  		t.Run(input.name, func(t *testing.T) {
   487  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   488  			defer tf.Cleanup()
   489  
   490  			tf.Client = &fake.RESTClient{
   491  				GroupVersion:         input.groupVersion,
   492  				NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   493  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   494  					switch p, m := req.URL.Path, req.Method; {
   495  					case p == input.path && m == http.MethodGet:
   496  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
   497  					case p == input.path && m == http.MethodPatch:
   498  						stream, err := req.GetBody()
   499  						if err != nil {
   500  							return nil, err
   501  						}
   502  						bytes, err := io.ReadAll(stream)
   503  						if err != nil {
   504  							return nil, err
   505  						}
   506  						assert.Contains(t, string(bytes), `"value":`+`"`+"prod"+`"`, fmt.Sprintf("env not updated for %#v", input.object))
   507  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
   508  					default:
   509  						t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
   510  						return nil, fmt.Errorf("unexpected request")
   511  					}
   512  				}),
   513  			}
   514  
   515  			outputFormat := "yaml"
   516  			streams := genericiooptions.NewTestIOStreamsDiscard()
   517  			opts := NewEnvOptions(streams)
   518  			opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
   519  			opts.Local = false
   520  			opts.IOStreams = streams
   521  			err := opts.Complete(tf, NewCmdEnv(tf, streams), input.args)
   522  			assert.NoError(t, err)
   523  			err = opts.RunEnv()
   524  			assert.NoError(t, err)
   525  		})
   526  	}
   527  }
   528  
   529  func TestSetEnvFromResource(t *testing.T) {
   530  	mockConfigMap := &corev1.ConfigMap{
   531  		ObjectMeta: metav1.ObjectMeta{Name: "testconfigmap"},
   532  		Data: map[string]string{
   533  			"env":          "prod",
   534  			"test-key":     "testValue",
   535  			"test-key-two": "testValueTwo",
   536  		},
   537  	}
   538  
   539  	mockConfigMapUpperCaseKey := &corev1.ConfigMap{
   540  		ObjectMeta: metav1.ObjectMeta{Name: "testconfigmapuppercasekey"},
   541  		Data: map[string]string{
   542  			"ENV":          "prod",
   543  			"TEST_KEY":     "testValue",
   544  			"TEST_KEY_TWO": "testValueTwo",
   545  		},
   546  	}
   547  
   548  	mockSecret := &corev1.Secret{
   549  		ObjectMeta: metav1.ObjectMeta{Name: "testsecret"},
   550  		Data: map[string][]byte{
   551  			"env":          []byte("prod"),
   552  			"test-key":     []byte("testValue"),
   553  			"test-key-two": []byte("testValueTwo"),
   554  		},
   555  	}
   556  
   557  	mockSecretUpperCaseKey := &corev1.Secret{
   558  		ObjectMeta: metav1.ObjectMeta{Name: "testsecretuppercasekey"},
   559  		Data: map[string][]byte{
   560  			"ENV":          []byte("prod"),
   561  			"TEST_KEY":     []byte("testValue"),
   562  			"TEST_KEY_TWO": []byte("testValueTwo"),
   563  		},
   564  	}
   565  
   566  	inputs := []struct {
   567  		name           string
   568  		args           []string
   569  		from           string
   570  		keys           []string
   571  		assertIncludes []string
   572  		assertExcludes []string
   573  		warning        bool
   574  	}{
   575  		{
   576  			name: "test from configmap",
   577  			args: []string{"deployment", "nginx"},
   578  			from: "configmap/testconfigmap",
   579  			keys: []string{},
   580  			assertIncludes: []string{
   581  				`{"name":"ENV","valueFrom":{"configMapKeyRef":{"key":"env","name":"testconfigmap"}}}`,
   582  				`{"name":"TEST_KEY","valueFrom":{"configMapKeyRef":{"key":"test-key","name":"testconfigmap"}}}`,
   583  				`{"name":"TEST_KEY_TWO","valueFrom":{"configMapKeyRef":{"key":"test-key-two","name":"testconfigmap"}}}`,
   584  			},
   585  			assertExcludes: []string{},
   586  			warning:        true,
   587  		},
   588  		{
   589  			name: "test from configmap with upper case key",
   590  			args: []string{"deployment", "nginx"},
   591  			from: "configmap/testconfigmapuppercasekey",
   592  			keys: []string{},
   593  			assertIncludes: []string{
   594  				`{"name":"ENV","valueFrom":{"configMapKeyRef":{"key":"ENV","name":"testconfigmapuppercasekey"}}}`,
   595  				`{"name":"TEST_KEY","valueFrom":{"configMapKeyRef":{"key":"TEST_KEY","name":"testconfigmapuppercasekey"}}}`,
   596  				`{"name":"TEST_KEY_TWO","valueFrom":{"configMapKeyRef":{"key":"TEST_KEY_TWO","name":"testconfigmapuppercasekey"}}}`,
   597  			},
   598  			assertExcludes: []string{},
   599  			warning:        false,
   600  		},
   601  		{
   602  			name: "test from secret",
   603  			args: []string{"deployment", "nginx"},
   604  			from: "secret/testsecret",
   605  			keys: []string{},
   606  			assertIncludes: []string{
   607  				`{"name":"ENV","valueFrom":{"secretKeyRef":{"key":"env","name":"testsecret"}}}`,
   608  				`{"name":"TEST_KEY","valueFrom":{"secretKeyRef":{"key":"test-key","name":"testsecret"}}}`,
   609  				`{"name":"TEST_KEY_TWO","valueFrom":{"secretKeyRef":{"key":"test-key-two","name":"testsecret"}}}`,
   610  			},
   611  			assertExcludes: []string{},
   612  			warning:        true,
   613  		},
   614  		{
   615  			name: "test from secret with upper case key",
   616  			args: []string{"deployment", "nginx"},
   617  			from: "secret/testsecretuppercasekey",
   618  			keys: []string{},
   619  			assertIncludes: []string{
   620  				`{"name":"ENV","valueFrom":{"secretKeyRef":{"key":"ENV","name":"testsecretuppercasekey"}}}`,
   621  				`{"name":"TEST_KEY","valueFrom":{"secretKeyRef":{"key":"TEST_KEY","name":"testsecretuppercasekey"}}}`,
   622  				`{"name":"TEST_KEY_TWO","valueFrom":{"secretKeyRef":{"key":"TEST_KEY_TWO","name":"testsecretuppercasekey"}}}`,
   623  			},
   624  			assertExcludes: []string{},
   625  			warning:        false,
   626  		},
   627  		{
   628  			name: "test from configmap with keys",
   629  			args: []string{"deployment", "nginx"},
   630  			from: "configmap/testconfigmap",
   631  			keys: []string{"env", "test-key-two"},
   632  			assertIncludes: []string{
   633  				`{"name":"ENV","valueFrom":{"configMapKeyRef":{"key":"env","name":"testconfigmap"}}}`,
   634  				`{"name":"TEST_KEY_TWO","valueFrom":{"configMapKeyRef":{"key":"test-key-two","name":"testconfigmap"}}}`,
   635  			},
   636  			assertExcludes: []string{`{"name":"TEST_KEY","valueFrom":{"configMapKeyRef":{"key":"test-key","name":"testconfigmap"}}}`},
   637  			warning:        true,
   638  		},
   639  		{
   640  			name: "test from secret with keys",
   641  			args: []string{"deployment", "nginx"},
   642  			from: "secret/testsecret",
   643  			keys: []string{"env", "test-key-two"},
   644  			assertIncludes: []string{
   645  				`{"name":"ENV","valueFrom":{"secretKeyRef":{"key":"env","name":"testsecret"}}}`,
   646  				`{"name":"TEST_KEY_TWO","valueFrom":{"secretKeyRef":{"key":"test-key-two","name":"testsecret"}}}`,
   647  			},
   648  			assertExcludes: []string{`{"name":"TEST_KEY","valueFrom":{"secretKeyRef":{"key":"test-key","name":"testsecret"}}}`},
   649  			warning:        true,
   650  		},
   651  	}
   652  
   653  	for _, input := range inputs {
   654  		mockDeployment := &appsv1.Deployment{
   655  			ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   656  			Spec: appsv1.DeploymentSpec{
   657  				Template: corev1.PodTemplateSpec{
   658  					Spec: corev1.PodSpec{
   659  						Containers: []corev1.Container{
   660  							{
   661  								Name:  "nginx",
   662  								Image: "nginx",
   663  							},
   664  						},
   665  					},
   666  				},
   667  			},
   668  		}
   669  		t.Run(input.name, func(t *testing.T) {
   670  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   671  			defer tf.Cleanup()
   672  
   673  			tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
   674  			tf.Client = &fake.RESTClient{
   675  				GroupVersion:         schema.GroupVersion{Group: "", Version: "v1"},
   676  				NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   677  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   678  					switch p, m := req.URL.Path, req.Method; {
   679  					case p == "/namespaces/test/configmaps/testconfigmap" && m == http.MethodGet:
   680  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockConfigMap)}, nil
   681  					case p == "/namespaces/test/configmaps/testconfigmapuppercasekey" && m == http.MethodGet:
   682  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockConfigMapUpperCaseKey)}, nil
   683  					case p == "/namespaces/test/secrets/testsecret" && m == http.MethodGet:
   684  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockSecret)}, nil
   685  					case p == "/namespaces/test/secrets/testsecretuppercasekey" && m == http.MethodGet:
   686  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockSecretUpperCaseKey)}, nil
   687  					case p == "/namespaces/test/deployments/nginx" && m == http.MethodGet:
   688  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockDeployment)}, nil
   689  					case p == "/namespaces/test/deployments/nginx" && m == http.MethodPatch:
   690  						stream, err := req.GetBody()
   691  						if err != nil {
   692  							return nil, err
   693  						}
   694  						bytes, err := io.ReadAll(stream)
   695  						if err != nil {
   696  							return nil, err
   697  						}
   698  						for _, include := range input.assertIncludes {
   699  							assert.Contains(t, string(bytes), include)
   700  						}
   701  						for _, exclude := range input.assertExcludes {
   702  							assert.NotContains(t, string(bytes), exclude)
   703  						}
   704  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockDeployment)}, nil
   705  					default:
   706  						t.Errorf("%s: unexpected request: %#v\n%#v", input.name, req.URL, req)
   707  						return nil, nil
   708  					}
   709  				}),
   710  			}
   711  
   712  			outputFormat := "yaml"
   713  			streams, _, _, errOut := genericiooptions.NewTestIOStreams()
   714  			opts := NewEnvOptions(streams)
   715  			opts.From = input.from
   716  			opts.Keys = input.keys
   717  			opts.PrintFlags = genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme)
   718  			opts.Local = false
   719  			opts.IOStreams = streams
   720  			err := opts.Complete(tf, NewCmdEnv(tf, streams), input.args)
   721  			assert.NoError(t, err)
   722  			err = opts.RunEnv()
   723  			if input.warning {
   724  				assert.Contains(t, errOut.String(), "Warning")
   725  			} else {
   726  				assert.NotContains(t, errOut.String(), "Warning")
   727  			}
   728  			assert.NoError(t, err)
   729  		})
   730  	}
   731  }
   732  
   733  func TestSetEnvRemoteWithSpecificContainers(t *testing.T) {
   734  	inputs := []struct {
   735  		name     string
   736  		args     []string
   737  		selector string
   738  
   739  		expectedContainers int
   740  	}{
   741  		{
   742  			name:               "all containers",
   743  			args:               []string{"deployments", "redis", "env=prod"},
   744  			selector:           "*",
   745  			expectedContainers: 2,
   746  		},
   747  		{
   748  			name:               "use wildcards to select some containers",
   749  			args:               []string{"deployments", "redis", "env=prod"},
   750  			selector:           "red*",
   751  			expectedContainers: 1,
   752  		},
   753  		{
   754  			name:               "single container",
   755  			args:               []string{"deployments", "redis", "env=prod"},
   756  			selector:           "redis",
   757  			expectedContainers: 1,
   758  		},
   759  	}
   760  
   761  	for _, input := range inputs {
   762  		mockDeployment := &appsv1.Deployment{
   763  			ObjectMeta: metav1.ObjectMeta{
   764  				Name:      "redis",
   765  				Namespace: "test",
   766  			},
   767  			Spec: appsv1.DeploymentSpec{
   768  				Template: corev1.PodTemplateSpec{
   769  					Spec: corev1.PodSpec{
   770  						InitContainers: []corev1.Container{
   771  							{
   772  								Name:  "init",
   773  								Image: "redis",
   774  							},
   775  						},
   776  						Containers: []corev1.Container{
   777  							{
   778  								Name:  "redis",
   779  								Image: "redis",
   780  							},
   781  						},
   782  					},
   783  				},
   784  			},
   785  		}
   786  		t.Run(input.name, func(t *testing.T) {
   787  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   788  			defer tf.Cleanup()
   789  			tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
   790  			tf.Client = &fake.RESTClient{
   791  				GroupVersion:         schema.GroupVersion{Group: "", Version: "v1"},
   792  				NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   793  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   794  					switch p, m := req.URL.Path, req.Method; {
   795  					case p == "/namespaces/test/deployments/redis" && m == http.MethodGet:
   796  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockDeployment)}, nil
   797  					case p == "/namespaces/test/deployments/redis" && m == http.MethodPatch:
   798  						stream, err := req.GetBody()
   799  						if err != nil {
   800  							return nil, err
   801  						}
   802  						bytes, err := io.ReadAll(stream)
   803  						if err != nil {
   804  							return nil, err
   805  						}
   806  						updated := strings.Count(string(bytes), `"value":`+`"`+"prod"+`"`)
   807  						if updated != input.expectedContainers {
   808  							t.Errorf("expected %d containers to be selected but got %d \n", input.expectedContainers, updated)
   809  						}
   810  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(mockDeployment)}, nil
   811  					default:
   812  						t.Errorf("%s: unexpected request: %#v\n%#v", input.name, req.URL, req)
   813  						return nil, nil
   814  					}
   815  				}),
   816  			}
   817  			streams := genericiooptions.NewTestIOStreamsDiscard()
   818  			opts := &EnvOptions{
   819  				PrintFlags:        genericclioptions.NewPrintFlags("").WithDefaultOutput("yaml").WithTypeSetter(scheme.Scheme),
   820  				ContainerSelector: input.selector,
   821  				Overwrite:         true,
   822  				IOStreams:         streams,
   823  			}
   824  			err := opts.Complete(tf, NewCmdEnv(tf, streams), input.args)
   825  			assert.NoError(t, err)
   826  			err = opts.RunEnv()
   827  			assert.NoError(t, err)
   828  		})
   829  	}
   830  }
   831  
   832  func TestSetEnvDoubleStdinUsage(t *testing.T) {
   833  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   834  	defer tf.Cleanup()
   835  
   836  	tf.Client = &fake.RESTClient{
   837  		GroupVersion:         schema.GroupVersion{Version: ""},
   838  		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   839  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   840  			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   841  			return nil, nil
   842  		}),
   843  	}
   844  	tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
   845  
   846  	streams, bufIn, _, _ := genericiooptions.NewTestIOStreams()
   847  	bufIn.WriteString("SOME_ENV_VAR_KEY=SOME_ENV_VAR_VAL")
   848  	opts := NewEnvOptions(streams)
   849  	opts.FilenameOptions = resource.FilenameOptions{
   850  		Filenames: []string{"-"},
   851  	}
   852  
   853  	err := opts.Complete(tf, NewCmdEnv(tf, streams), []string{"-"})
   854  	assert.NoError(t, err)
   855  	err = opts.Validate()
   856  	assert.NoError(t, err)
   857  	err = opts.RunEnv()
   858  	assert.ErrorIs(t, err, resource.StdinMultiUseError)
   859  }
   860  

View as plain text