1
16
17 package set
18
19 import (
20 "fmt"
21 "io"
22 "net/http"
23 "testing"
24
25 "github.com/stretchr/testify/assert"
26 appsv1 "k8s.io/api/apps/v1"
27 appsv1beta1 "k8s.io/api/apps/v1beta1"
28 appsv1beta2 "k8s.io/api/apps/v1beta2"
29 batchv1 "k8s.io/api/batch/v1"
30 corev1 "k8s.io/api/core/v1"
31 extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/apimachinery/pkg/runtime/schema"
35 "k8s.io/cli-runtime/pkg/genericclioptions"
36 "k8s.io/cli-runtime/pkg/genericiooptions"
37 "k8s.io/cli-runtime/pkg/resource"
38 restclient "k8s.io/client-go/rest"
39 "k8s.io/client-go/rest/fake"
40 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
41 "k8s.io/kubectl/pkg/scheme"
42 )
43
44 const (
45 serviceAccount = "serviceaccount1"
46 serviceAccountMissingErrString = "serviceaccount is required"
47 resourceMissingErrString = `You must provide one or more resources by argument or filename.
48 Example resource specifications include:
49 '-f rsrc.yaml'
50 '--filename=rsrc.json'
51 '<resource> <name>'
52 '<resource>'`
53 )
54
55 func TestSetServiceAccountLocal(t *testing.T) {
56 inputs := []struct {
57 yaml string
58 apiGroup string
59 }{
60 {yaml: "../../../testdata/set/replication.yaml", apiGroup: ""},
61 {yaml: "../../../testdata/set/daemon.yaml", apiGroup: "extensions"},
62 {yaml: "../../../testdata/set/redis-slave.yaml", apiGroup: "extensions"},
63 {yaml: "../../../testdata/set/job.yaml", apiGroup: "batch"},
64 {yaml: "../../../testdata/set/deployment.yaml", apiGroup: "extensions"},
65 }
66
67 for i, input := range inputs {
68 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
69 tf := cmdtesting.NewTestFactory().WithNamespace("test")
70 defer tf.Cleanup()
71
72 tf.Client = &fake.RESTClient{
73 GroupVersion: schema.GroupVersion{Version: "v1"},
74 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
75 t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
76 return nil, nil
77 }),
78 }
79
80 outputFormat := "yaml"
81
82 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
83 cmd := NewCmdServiceAccount(tf, streams)
84 cmd.Flags().Set("output", outputFormat)
85 cmd.Flags().Set("local", "true")
86 saConfig := SetServiceAccountOptions{
87 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
88 fileNameOptions: resource.FilenameOptions{
89 Filenames: []string{input.yaml}},
90 local: true,
91 IOStreams: streams,
92 }
93 err := saConfig.Complete(tf, cmd, []string{serviceAccount})
94 assert.NoError(t, err)
95 err = saConfig.Run()
96 assert.NoError(t, err)
97 assert.Contains(t, buf.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml))
98 })
99 }
100 }
101
102 func TestSetServiceAccountMultiLocal(t *testing.T) {
103 tf := cmdtesting.NewTestFactory().WithNamespace("test")
104 defer tf.Cleanup()
105
106 tf.Client = &fake.RESTClient{
107 GroupVersion: schema.GroupVersion{Version: ""},
108 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
109 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
110 t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
111 return nil, nil
112 }),
113 }
114 tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
115
116 outputFormat := "name"
117
118 streams, _, buf, _ := genericiooptions.NewTestIOStreams()
119 cmd := NewCmdServiceAccount(tf, streams)
120 cmd.Flags().Set("output", outputFormat)
121 cmd.Flags().Set("local", "true")
122 opts := SetServiceAccountOptions{
123 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
124 fileNameOptions: resource.FilenameOptions{
125 Filenames: []string{"../../../testdata/set/multi-resource-yaml.yaml"}},
126 local: true,
127 IOStreams: streams,
128 }
129
130 err := opts.Complete(tf, cmd, []string{serviceAccount})
131 if err == nil {
132 err = opts.Run()
133 }
134 if err != nil {
135 t.Fatalf("unexpected error: %v", err)
136 }
137 expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n"
138 if buf.String() != expectedOut {
139 t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String())
140 }
141 }
142
143 func TestSetServiceAccountRemote(t *testing.T) {
144 inputs := []struct {
145 object runtime.Object
146 groupVersion schema.GroupVersion
147 path string
148 args []string
149 }{
150 {
151 object: &extensionsv1beta1.ReplicaSet{
152 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
153 },
154 groupVersion: extensionsv1beta1.SchemeGroupVersion,
155 path: "/namespaces/test/replicasets/nginx",
156 args: []string{"replicaset", "nginx", serviceAccount},
157 },
158 {
159 object: &appsv1beta2.ReplicaSet{
160 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
161 Spec: appsv1beta2.ReplicaSetSpec{
162 Template: corev1.PodTemplateSpec{
163 Spec: corev1.PodSpec{
164 Containers: []corev1.Container{
165 {
166 Name: "nginx",
167 Image: "nginx",
168 },
169 },
170 },
171 },
172 },
173 },
174 groupVersion: appsv1beta2.SchemeGroupVersion,
175 path: "/namespaces/test/replicasets/nginx",
176 args: []string{"replicaset", "nginx", serviceAccount},
177 },
178 {
179 object: &appsv1.ReplicaSet{
180 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
181 Spec: appsv1.ReplicaSetSpec{
182 Template: corev1.PodTemplateSpec{
183 Spec: corev1.PodSpec{
184 Containers: []corev1.Container{
185 {
186 Name: "nginx",
187 Image: "nginx",
188 },
189 },
190 },
191 },
192 },
193 },
194 groupVersion: appsv1.SchemeGroupVersion,
195 path: "/namespaces/test/replicasets/nginx",
196 args: []string{"replicaset", "nginx", serviceAccount},
197 },
198 {
199 object: &extensionsv1beta1.DaemonSet{
200 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
201 },
202 groupVersion: extensionsv1beta1.SchemeGroupVersion,
203 path: "/namespaces/test/daemonsets/nginx",
204 args: []string{"daemonset", "nginx", serviceAccount},
205 },
206 {
207 object: &appsv1beta2.DaemonSet{
208 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
209 },
210 groupVersion: appsv1beta2.SchemeGroupVersion,
211 path: "/namespaces/test/daemonsets/nginx",
212 args: []string{"daemonset", "nginx", serviceAccount},
213 },
214 {
215 object: &appsv1.DaemonSet{
216 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
217 },
218 groupVersion: appsv1.SchemeGroupVersion,
219 path: "/namespaces/test/daemonsets/nginx",
220 args: []string{"daemonset", "nginx", serviceAccount},
221 },
222 {
223 object: &extensionsv1beta1.Deployment{
224 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
225 },
226 groupVersion: extensionsv1beta1.SchemeGroupVersion,
227 path: "/namespaces/test/deployments/nginx",
228 args: []string{"deployment", "nginx", serviceAccount},
229 },
230 {
231 object: &appsv1beta1.Deployment{
232 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
233 },
234 groupVersion: appsv1beta1.SchemeGroupVersion,
235 path: "/namespaces/test/deployments/nginx",
236 args: []string{"deployment", "nginx", serviceAccount},
237 },
238 {
239 object: &appsv1beta2.Deployment{
240 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
241 },
242 groupVersion: appsv1beta2.SchemeGroupVersion,
243 path: "/namespaces/test/deployments/nginx",
244 args: []string{"deployment", "nginx", serviceAccount},
245 },
246 {
247 object: &appsv1.Deployment{
248 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
249 Spec: appsv1.DeploymentSpec{
250 Template: corev1.PodTemplateSpec{
251 Spec: corev1.PodSpec{
252 Containers: []corev1.Container{
253 {
254 Name: "nginx",
255 Image: "nginx",
256 },
257 },
258 },
259 },
260 },
261 },
262 groupVersion: appsv1.SchemeGroupVersion,
263 path: "/namespaces/test/deployments/nginx",
264 args: []string{"deployment", "nginx", serviceAccount},
265 },
266 {
267 object: &appsv1beta1.StatefulSet{
268 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
269 },
270 groupVersion: appsv1beta1.SchemeGroupVersion,
271 path: "/namespaces/test/statefulsets/nginx",
272 args: []string{"statefulset", "nginx", serviceAccount},
273 },
274 {
275 object: &appsv1beta2.StatefulSet{
276 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
277 },
278 groupVersion: appsv1beta2.SchemeGroupVersion,
279 path: "/namespaces/test/statefulsets/nginx",
280 args: []string{"statefulset", "nginx", serviceAccount},
281 },
282 {
283 object: &appsv1.StatefulSet{
284 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
285 Spec: appsv1.StatefulSetSpec{
286 Template: corev1.PodTemplateSpec{
287 Spec: corev1.PodSpec{
288 Containers: []corev1.Container{
289 {
290 Name: "nginx",
291 Image: "nginx",
292 },
293 },
294 },
295 },
296 },
297 },
298 groupVersion: appsv1.SchemeGroupVersion,
299 path: "/namespaces/test/statefulsets/nginx",
300 args: []string{"statefulset", "nginx", serviceAccount},
301 },
302 {
303 object: &batchv1.Job{
304 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
305 },
306 groupVersion: batchv1.SchemeGroupVersion,
307 path: "/namespaces/test/jobs/nginx",
308 args: []string{"job", "nginx", serviceAccount},
309 },
310 {
311 object: &corev1.ReplicationController{
312 ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
313 },
314 groupVersion: corev1.SchemeGroupVersion,
315 path: "/namespaces/test/replicationcontrollers/nginx",
316 args: []string{"replicationcontroller", "nginx", serviceAccount},
317 },
318 }
319 for i, input := range inputs {
320 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
321 tf := cmdtesting.NewTestFactory().WithNamespace("test")
322 defer tf.Cleanup()
323
324 tf.Client = &fake.RESTClient{
325 GroupVersion: input.groupVersion,
326 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
327 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
328 switch p, m := req.URL.Path, req.Method; {
329 case p == input.path && m == http.MethodGet:
330 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
331 case p == input.path && m == http.MethodPatch:
332 stream, err := req.GetBody()
333 if err != nil {
334 return nil, err
335 }
336 bytes, err := io.ReadAll(stream)
337 if err != nil {
338 return nil, err
339 }
340 assert.Contains(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, fmt.Sprintf("serviceaccount not updated for %#v", input.object))
341 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
342 default:
343 t.Errorf("%s: unexpected request: %s %#v\n%#v", "serviceaccount", req.Method, req.URL, req)
344 return nil, fmt.Errorf("unexpected request")
345 }
346 }),
347 }
348
349 outputFormat := "yaml"
350
351 streams := genericiooptions.NewTestIOStreamsDiscard()
352 cmd := NewCmdServiceAccount(tf, streams)
353 cmd.Flags().Set("output", outputFormat)
354 saConfig := SetServiceAccountOptions{
355 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
356
357 local: false,
358 IOStreams: streams,
359 }
360 err := saConfig.Complete(tf, cmd, input.args)
361 assert.NoError(t, err)
362 err = saConfig.Run()
363 assert.NoError(t, err)
364 })
365 }
366 }
367
368 func TestServiceAccountValidation(t *testing.T) {
369 inputs := []struct {
370 name string
371 args []string
372 errorString string
373 }{
374 {name: "test service account missing", args: []string{}, errorString: serviceAccountMissingErrString},
375 {name: "test service account resource missing", args: []string{serviceAccount}, errorString: resourceMissingErrString},
376 }
377 for _, input := range inputs {
378 t.Run(input.name, func(t *testing.T) {
379 tf := cmdtesting.NewTestFactory().WithNamespace("test")
380 defer tf.Cleanup()
381
382 tf.Client = &fake.RESTClient{
383 GroupVersion: schema.GroupVersion{Version: "v1"},
384 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
385 t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
386 return nil, nil
387 }),
388 }
389
390 outputFormat := ""
391
392 streams := genericiooptions.NewTestIOStreamsDiscard()
393 cmd := NewCmdServiceAccount(tf, streams)
394
395 saConfig := &SetServiceAccountOptions{
396 PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
397 IOStreams: streams,
398 }
399 err := saConfig.Complete(tf, cmd, input.args)
400 assert.EqualError(t, err, input.errorString)
401 })
402 }
403 }
404
405 func objBody(obj runtime.Object) io.ReadCloser {
406 return cmdtesting.BytesBody([]byte(runtime.EncodeOrDie(scheme.DefaultJSONEncoder(), obj)))
407 }
408
View as plain text