1
16
17 package create
18
19 import (
20 "reflect"
21 "testing"
22
23 "github.com/google/go-cmp/cmp"
24 rbac "k8s.io/api/rbac/v1"
25 "k8s.io/apimachinery/pkg/api/equality"
26 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/cli-runtime/pkg/genericclioptions"
30 "k8s.io/cli-runtime/pkg/genericiooptions"
31 "k8s.io/client-go/rest/fake"
32 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
33 "k8s.io/kubectl/pkg/scheme"
34 )
35
36 func TestCreateRole(t *testing.T) {
37 roleName := "my-role"
38 testNameSpace := "test"
39 tf := cmdtesting.NewTestFactory().WithNamespace(testNameSpace)
40 defer tf.Cleanup()
41
42 tf.Client = &fake.RESTClient{}
43 tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
44
45 tests := map[string]struct {
46 verbs string
47 resources string
48 resourceNames string
49 expectedRole *rbac.Role
50 }{
51 "test-duplicate-resources": {
52 verbs: "get,watch,list",
53 resources: "pods,pods",
54 expectedRole: &rbac.Role{
55 TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
56 ObjectMeta: v1.ObjectMeta{
57 Name: roleName,
58 Namespace: testNameSpace,
59 },
60 Rules: []rbac.PolicyRule{
61 {
62 Verbs: []string{"get", "watch", "list"},
63 Resources: []string{"pods"},
64 APIGroups: []string{""},
65 ResourceNames: []string{},
66 },
67 },
68 },
69 },
70 "test-subresources": {
71 verbs: "get,watch,list",
72 resources: "replicasets/scale",
73 expectedRole: &rbac.Role{
74 TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
75 ObjectMeta: v1.ObjectMeta{
76 Name: roleName,
77 Namespace: testNameSpace,
78 },
79 Rules: []rbac.PolicyRule{
80 {
81 Verbs: []string{"get", "watch", "list"},
82 Resources: []string{"replicasets/scale"},
83 APIGroups: []string{"extensions"},
84 ResourceNames: []string{},
85 },
86 },
87 },
88 },
89 "test-subresources-with-apigroup": {
90 verbs: "get,watch,list",
91 resources: "replicasets.extensions/scale",
92 expectedRole: &rbac.Role{
93 TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
94 ObjectMeta: v1.ObjectMeta{
95 Name: roleName,
96 Namespace: testNameSpace,
97 },
98 Rules: []rbac.PolicyRule{
99 {
100 Verbs: []string{"get", "watch", "list"},
101 Resources: []string{"replicasets/scale"},
102 APIGroups: []string{"extensions"},
103 ResourceNames: []string{},
104 },
105 },
106 },
107 },
108 "test-valid-case-with-multiple-apigroups": {
109 verbs: "get,watch,list",
110 resources: "pods,deployments.extensions",
111 expectedRole: &rbac.Role{
112 TypeMeta: v1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"},
113 ObjectMeta: v1.ObjectMeta{
114 Name: roleName,
115 Namespace: testNameSpace,
116 },
117 Rules: []rbac.PolicyRule{
118 {
119 Verbs: []string{"get", "watch", "list"},
120 Resources: []string{"pods"},
121 APIGroups: []string{""},
122 ResourceNames: []string{},
123 },
124 {
125 Verbs: []string{"get", "watch", "list"},
126 Resources: []string{"deployments"},
127 APIGroups: []string{"extensions"},
128 ResourceNames: []string{},
129 },
130 },
131 },
132 },
133 }
134
135 for name, test := range tests {
136 t.Run(name, func(t *testing.T) {
137 ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
138 cmd := NewCmdCreateRole(tf, ioStreams)
139 cmd.Flags().Set("dry-run", "client")
140 cmd.Flags().Set("output", "yaml")
141 cmd.Flags().Set("verb", test.verbs)
142 cmd.Flags().Set("resource", test.resources)
143 if test.resourceNames != "" {
144 cmd.Flags().Set("resource-name", test.resourceNames)
145 }
146 cmd.Run(cmd, []string{roleName})
147 actual := &rbac.Role{}
148 if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), buf.Bytes(), actual); err != nil {
149 t.Log(buf.String())
150 t.Fatal(err)
151 }
152 if !equality.Semantic.DeepEqual(test.expectedRole, actual) {
153 t.Errorf("%s", cmp.Diff(test.expectedRole, actual))
154 }
155 })
156 }
157 }
158
159 func TestValidate(t *testing.T) {
160 tf := cmdtesting.NewTestFactory().WithNamespace("test")
161 defer tf.Cleanup()
162
163 tests := map[string]struct {
164 roleOptions *CreateRoleOptions
165 expectErr bool
166 }{
167 "test-missing-name": {
168 roleOptions: &CreateRoleOptions{},
169 expectErr: true,
170 },
171 "test-missing-verb": {
172 roleOptions: &CreateRoleOptions{
173 Name: "my-role",
174 },
175 expectErr: true,
176 },
177 "test-missing-resource": {
178 roleOptions: &CreateRoleOptions{
179 Name: "my-role",
180 Verbs: []string{"get"},
181 },
182 expectErr: true,
183 },
184 "test-missing-resource-existing-apigroup": {
185 roleOptions: &CreateRoleOptions{
186 Name: "my-role",
187 Verbs: []string{"get"},
188 Resources: []ResourceOptions{
189 {
190 Group: "extensions",
191 },
192 },
193 },
194 expectErr: true,
195 },
196 "test-missing-resource-existing-subresource": {
197 roleOptions: &CreateRoleOptions{
198 Name: "my-role",
199 Verbs: []string{"get"},
200 Resources: []ResourceOptions{
201 {
202 SubResource: "scale",
203 },
204 },
205 },
206 expectErr: true,
207 },
208 "test-invalid-verb": {
209 roleOptions: &CreateRoleOptions{
210 Name: "my-role",
211 Verbs: []string{"invalid-verb"},
212 Resources: []ResourceOptions{
213 {
214 Resource: "pods",
215 },
216 },
217 },
218 expectErr: false,
219 },
220 "test-nonresource-verb": {
221 roleOptions: &CreateRoleOptions{
222 Name: "my-role",
223 Verbs: []string{"post"},
224 Resources: []ResourceOptions{
225 {
226 Resource: "pods",
227 },
228 },
229 },
230 expectErr: false,
231 },
232 "test-special-verb": {
233 roleOptions: &CreateRoleOptions{
234 Name: "my-role",
235 Verbs: []string{"use"},
236 Resources: []ResourceOptions{
237 {
238 Resource: "pods",
239 },
240 },
241 },
242 expectErr: true,
243 },
244 "test-mix-verbs": {
245 roleOptions: &CreateRoleOptions{
246 Name: "my-role",
247 Verbs: []string{"impersonate", "use"},
248 Resources: []ResourceOptions{
249 {
250 Resource: "userextras",
251 SubResource: "scopes",
252 },
253 },
254 },
255 expectErr: true,
256 },
257 "test-special-verb-with-wrong-apigroup": {
258 roleOptions: &CreateRoleOptions{
259 Name: "my-role",
260 Verbs: []string{"impersonate"},
261 Resources: []ResourceOptions{
262 {
263 Resource: "userextras",
264 SubResource: "scopes",
265 Group: "extensions",
266 },
267 },
268 },
269 expectErr: true,
270 },
271 "test-invalid-resource": {
272 roleOptions: &CreateRoleOptions{
273 Name: "my-role",
274 Verbs: []string{"get"},
275 Resources: []ResourceOptions{
276 {
277 Resource: "invalid-resource",
278 },
279 },
280 },
281 expectErr: true,
282 },
283 "test-resource-name-with-multiple-resources": {
284 roleOptions: &CreateRoleOptions{
285 Name: "my-role",
286 Verbs: []string{"get"},
287 Resources: []ResourceOptions{
288 {
289 Resource: "pods",
290 },
291 {
292 Resource: "deployments",
293 Group: "extensions",
294 },
295 },
296 ResourceNames: []string{"foo"},
297 },
298 expectErr: false,
299 },
300 "test-valid-case": {
301 roleOptions: &CreateRoleOptions{
302 Name: "role-binder",
303 Verbs: []string{"get", "list", "bind"},
304 Resources: []ResourceOptions{
305 {
306 Resource: "roles",
307 Group: "rbac.authorization.k8s.io",
308 },
309 },
310 ResourceNames: []string{"foo"},
311 },
312 expectErr: false,
313 },
314 "test-valid-case-with-subresource": {
315 roleOptions: &CreateRoleOptions{
316 Name: "my-role",
317 Verbs: []string{"get", "list"},
318 Resources: []ResourceOptions{
319 {
320 Resource: "replicasets",
321 SubResource: "scale",
322 },
323 },
324 ResourceNames: []string{"bar"},
325 },
326 expectErr: false,
327 },
328 "test-valid-case-with-additional-resource": {
329 roleOptions: &CreateRoleOptions{
330 Name: "my-role",
331 Verbs: []string{"impersonate"},
332 Resources: []ResourceOptions{
333 {
334 Resource: "userextras",
335 SubResource: "scopes",
336 Group: "authentication.k8s.io",
337 },
338 },
339 },
340 expectErr: false,
341 },
342 }
343
344 for name, test := range tests {
345 test.roleOptions.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
346
347 var err error
348 test.roleOptions.Mapper, err = tf.ToRESTMapper()
349 if err != nil {
350 t.Fatal(err)
351 }
352 err = test.roleOptions.Validate()
353 if test.expectErr && err == nil {
354 t.Errorf("%s: expect error happens but validate passes.", name)
355 }
356 if !test.expectErr && err != nil {
357 t.Errorf("%s: unexpected error: %v", name, err)
358 }
359 }
360 }
361
362 func TestComplete(t *testing.T) {
363 roleName := "my-role"
364
365 tf := cmdtesting.NewTestFactory().WithNamespace("test")
366 defer tf.Cleanup()
367
368 tf.Client = &fake.RESTClient{}
369 tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
370
371 defaultTestResources := "pods,deployments.extensions"
372
373 tests := map[string]struct {
374 params []string
375 resources string
376 roleOptions *CreateRoleOptions
377 expected *CreateRoleOptions
378 expectErr bool
379 }{
380 "test-missing-name": {
381 params: []string{},
382 resources: defaultTestResources,
383 roleOptions: &CreateRoleOptions{
384 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
385 },
386 expectErr: true,
387 },
388 "test-duplicate-verbs": {
389 params: []string{roleName},
390 resources: defaultTestResources,
391 roleOptions: &CreateRoleOptions{
392 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
393 Name: roleName,
394 Verbs: []string{
395 "get",
396 "watch",
397 "list",
398 "get",
399 },
400 },
401 expected: &CreateRoleOptions{
402 Name: roleName,
403 Verbs: []string{
404 "get",
405 "watch",
406 "list",
407 },
408 Resources: []ResourceOptions{
409 {
410 Resource: "pods",
411 Group: "",
412 },
413 {
414 Resource: "deployments",
415 Group: "extensions",
416 },
417 },
418 ResourceNames: []string{},
419 },
420 expectErr: false,
421 },
422 "test-verball": {
423 params: []string{roleName},
424 resources: defaultTestResources,
425 roleOptions: &CreateRoleOptions{
426 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
427 Name: roleName,
428 Verbs: []string{
429 "get",
430 "watch",
431 "list",
432 "*",
433 },
434 },
435 expected: &CreateRoleOptions{
436 Name: roleName,
437 Verbs: []string{"*"},
438 Resources: []ResourceOptions{
439 {
440 Resource: "pods",
441 Group: "",
442 },
443 {
444 Resource: "deployments",
445 Group: "extensions",
446 },
447 },
448 ResourceNames: []string{},
449 },
450 expectErr: false,
451 },
452 "test-allresource": {
453 params: []string{roleName},
454 resources: "*,pods",
455 roleOptions: &CreateRoleOptions{
456 PrintFlags: genericclioptions.NewPrintFlags("created"),
457 Name: roleName,
458 Verbs: []string{"*"},
459 },
460 expected: &CreateRoleOptions{
461 Name: roleName,
462 Verbs: []string{"*"},
463 Resources: []ResourceOptions{
464 {
465 Resource: "*",
466 },
467 },
468 ResourceNames: []string{},
469 },
470 expectErr: false,
471 },
472 "test-allresource-subresource": {
473 params: []string{roleName},
474 resources: "*/scale,pods",
475 roleOptions: &CreateRoleOptions{
476 PrintFlags: genericclioptions.NewPrintFlags("created"),
477 Name: roleName,
478 Verbs: []string{"*"},
479 },
480 expected: &CreateRoleOptions{
481 Name: roleName,
482 Verbs: []string{"*"},
483 Resources: []ResourceOptions{
484 {
485 Resource: "*",
486 SubResource: "scale",
487 },
488 {
489 Resource: "pods",
490 },
491 },
492 ResourceNames: []string{},
493 },
494 expectErr: false,
495 },
496 "test-allresrouce-allgroup": {
497 params: []string{roleName},
498 resources: "*.*,pods",
499 roleOptions: &CreateRoleOptions{
500 PrintFlags: genericclioptions.NewPrintFlags("created"),
501 Name: roleName,
502 Verbs: []string{"*"},
503 },
504 expected: &CreateRoleOptions{
505 Name: roleName,
506 Verbs: []string{"*"},
507 Resources: []ResourceOptions{
508 {
509 Resource: "*",
510 Group: "*",
511 },
512 {
513 Resource: "pods",
514 },
515 },
516 ResourceNames: []string{},
517 },
518 expectErr: false,
519 },
520 "test-allresource-allgroup-subresource": {
521 params: []string{roleName},
522 resources: "*.*/scale,pods",
523 roleOptions: &CreateRoleOptions{
524 PrintFlags: genericclioptions.NewPrintFlags("created"),
525 Name: roleName,
526 Verbs: []string{"*"},
527 },
528 expected: &CreateRoleOptions{
529 Name: roleName,
530 Verbs: []string{"*"},
531 Resources: []ResourceOptions{
532 {
533 Resource: "*",
534 Group: "*",
535 SubResource: "scale",
536 },
537 {
538 Resource: "pods",
539 },
540 },
541 ResourceNames: []string{},
542 },
543 expectErr: false,
544 },
545 "test-allresource-specificgroup": {
546 params: []string{roleName},
547 resources: "*.extensions,pods",
548 roleOptions: &CreateRoleOptions{
549 PrintFlags: genericclioptions.NewPrintFlags("created"),
550 Name: roleName,
551 Verbs: []string{"*"},
552 },
553 expected: &CreateRoleOptions{
554 Name: roleName,
555 Verbs: []string{"*"},
556 Resources: []ResourceOptions{
557 {
558 Resource: "*",
559 Group: "extensions",
560 },
561 {
562 Resource: "pods",
563 },
564 },
565 ResourceNames: []string{},
566 },
567 expectErr: false,
568 },
569 "test-allresource-specificgroup-subresource": {
570 params: []string{roleName},
571 resources: "*.extensions/scale,pods",
572 roleOptions: &CreateRoleOptions{
573 PrintFlags: genericclioptions.NewPrintFlags("created"),
574 Name: roleName,
575 Verbs: []string{"*"},
576 },
577 expected: &CreateRoleOptions{
578 Name: roleName,
579 Verbs: []string{"*"},
580 Resources: []ResourceOptions{
581 {
582 Resource: "*",
583 Group: "extensions",
584 SubResource: "scale",
585 },
586 {
587 Resource: "pods",
588 },
589 },
590 ResourceNames: []string{},
591 },
592 expectErr: false,
593 },
594 "test-duplicate-resourcenames": {
595 params: []string{roleName},
596 resources: defaultTestResources,
597 roleOptions: &CreateRoleOptions{
598 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
599 Name: roleName,
600 Verbs: []string{"*"},
601 ResourceNames: []string{"foo", "foo"},
602 },
603 expected: &CreateRoleOptions{
604 Name: roleName,
605 Verbs: []string{"*"},
606 Resources: []ResourceOptions{
607 {
608 Resource: "pods",
609 Group: "",
610 },
611 {
612 Resource: "deployments",
613 Group: "extensions",
614 },
615 },
616 ResourceNames: []string{"foo"},
617 },
618 expectErr: false,
619 },
620 "test-valid-complete-case": {
621 params: []string{roleName},
622 resources: defaultTestResources,
623 roleOptions: &CreateRoleOptions{
624 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
625 Name: roleName,
626 Verbs: []string{"*"},
627 ResourceNames: []string{"foo"},
628 },
629 expected: &CreateRoleOptions{
630 Name: roleName,
631 Verbs: []string{"*"},
632 Resources: []ResourceOptions{
633 {
634 Resource: "pods",
635 Group: "",
636 },
637 {
638 Resource: "deployments",
639 Group: "extensions",
640 },
641 },
642 ResourceNames: []string{"foo"},
643 },
644 expectErr: false,
645 },
646 }
647
648 for name, test := range tests {
649 cmd := NewCmdCreateRole(tf, genericiooptions.NewTestIOStreamsDiscard())
650 cmd.Flags().Set("resource", test.resources)
651
652 err := test.roleOptions.Complete(tf, cmd, test.params)
653 if !test.expectErr && err != nil {
654 t.Errorf("%s: unexpected error: %v", name, err)
655 }
656
657 if test.expectErr {
658 if err != nil {
659 continue
660 } else {
661 t.Errorf("%s: expect error happens but test passes.", name)
662 }
663 }
664
665 if test.roleOptions.Name != test.expected.Name {
666 t.Errorf("%s:\nexpected name:\n%#v\nsaw name:\n%#v", name, test.expected.Name, test.roleOptions.Name)
667 }
668
669 if !reflect.DeepEqual(test.roleOptions.Verbs, test.expected.Verbs) {
670 t.Errorf("%s:\nexpected verbs:\n%#v\nsaw verbs:\n%#v", name, test.expected.Verbs, test.roleOptions.Verbs)
671 }
672
673 if !reflect.DeepEqual(test.roleOptions.Resources, test.expected.Resources) {
674 t.Errorf("%s:\nexpected resources:\n%#v\nsaw resources:\n%#v", name, test.expected.Resources, test.roleOptions.Resources)
675 }
676
677 if !reflect.DeepEqual(test.roleOptions.ResourceNames, test.expected.ResourceNames) {
678 t.Errorf("%s:\nexpected resource names:\n%#v\nsaw resource names:\n%#v", name, test.expected.ResourceNames, test.roleOptions.ResourceNames)
679 }
680 }
681 }
682
683 func TestAddSpecialVerb(t *testing.T) {
684 testCases := map[string]struct {
685 verb string
686 resource schema.GroupResource
687 }{
688 "existing verb": {
689 verb: "use",
690 resource: schema.GroupResource{Group: "my.custom.io", Resource: "one"},
691 },
692 "new verb": {
693 verb: "new",
694 resource: schema.GroupResource{Group: "my.custom.io", Resource: "two"},
695 },
696 }
697
698 for name, tc := range testCases {
699 t.Run(name, func(t *testing.T) {
700 AddSpecialVerb(tc.verb, tc.resource)
701 resources, ok := specialVerbs[tc.verb]
702 if !ok {
703 t.Errorf("missing expected verb: %s", tc.verb)
704 }
705
706 for _, res := range resources {
707 if reflect.DeepEqual(tc.resource, res) {
708 return
709 }
710 }
711 t.Errorf("missing expected resource:%#v", tc.resource)
712 })
713 }
714 }
715
View as plain text