1
2
3
4 package graph
5
6 import (
7 "errors"
8 "testing"
9
10 "github.com/google/go-cmp/cmp"
11 "github.com/google/go-cmp/cmp/cmpopts"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15 "k8s.io/apimachinery/pkg/runtime/schema"
16 "sigs.k8s.io/cli-utils/pkg/multierror"
17 "sigs.k8s.io/cli-utils/pkg/object"
18 "sigs.k8s.io/cli-utils/pkg/object/dependson"
19 "sigs.k8s.io/cli-utils/pkg/object/mutation"
20 mutationutil "sigs.k8s.io/cli-utils/pkg/object/mutation/testutil"
21 "sigs.k8s.io/cli-utils/pkg/object/validation"
22 "sigs.k8s.io/cli-utils/pkg/testutil"
23 )
24
25 var (
26 resources = map[string]string{
27 "pod": `
28 kind: Pod
29 apiVersion: v1
30 metadata:
31 name: test-pod
32 namespace: test-namespace
33 `,
34 "default-pod": `
35 kind: Pod
36 apiVersion: v1
37 metadata:
38 name: pod-in-default-namespace
39 namespace: default
40 `,
41 "deployment": `
42 kind: Deployment
43 apiVersion: apps/v1
44 metadata:
45 name: foo
46 namespace: test-namespace
47 uid: dep-uid
48 generation: 1
49 spec:
50 replicas: 1
51 `,
52 "secret": `
53 kind: Secret
54 apiVersion: v1
55 metadata:
56 name: secret
57 namespace: test-namespace
58 uid: secret-uid
59 generation: 1
60 type: Opaque
61 spec:
62 foo: bar
63 `,
64 "namespace": `
65 kind: Namespace
66 apiVersion: v1
67 metadata:
68 name: test-namespace
69 `,
70
71 "crd": `
72 apiVersion: apiextensions.k8s.io/v1
73 kind: CustomResourceDefinition
74 metadata:
75 name: crontabs.stable.example.com
76 spec:
77 group: stable.example.com
78 versions:
79 - name: v1
80 served: true
81 storage: true
82 scope: Namespaced
83 names:
84 plural: crontabs
85 singular: crontab
86 kind: CronTab
87 `,
88 "crontab1": `
89 apiVersion: "stable.example.com/v1"
90 kind: CronTab
91 metadata:
92 name: cron-tab-01
93 namespace: test-namespace
94 `,
95 "crontab2": `
96 apiVersion: "stable.example.com/v1"
97 kind: CronTab
98 metadata:
99 name: cron-tab-02
100 namespace: test-namespace
101 `,
102 }
103 )
104
105 func TestSortObjs(t *testing.T) {
106 testCases := map[string]struct {
107 objs []*unstructured.Unstructured
108 expected []object.UnstructuredSet
109 isError bool
110 }{
111 "no objects returns no object sets": {
112 objs: []*unstructured.Unstructured{},
113 expected: []object.UnstructuredSet{},
114 isError: false,
115 },
116 "one object returns single object set": {
117 objs: []*unstructured.Unstructured{
118 testutil.Unstructured(t, resources["deployment"]),
119 },
120 expected: []object.UnstructuredSet{
121 {
122 testutil.Unstructured(t, resources["deployment"]),
123 },
124 },
125 isError: false,
126 },
127 "two unrelated objects returns single object set with two objs": {
128 objs: []*unstructured.Unstructured{
129 testutil.Unstructured(t, resources["deployment"]),
130 testutil.Unstructured(t, resources["secret"]),
131 },
132 expected: []object.UnstructuredSet{
133 {
134 testutil.Unstructured(t, resources["deployment"]),
135 testutil.Unstructured(t, resources["secret"]),
136 },
137 },
138 isError: false,
139 },
140 "one object depends on the other; two single object sets": {
141 objs: []*unstructured.Unstructured{
142 testutil.Unstructured(t, resources["deployment"],
143 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
144 testutil.Unstructured(t, resources["secret"]),
145 },
146 expected: []object.UnstructuredSet{
147 {
148 testutil.Unstructured(t, resources["secret"]),
149 },
150 {
151 testutil.Unstructured(t, resources["deployment"]),
152 },
153 },
154 isError: false,
155 },
156 "three objects depend on another; three single object sets": {
157 objs: []*unstructured.Unstructured{
158 testutil.Unstructured(t, resources["deployment"],
159 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
160 testutil.Unstructured(t, resources["secret"],
161 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["pod"]))),
162 testutil.Unstructured(t, resources["pod"]),
163 },
164 expected: []object.UnstructuredSet{
165 {
166 testutil.Unstructured(t, resources["pod"]),
167 },
168 {
169 testutil.Unstructured(t, resources["secret"]),
170 },
171 {
172 testutil.Unstructured(t, resources["deployment"]),
173 },
174 },
175 isError: false,
176 },
177 "Two objects depend on secret; two object sets": {
178 objs: []*unstructured.Unstructured{
179 testutil.Unstructured(t, resources["deployment"],
180 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
181 testutil.Unstructured(t, resources["pod"],
182 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
183 testutil.Unstructured(t, resources["secret"]),
184 },
185 expected: []object.UnstructuredSet{
186 {
187 testutil.Unstructured(t, resources["secret"]),
188 },
189 {
190 testutil.Unstructured(t, resources["pod"]),
191 testutil.Unstructured(t, resources["deployment"]),
192 },
193 },
194 isError: false,
195 },
196 "two objects applied with their namespace; two object sets": {
197 objs: []*unstructured.Unstructured{
198 testutil.Unstructured(t, resources["deployment"]),
199 testutil.Unstructured(t, resources["namespace"]),
200 testutil.Unstructured(t, resources["secret"]),
201 },
202 expected: []object.UnstructuredSet{
203 {
204 testutil.Unstructured(t, resources["namespace"]),
205 },
206 {
207 testutil.Unstructured(t, resources["secret"]),
208 testutil.Unstructured(t, resources["deployment"]),
209 },
210 },
211 isError: false,
212 },
213 "two custom resources applied with their CRD; two object sets": {
214 objs: []*unstructured.Unstructured{
215 testutil.Unstructured(t, resources["crontab1"]),
216 testutil.Unstructured(t, resources["crontab2"]),
217 testutil.Unstructured(t, resources["crd"]),
218 },
219 expected: []object.UnstructuredSet{
220 {
221 testutil.Unstructured(t, resources["crd"]),
222 },
223 {
224 testutil.Unstructured(t, resources["crontab1"]),
225 testutil.Unstructured(t, resources["crontab2"]),
226 },
227 },
228 isError: false,
229 },
230 "two custom resources wit CRD and namespace; two object sets": {
231 objs: []*unstructured.Unstructured{
232 testutil.Unstructured(t, resources["crontab1"]),
233 testutil.Unstructured(t, resources["crontab2"]),
234 testutil.Unstructured(t, resources["namespace"]),
235 testutil.Unstructured(t, resources["crd"]),
236 },
237 expected: []object.UnstructuredSet{
238 {
239 testutil.Unstructured(t, resources["crd"]),
240 testutil.Unstructured(t, resources["namespace"]),
241 },
242 {
243 testutil.Unstructured(t, resources["crontab1"]),
244 testutil.Unstructured(t, resources["crontab2"]),
245 },
246 },
247 isError: false,
248 },
249 "two objects depends on each other is cyclic dependency": {
250 objs: []*unstructured.Unstructured{
251 testutil.Unstructured(t, resources["deployment"],
252 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
253 testutil.Unstructured(t, resources["secret"],
254 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
255 },
256 expected: []object.UnstructuredSet{},
257 isError: true,
258 },
259 "three objects in cyclic dependency": {
260 objs: []*unstructured.Unstructured{
261 testutil.Unstructured(t, resources["deployment"],
262 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
263 testutil.Unstructured(t, resources["secret"],
264 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["pod"]))),
265 testutil.Unstructured(t, resources["pod"],
266 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
267 },
268 expected: []object.UnstructuredSet{},
269 isError: true,
270 },
271 }
272
273 for tn, tc := range testCases {
274 t.Run(tn, func(t *testing.T) {
275 actual, err := SortObjs(tc.objs)
276 if tc.isError {
277 assert.NotNil(t, err, "expected error, but received none")
278 return
279 }
280 assert.Nil(t, err, "unexpected error received")
281 verifyObjSets(t, tc.expected, actual)
282 })
283 }
284 }
285
286 func TestReverseSortObjs(t *testing.T) {
287 testCases := map[string]struct {
288 objs []*unstructured.Unstructured
289 expected []object.UnstructuredSet
290 isError bool
291 }{
292 "no objects returns no object sets": {
293 objs: []*unstructured.Unstructured{},
294 expected: []object.UnstructuredSet{},
295 isError: false,
296 },
297 "one object returns single object set": {
298 objs: []*unstructured.Unstructured{
299 testutil.Unstructured(t, resources["deployment"]),
300 },
301 expected: []object.UnstructuredSet{
302 {
303 testutil.Unstructured(t, resources["deployment"]),
304 },
305 },
306 isError: false,
307 },
308 "three objects depend on another; three single object sets in opposite order": {
309 objs: []*unstructured.Unstructured{
310 testutil.Unstructured(t, resources["deployment"],
311 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
312 testutil.Unstructured(t, resources["secret"],
313 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["pod"]))),
314 testutil.Unstructured(t, resources["pod"]),
315 },
316 expected: []object.UnstructuredSet{
317 {
318 testutil.Unstructured(t, resources["deployment"]),
319 },
320 {
321 testutil.Unstructured(t, resources["secret"]),
322 },
323 {
324 testutil.Unstructured(t, resources["pod"]),
325 },
326 },
327 isError: false,
328 },
329 "two objects applied with their namespace; two sets in opposite order": {
330 objs: []*unstructured.Unstructured{
331 testutil.Unstructured(t, resources["deployment"]),
332 testutil.Unstructured(t, resources["namespace"]),
333 testutil.Unstructured(t, resources["secret"]),
334 },
335 expected: []object.UnstructuredSet{
336 {
337 testutil.Unstructured(t, resources["secret"]),
338 testutil.Unstructured(t, resources["deployment"]),
339 },
340 {
341 testutil.Unstructured(t, resources["namespace"]),
342 },
343 },
344 isError: false,
345 },
346 "two custom resources wit CRD and namespace; two object sets in opposite order": {
347 objs: []*unstructured.Unstructured{
348 testutil.Unstructured(t, resources["crontab1"]),
349 testutil.Unstructured(t, resources["crontab2"]),
350 testutil.Unstructured(t, resources["namespace"]),
351 testutil.Unstructured(t, resources["crd"]),
352 },
353 expected: []object.UnstructuredSet{
354 {
355 testutil.Unstructured(t, resources["crontab1"]),
356 testutil.Unstructured(t, resources["crontab2"]),
357 },
358 {
359 testutil.Unstructured(t, resources["crd"]),
360 testutil.Unstructured(t, resources["namespace"]),
361 },
362 },
363 isError: false,
364 },
365 }
366
367 for tn, tc := range testCases {
368 t.Run(tn, func(t *testing.T) {
369 actual, err := ReverseSortObjs(tc.objs)
370 if tc.isError {
371 assert.NotNil(t, err, "expected error, but received none")
372 return
373 }
374 assert.Nil(t, err, "unexpected error received")
375 verifyObjSets(t, tc.expected, actual)
376 })
377 }
378 }
379
380 func TestDependencyGraph(t *testing.T) {
381
382 asserter := testutil.NewAsserter(
383 cmpopts.EquateErrors(),
384 graphComparer(),
385 )
386
387 testCases := map[string]struct {
388 objs object.UnstructuredSet
389 graph *Graph
390 expectedError error
391 }{
392 "no objects": {
393 objs: object.UnstructuredSet{},
394 graph: New(),
395 },
396 "one object no dependencies": {
397 objs: object.UnstructuredSet{
398 testutil.Unstructured(t, resources["deployment"]),
399 },
400 graph: &Graph{
401 edges: map[object.ObjMetadata]object.ObjMetadataSet{
402 testutil.ToIdentifier(t, resources["deployment"]): {},
403 },
404 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
405 testutil.ToIdentifier(t, resources["deployment"]): {},
406 },
407 },
408 },
409 "two unrelated objects": {
410 objs: object.UnstructuredSet{
411 testutil.Unstructured(t, resources["deployment"]),
412 testutil.Unstructured(t, resources["secret"]),
413 },
414 graph: &Graph{
415 edges: map[object.ObjMetadata]object.ObjMetadataSet{
416 testutil.ToIdentifier(t, resources["deployment"]): {},
417 testutil.ToIdentifier(t, resources["secret"]): {},
418 },
419 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
420 testutil.ToIdentifier(t, resources["deployment"]): {},
421 testutil.ToIdentifier(t, resources["secret"]): {},
422 },
423 },
424 },
425 "two objects one dependency": {
426 objs: object.UnstructuredSet{
427 testutil.Unstructured(t, resources["deployment"],
428 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
429 testutil.Unstructured(t, resources["secret"]),
430 },
431 graph: &Graph{
432 edges: map[object.ObjMetadata]object.ObjMetadataSet{
433 testutil.ToIdentifier(t, resources["deployment"]): {
434 testutil.ToIdentifier(t, resources["secret"]),
435 },
436 testutil.ToIdentifier(t, resources["secret"]): {},
437 },
438 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
439 testutil.ToIdentifier(t, resources["deployment"]): {},
440 testutil.ToIdentifier(t, resources["secret"]): {
441 testutil.ToIdentifier(t, resources["deployment"]),
442 },
443 },
444 },
445 },
446 "three objects two dependencies": {
447 objs: object.UnstructuredSet{
448 testutil.Unstructured(t, resources["deployment"],
449 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
450 testutil.Unstructured(t, resources["secret"],
451 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["pod"]))),
452 testutil.Unstructured(t, resources["pod"]),
453 },
454 graph: &Graph{
455 edges: map[object.ObjMetadata]object.ObjMetadataSet{
456 testutil.ToIdentifier(t, resources["deployment"]): {
457 testutil.ToIdentifier(t, resources["secret"]),
458 },
459 testutil.ToIdentifier(t, resources["secret"]): {
460 testutil.ToIdentifier(t, resources["pod"]),
461 },
462 testutil.ToIdentifier(t, resources["pod"]): {},
463 },
464 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
465 testutil.ToIdentifier(t, resources["pod"]): {
466 testutil.ToIdentifier(t, resources["secret"]),
467 },
468 testutil.ToIdentifier(t, resources["secret"]): {
469 testutil.ToIdentifier(t, resources["deployment"]),
470 },
471 testutil.ToIdentifier(t, resources["deployment"]): {},
472 },
473 },
474 },
475 "three objects two dependencies on the same object": {
476 objs: object.UnstructuredSet{
477 testutil.Unstructured(t, resources["deployment"],
478 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
479 testutil.Unstructured(t, resources["pod"],
480 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
481 testutil.Unstructured(t, resources["secret"]),
482 },
483 graph: &Graph{
484 edges: map[object.ObjMetadata]object.ObjMetadataSet{
485 testutil.ToIdentifier(t, resources["deployment"]): {
486 testutil.ToIdentifier(t, resources["secret"]),
487 },
488 testutil.ToIdentifier(t, resources["pod"]): {
489 testutil.ToIdentifier(t, resources["secret"]),
490 },
491 testutil.ToIdentifier(t, resources["secret"]): {},
492 },
493 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
494 testutil.ToIdentifier(t, resources["secret"]): {
495 testutil.ToIdentifier(t, resources["deployment"]),
496 testutil.ToIdentifier(t, resources["pod"]),
497 },
498 testutil.ToIdentifier(t, resources["pod"]): {},
499 testutil.ToIdentifier(t, resources["deployment"]): {},
500 },
501 },
502 },
503 "two objects and their namespace": {
504 objs: object.UnstructuredSet{
505 testutil.Unstructured(t, resources["deployment"]),
506 testutil.Unstructured(t, resources["namespace"]),
507 testutil.Unstructured(t, resources["secret"]),
508 },
509 graph: &Graph{
510 edges: map[object.ObjMetadata]object.ObjMetadataSet{
511 testutil.ToIdentifier(t, resources["deployment"]): {
512 testutil.ToIdentifier(t, resources["namespace"]),
513 },
514 testutil.ToIdentifier(t, resources["secret"]): {
515 testutil.ToIdentifier(t, resources["namespace"]),
516 },
517 testutil.ToIdentifier(t, resources["namespace"]): {},
518 },
519 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
520 testutil.ToIdentifier(t, resources["namespace"]): {
521 testutil.ToIdentifier(t, resources["secret"]),
522 testutil.ToIdentifier(t, resources["deployment"]),
523 },
524 testutil.ToIdentifier(t, resources["secret"]): {},
525 testutil.ToIdentifier(t, resources["deployment"]): {},
526 },
527 },
528 },
529 "two custom resources and their CRD": {
530 objs: object.UnstructuredSet{
531 testutil.Unstructured(t, resources["crontab1"]),
532 testutil.Unstructured(t, resources["crontab2"]),
533 testutil.Unstructured(t, resources["crd"]),
534 },
535 graph: &Graph{
536 edges: map[object.ObjMetadata]object.ObjMetadataSet{
537 testutil.ToIdentifier(t, resources["crontab1"]): {
538 testutil.ToIdentifier(t, resources["crd"]),
539 },
540 testutil.ToIdentifier(t, resources["crontab2"]): {
541 testutil.ToIdentifier(t, resources["crd"]),
542 },
543 testutil.ToIdentifier(t, resources["crd"]): {},
544 },
545 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
546 testutil.ToIdentifier(t, resources["crd"]): {
547 testutil.ToIdentifier(t, resources["crontab1"]),
548 testutil.ToIdentifier(t, resources["crontab2"]),
549 },
550 testutil.ToIdentifier(t, resources["crontab1"]): {},
551 testutil.ToIdentifier(t, resources["crontab2"]): {},
552 },
553 },
554 },
555 "two custom resources with their CRD and namespace": {
556 objs: object.UnstructuredSet{
557 testutil.Unstructured(t, resources["crontab1"]),
558 testutil.Unstructured(t, resources["crontab2"]),
559 testutil.Unstructured(t, resources["namespace"]),
560 testutil.Unstructured(t, resources["crd"]),
561 },
562 graph: &Graph{
563 edges: map[object.ObjMetadata]object.ObjMetadataSet{
564 testutil.ToIdentifier(t, resources["crontab1"]): {
565 testutil.ToIdentifier(t, resources["crd"]),
566 testutil.ToIdentifier(t, resources["namespace"]),
567 },
568 testutil.ToIdentifier(t, resources["crontab2"]): {
569 testutil.ToIdentifier(t, resources["crd"]),
570 testutil.ToIdentifier(t, resources["namespace"]),
571 },
572 testutil.ToIdentifier(t, resources["crd"]): {},
573 testutil.ToIdentifier(t, resources["namespace"]): {},
574 },
575 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
576 testutil.ToIdentifier(t, resources["crd"]): {
577 testutil.ToIdentifier(t, resources["crontab1"]),
578 testutil.ToIdentifier(t, resources["crontab2"]),
579 },
580 testutil.ToIdentifier(t, resources["namespace"]): {
581 testutil.ToIdentifier(t, resources["crontab1"]),
582 testutil.ToIdentifier(t, resources["crontab2"]),
583 },
584 testutil.ToIdentifier(t, resources["crontab1"]): {},
585 testutil.ToIdentifier(t, resources["crontab2"]): {},
586 },
587 },
588 },
589 "two object cyclic dependency": {
590 objs: object.UnstructuredSet{
591 testutil.Unstructured(t, resources["deployment"],
592 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
593 testutil.Unstructured(t, resources["secret"],
594 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
595 },
596 graph: &Graph{
597 edges: map[object.ObjMetadata]object.ObjMetadataSet{
598 testutil.ToIdentifier(t, resources["deployment"]): {
599 testutil.ToIdentifier(t, resources["secret"]),
600 },
601 testutil.ToIdentifier(t, resources["secret"]): {
602 testutil.ToIdentifier(t, resources["deployment"]),
603 },
604 },
605 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
606 testutil.ToIdentifier(t, resources["secret"]): {
607 testutil.ToIdentifier(t, resources["deployment"]),
608 },
609 testutil.ToIdentifier(t, resources["deployment"]): {
610 testutil.ToIdentifier(t, resources["secret"]),
611 },
612 },
613 },
614 },
615 "three object cyclic dependency": {
616 objs: object.UnstructuredSet{
617 testutil.Unstructured(t, resources["deployment"],
618 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
619 testutil.Unstructured(t, resources["secret"],
620 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["pod"]))),
621 testutil.Unstructured(t, resources["pod"],
622 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
623 },
624 graph: &Graph{
625 edges: map[object.ObjMetadata]object.ObjMetadataSet{
626 testutil.ToIdentifier(t, resources["deployment"]): {
627 testutil.ToIdentifier(t, resources["secret"]),
628 },
629 testutil.ToIdentifier(t, resources["secret"]): {
630 testutil.ToIdentifier(t, resources["pod"]),
631 },
632 testutil.ToIdentifier(t, resources["pod"]): {
633 testutil.ToIdentifier(t, resources["deployment"]),
634 },
635 },
636 reverseEdges: map[object.ObjMetadata]object.ObjMetadataSet{
637 testutil.ToIdentifier(t, resources["deployment"]): {
638 testutil.ToIdentifier(t, resources["pod"]),
639 },
640 testutil.ToIdentifier(t, resources["pod"]): {
641 testutil.ToIdentifier(t, resources["secret"]),
642 },
643 testutil.ToIdentifier(t, resources["secret"]): {
644 testutil.ToIdentifier(t, resources["deployment"]),
645 },
646 },
647 },
648 },
649 }
650
651 for tn, tc := range testCases {
652 t.Run(tn, func(t *testing.T) {
653 g, err := DependencyGraph(tc.objs)
654 if tc.expectedError != nil {
655 require.EqualError(t, err, tc.expectedError.Error())
656 return
657 }
658 assert.NoError(t, err)
659 asserter.Equal(t, tc.graph, g)
660 })
661 }
662 }
663
664 func TestHydrateSetList(t *testing.T) {
665 testCases := map[string]struct {
666 idSetList []object.ObjMetadataSet
667 objs object.UnstructuredSet
668 expected []object.UnstructuredSet
669 }{
670 "no object sets": {
671 idSetList: []object.ObjMetadataSet{},
672 expected: nil,
673 },
674 "one object set": {
675 idSetList: []object.ObjMetadataSet{
676 {
677 testutil.ToIdentifier(t, resources["deployment"]),
678 },
679 },
680 objs: object.UnstructuredSet{
681 testutil.Unstructured(t, resources["deployment"]),
682 },
683 expected: []object.UnstructuredSet{
684 {
685 testutil.Unstructured(t, resources["deployment"]),
686 },
687 },
688 },
689 "two out of three": {
690 idSetList: []object.ObjMetadataSet{
691 {
692 testutil.ToIdentifier(t, resources["deployment"]),
693 },
694 {
695 testutil.ToIdentifier(t, resources["secret"]),
696 },
697 {
698 testutil.ToIdentifier(t, resources["pod"]),
699 },
700 },
701 objs: object.UnstructuredSet{
702 testutil.Unstructured(t, resources["deployment"]),
703 testutil.Unstructured(t, resources["pod"]),
704 },
705 expected: []object.UnstructuredSet{
706 {
707 testutil.Unstructured(t, resources["deployment"]),
708 },
709 {
710 testutil.Unstructured(t, resources["pod"]),
711 },
712 },
713 },
714 "two uneven sets": {
715 idSetList: []object.ObjMetadataSet{
716 {
717 testutil.ToIdentifier(t, resources["secret"]),
718 testutil.ToIdentifier(t, resources["deployment"]),
719 },
720 {
721 testutil.ToIdentifier(t, resources["namespace"]),
722 },
723 },
724 objs: object.UnstructuredSet{
725 testutil.Unstructured(t, resources["namespace"]),
726 testutil.Unstructured(t, resources["deployment"]),
727 testutil.Unstructured(t, resources["secret"]),
728 testutil.Unstructured(t, resources["pod"]),
729 },
730 expected: []object.UnstructuredSet{
731 {
732 testutil.Unstructured(t, resources["secret"]),
733 testutil.Unstructured(t, resources["deployment"]),
734 },
735 {
736 testutil.Unstructured(t, resources["namespace"]),
737 },
738 },
739 },
740 "one of two sets": {
741 idSetList: []object.ObjMetadataSet{
742 {
743 testutil.ToIdentifier(t, resources["namespace"]),
744 testutil.ToIdentifier(t, resources["crd"]),
745 },
746 {
747 testutil.ToIdentifier(t, resources["crontab1"]),
748 testutil.ToIdentifier(t, resources["crontab2"]),
749 },
750 },
751 objs: object.UnstructuredSet{
752 testutil.Unstructured(t, resources["namespace"]),
753 testutil.Unstructured(t, resources["crd"]),
754 },
755 expected: []object.UnstructuredSet{
756 {
757 testutil.Unstructured(t, resources["namespace"]),
758 testutil.Unstructured(t, resources["crd"]),
759 },
760 },
761 },
762 }
763
764 for tn, tc := range testCases {
765 t.Run(tn, func(t *testing.T) {
766 objSetList := HydrateSetList(tc.idSetList, tc.objs)
767 assert.Equal(t, tc.expected, objSetList)
768 })
769 }
770 }
771
772 func TestReverseSetList(t *testing.T) {
773 testCases := map[string]struct {
774 setList []object.UnstructuredSet
775 expected []object.UnstructuredSet
776 }{
777 "no object sets": {
778 setList: []object.UnstructuredSet{},
779 expected: []object.UnstructuredSet{},
780 },
781 "one object set": {
782 setList: []object.UnstructuredSet{
783 {
784 testutil.Unstructured(t, resources["deployment"]),
785 },
786 },
787 expected: []object.UnstructuredSet{
788 {
789 testutil.Unstructured(t, resources["deployment"]),
790 },
791 },
792 },
793 "three object sets": {
794 setList: []object.UnstructuredSet{
795 {
796 testutil.Unstructured(t, resources["deployment"]),
797 },
798 {
799 testutil.Unstructured(t, resources["secret"]),
800 },
801 {
802 testutil.Unstructured(t, resources["pod"]),
803 },
804 },
805 expected: []object.UnstructuredSet{
806 {
807 testutil.Unstructured(t, resources["pod"]),
808 },
809 {
810 testutil.Unstructured(t, resources["secret"]),
811 },
812 {
813 testutil.Unstructured(t, resources["deployment"]),
814 },
815 },
816 },
817 "two uneven sets": {
818 setList: []object.UnstructuredSet{
819 {
820 testutil.Unstructured(t, resources["secret"]),
821 testutil.Unstructured(t, resources["deployment"]),
822 },
823 {
824 testutil.Unstructured(t, resources["namespace"]),
825 },
826 },
827 expected: []object.UnstructuredSet{
828 {
829 testutil.Unstructured(t, resources["namespace"]),
830 },
831 {
832 testutil.Unstructured(t, resources["deployment"]),
833 testutil.Unstructured(t, resources["secret"]),
834 },
835 },
836 },
837 "two even sets": {
838 setList: []object.UnstructuredSet{
839 {
840 testutil.Unstructured(t, resources["crontab1"]),
841 testutil.Unstructured(t, resources["crontab2"]),
842 },
843 {
844 testutil.Unstructured(t, resources["crd"]),
845 testutil.Unstructured(t, resources["namespace"]),
846 },
847 },
848 expected: []object.UnstructuredSet{
849 {
850 testutil.Unstructured(t, resources["namespace"]),
851 testutil.Unstructured(t, resources["crd"]),
852 },
853 {
854 testutil.Unstructured(t, resources["crontab2"]),
855 testutil.Unstructured(t, resources["crontab1"]),
856 },
857 },
858 },
859 }
860
861 for tn, tc := range testCases {
862 t.Run(tn, func(t *testing.T) {
863 ReverseSetList(tc.setList)
864 assert.Equal(t, tc.expected, tc.setList)
865 })
866 }
867 }
868
869 func TestApplyTimeMutationEdges(t *testing.T) {
870 testCases := map[string]struct {
871 objs []*unstructured.Unstructured
872 expected []Edge
873 expectedError error
874 }{
875 "no objects adds no graph edges": {
876 objs: []*unstructured.Unstructured{},
877 expected: []Edge{},
878 },
879 "no depends-on annotations adds no graph edges": {
880 objs: []*unstructured.Unstructured{
881 testutil.Unstructured(t, resources["deployment"]),
882 },
883 expected: []Edge{},
884 },
885 "no depends-on annotations, two objects, adds no graph edges": {
886 objs: []*unstructured.Unstructured{
887 testutil.Unstructured(t, resources["deployment"]),
888 testutil.Unstructured(t, resources["secret"]),
889 },
890 expected: []Edge{},
891 },
892 "two dependent objects, adds one edge": {
893 objs: []*unstructured.Unstructured{
894 testutil.Unstructured(
895 t,
896 resources["deployment"],
897 mutationutil.AddApplyTimeMutation(t, &mutation.ApplyTimeMutation{
898 {
899 SourceRef: mutation.ResourceReferenceFromObjMetadata(
900 testutil.ToIdentifier(t, resources["secret"]),
901 ),
902 SourcePath: "unused",
903 TargetPath: "unused",
904 Token: "unused",
905 },
906 }),
907 ),
908 testutil.Unstructured(t, resources["secret"]),
909 },
910 expected: []Edge{
911 {
912 From: testutil.ToIdentifier(t, resources["deployment"]),
913 To: testutil.ToIdentifier(t, resources["secret"]),
914 },
915 },
916 },
917 "three dependent objects, adds two edges": {
918 objs: []*unstructured.Unstructured{
919 testutil.Unstructured(
920 t,
921 resources["deployment"],
922 mutationutil.AddApplyTimeMutation(t, &mutation.ApplyTimeMutation{
923 {
924 SourceRef: mutation.ResourceReferenceFromObjMetadata(
925 testutil.ToIdentifier(t, resources["secret"]),
926 ),
927 SourcePath: "unused",
928 TargetPath: "unused",
929 Token: "unused",
930 },
931 }),
932 ),
933 testutil.Unstructured(
934 t,
935 resources["pod"],
936 mutationutil.AddApplyTimeMutation(t, &mutation.ApplyTimeMutation{
937 {
938 SourceRef: mutation.ResourceReferenceFromObjMetadata(
939 testutil.ToIdentifier(t, resources["secret"]),
940 ),
941 SourcePath: "unused",
942 TargetPath: "unused",
943 Token: "unused",
944 },
945 }),
946 ),
947 testutil.Unstructured(t, resources["secret"]),
948 },
949 expected: []Edge{
950 {
951 From: testutil.ToIdentifier(t, resources["deployment"]),
952 To: testutil.ToIdentifier(t, resources["secret"]),
953 },
954 {
955 From: testutil.ToIdentifier(t, resources["pod"]),
956 To: testutil.ToIdentifier(t, resources["secret"]),
957 },
958 },
959 },
960 "pod has two dependencies, adds two edges": {
961 objs: []*unstructured.Unstructured{
962 testutil.Unstructured(
963 t,
964 resources["pod"],
965 mutationutil.AddApplyTimeMutation(t, &mutation.ApplyTimeMutation{
966 {
967 SourceRef: mutation.ResourceReferenceFromObjMetadata(
968 testutil.ToIdentifier(t, resources["secret"]),
969 ),
970 SourcePath: "unused",
971 TargetPath: "unused",
972 Token: "unused",
973 },
974 {
975 SourceRef: mutation.ResourceReferenceFromObjMetadata(
976 testutil.ToIdentifier(t, resources["deployment"]),
977 ),
978 SourcePath: "unused",
979 TargetPath: "unused",
980 Token: "unused",
981 },
982 }),
983 ),
984 testutil.Unstructured(t, resources["deployment"]),
985 testutil.Unstructured(t, resources["secret"]),
986 },
987 expected: []Edge{
988 {
989 From: testutil.ToIdentifier(t, resources["pod"]),
990 To: testutil.ToIdentifier(t, resources["secret"]),
991 },
992 {
993 From: testutil.ToIdentifier(t, resources["pod"]),
994 To: testutil.ToIdentifier(t, resources["deployment"]),
995 },
996 },
997 },
998 "error: invalid annotation": {
999 objs: []*unstructured.Unstructured{
1000 {
1001 Object: map[string]interface{}{
1002 "apiVersion": "apps/v1",
1003 "kind": "Deployment",
1004 "metadata": map[string]interface{}{
1005 "name": "foo",
1006 "namespace": "default",
1007 "annotations": map[string]interface{}{
1008 mutation.Annotation: "invalid-mutation",
1009 },
1010 },
1011 },
1012 },
1013 },
1014 expected: []Edge{},
1015 expectedError: validation.NewError(
1016 object.InvalidAnnotationError{
1017 Annotation: mutation.Annotation,
1018 Cause: errors.New("error unmarshaling JSON: " +
1019 "while decoding JSON: json: " +
1020 "cannot unmarshal string into Go value of type mutation.ApplyTimeMutation"),
1021 },
1022 object.ObjMetadata{
1023 GroupKind: schema.GroupKind{
1024 Group: "apps",
1025 Kind: "Deployment",
1026 },
1027 Name: "foo",
1028 Namespace: "default",
1029 },
1030 ),
1031 },
1032 "error: dependency not in object set": {
1033 objs: []*unstructured.Unstructured{
1034 testutil.Unstructured(t, resources["pod"],
1035 mutationutil.AddApplyTimeMutation(t, &mutation.ApplyTimeMutation{
1036 {
1037 SourceRef: mutation.ResourceReferenceFromObjMetadata(
1038 testutil.ToIdentifier(t, resources["deployment"]),
1039 ),
1040 },
1041 }),
1042 ),
1043 },
1044 expected: []Edge{},
1045 expectedError: validation.NewError(
1046 object.InvalidAnnotationError{
1047 Annotation: mutation.Annotation,
1048 Cause: ExternalDependencyError{
1049 Edge: Edge{
1050 From: testutil.ToIdentifier(t, resources["pod"]),
1051 To: testutil.ToIdentifier(t, resources["deployment"]),
1052 },
1053 },
1054 },
1055 object.ObjMetadata{
1056 GroupKind: schema.GroupKind{
1057 Group: "",
1058 Kind: "Pod",
1059 },
1060 Name: "test-pod",
1061 Namespace: "test-namespace",
1062 },
1063 ),
1064 },
1065 "error: two invalid objects": {
1066 objs: []*unstructured.Unstructured{
1067 {
1068 Object: map[string]interface{}{
1069 "apiVersion": "apps/v1",
1070 "kind": "Deployment",
1071 "metadata": map[string]interface{}{
1072 "name": "foo",
1073 "namespace": "default",
1074 "annotations": map[string]interface{}{
1075 mutation.Annotation: "invalid-mutation",
1076 },
1077 },
1078 },
1079 },
1080 testutil.Unstructured(t, resources["pod"],
1081 mutationutil.AddApplyTimeMutation(t, &mutation.ApplyTimeMutation{
1082 {
1083 SourceRef: mutation.ResourceReferenceFromObjMetadata(
1084 testutil.ToIdentifier(t, resources["secret"]),
1085 ),
1086 },
1087 }),
1088 ),
1089 },
1090 expected: []Edge{},
1091 expectedError: multierror.New(
1092 validation.NewError(
1093 object.InvalidAnnotationError{
1094 Annotation: mutation.Annotation,
1095 Cause: errors.New("error unmarshaling JSON: " +
1096 "while decoding JSON: json: " +
1097 "cannot unmarshal string into Go value of type mutation.ApplyTimeMutation"),
1098 },
1099 object.ObjMetadata{
1100 GroupKind: schema.GroupKind{
1101 Group: "apps",
1102 Kind: "Deployment",
1103 },
1104 Name: "foo",
1105 Namespace: "default",
1106 },
1107 ),
1108 validation.NewError(
1109 object.InvalidAnnotationError{
1110 Annotation: mutation.Annotation,
1111 Cause: ExternalDependencyError{
1112 Edge: Edge{
1113 From: testutil.ToIdentifier(t, resources["pod"]),
1114 To: testutil.ToIdentifier(t, resources["secret"]),
1115 },
1116 },
1117 },
1118 object.ObjMetadata{
1119 GroupKind: schema.GroupKind{
1120 Group: "",
1121 Kind: "Pod",
1122 },
1123 Name: "test-pod",
1124 Namespace: "test-namespace",
1125 },
1126 ),
1127 ),
1128 },
1129 }
1130
1131 for tn, tc := range testCases {
1132 t.Run(tn, func(t *testing.T) {
1133 g := New()
1134 ids := object.UnstructuredSetToObjMetadataSet(tc.objs)
1135 err := addApplyTimeMutationEdges(g, tc.objs, ids)
1136 if tc.expectedError != nil {
1137 assert.EqualError(t, err, tc.expectedError.Error())
1138 } else {
1139 assert.NoError(t, err)
1140 }
1141 actual := edgeMapToList(g.edges)
1142 verifyEdges(t, tc.expected, actual)
1143 })
1144 }
1145 }
1146
1147 func TestAddDependsOnEdges(t *testing.T) {
1148 testCases := map[string]struct {
1149 objs []*unstructured.Unstructured
1150 expected []Edge
1151 expectedError error
1152 }{
1153 "no objects adds no graph edges": {
1154 objs: []*unstructured.Unstructured{},
1155 expected: []Edge{},
1156 },
1157 "no depends-on annotations adds no graph edges": {
1158 objs: []*unstructured.Unstructured{
1159 testutil.Unstructured(t, resources["deployment"]),
1160 },
1161 expected: []Edge{},
1162 },
1163 "no depends-on annotations, two objects, adds no graph edges": {
1164 objs: []*unstructured.Unstructured{
1165 testutil.Unstructured(t, resources["deployment"]),
1166 testutil.Unstructured(t, resources["secret"]),
1167 },
1168 expected: []Edge{},
1169 },
1170 "two dependent objects, adds one edge": {
1171 objs: []*unstructured.Unstructured{
1172 testutil.Unstructured(t, resources["deployment"],
1173 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1174 testutil.Unstructured(t, resources["secret"]),
1175 },
1176 expected: []Edge{
1177 {
1178 From: testutil.ToIdentifier(t, resources["deployment"]),
1179 To: testutil.ToIdentifier(t, resources["secret"]),
1180 },
1181 },
1182 },
1183 "three dependent objects, adds two edges": {
1184 objs: []*unstructured.Unstructured{
1185 testutil.Unstructured(t, resources["deployment"],
1186 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1187 testutil.Unstructured(t, resources["pod"],
1188 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1189 testutil.Unstructured(t, resources["secret"]),
1190 },
1191 expected: []Edge{
1192 {
1193 From: testutil.ToIdentifier(t, resources["deployment"]),
1194 To: testutil.ToIdentifier(t, resources["secret"]),
1195 },
1196 {
1197 From: testutil.ToIdentifier(t, resources["pod"]),
1198 To: testutil.ToIdentifier(t, resources["secret"]),
1199 },
1200 },
1201 },
1202 "pod has two dependencies, adds two edges": {
1203 objs: []*unstructured.Unstructured{
1204 testutil.Unstructured(t, resources["pod"],
1205 testutil.AddDependsOn(t,
1206 testutil.ToIdentifier(t, resources["secret"]),
1207 testutil.ToIdentifier(t, resources["deployment"]),
1208 ),
1209 ),
1210 testutil.Unstructured(t, resources["deployment"]),
1211 testutil.Unstructured(t, resources["secret"]),
1212 },
1213 expected: []Edge{
1214 {
1215 From: testutil.ToIdentifier(t, resources["pod"]),
1216 To: testutil.ToIdentifier(t, resources["secret"]),
1217 },
1218 {
1219 From: testutil.ToIdentifier(t, resources["pod"]),
1220 To: testutil.ToIdentifier(t, resources["deployment"]),
1221 },
1222 },
1223 },
1224 "error: invalid annotation": {
1225 objs: []*unstructured.Unstructured{
1226 {
1227 Object: map[string]interface{}{
1228 "apiVersion": "apps/v1",
1229 "kind": "Deployment",
1230 "metadata": map[string]interface{}{
1231 "name": "foo",
1232 "namespace": "default",
1233 "annotations": map[string]interface{}{
1234 dependson.Annotation: "invalid-obj-ref",
1235 },
1236 },
1237 },
1238 },
1239 },
1240 expected: []Edge{},
1241 expectedError: validation.NewError(
1242 object.InvalidAnnotationError{
1243 Annotation: dependson.Annotation,
1244 Cause: errors.New("failed to parse object reference (index: 0): " +
1245 `expected 3 or 5 fields, found 1: "invalid-obj-ref"`),
1246 },
1247 object.ObjMetadata{
1248 GroupKind: schema.GroupKind{
1249 Group: "apps",
1250 Kind: "Deployment",
1251 },
1252 Name: "foo",
1253 Namespace: "default",
1254 },
1255 ),
1256 },
1257 "error: duplicate reference": {
1258 objs: []*unstructured.Unstructured{
1259 testutil.Unstructured(t, resources["pod"],
1260 testutil.AddDependsOn(t,
1261 testutil.ToIdentifier(t, resources["deployment"]),
1262 testutil.ToIdentifier(t, resources["deployment"]),
1263 ),
1264 ),
1265 testutil.Unstructured(t, resources["deployment"]),
1266 },
1267 expected: []Edge{
1268 {
1269 From: testutil.ToIdentifier(t, resources["pod"]),
1270 To: testutil.ToIdentifier(t, resources["deployment"]),
1271 },
1272 },
1273 expectedError: validation.NewError(
1274 object.InvalidAnnotationError{
1275 Annotation: dependson.Annotation,
1276 Cause: DuplicateDependencyError{
1277 Edge: Edge{
1278 From: testutil.ToIdentifier(t, resources["pod"]),
1279 To: testutil.ToIdentifier(t, resources["deployment"]),
1280 },
1281 },
1282 },
1283 object.ObjMetadata{
1284 GroupKind: schema.GroupKind{
1285 Group: "",
1286 Kind: "Pod",
1287 },
1288 Name: "test-pod",
1289 Namespace: "test-namespace",
1290 },
1291 ),
1292 },
1293 "error: external dependency": {
1294 objs: []*unstructured.Unstructured{
1295 testutil.Unstructured(t, resources["pod"],
1296 testutil.AddDependsOn(t,
1297 testutil.ToIdentifier(t, resources["deployment"]),
1298 ),
1299 ),
1300 },
1301 expected: []Edge{},
1302 expectedError: validation.NewError(
1303 object.InvalidAnnotationError{
1304 Annotation: dependson.Annotation,
1305 Cause: ExternalDependencyError{
1306 Edge: Edge{
1307 From: testutil.ToIdentifier(t, resources["pod"]),
1308 To: testutil.ToIdentifier(t, resources["deployment"]),
1309 },
1310 },
1311 },
1312 object.ObjMetadata{
1313 GroupKind: schema.GroupKind{
1314 Group: "",
1315 Kind: "Pod",
1316 },
1317 Name: "test-pod",
1318 Namespace: "test-namespace",
1319 },
1320 ),
1321 },
1322 "error: two invalid objects": {
1323 objs: []*unstructured.Unstructured{
1324 {
1325 Object: map[string]interface{}{
1326 "apiVersion": "apps/v1",
1327 "kind": "Deployment",
1328 "metadata": map[string]interface{}{
1329 "name": "foo",
1330 "namespace": "default",
1331 "annotations": map[string]interface{}{
1332 dependson.Annotation: "invalid-obj-ref",
1333 },
1334 },
1335 },
1336 },
1337 testutil.Unstructured(t, resources["pod"],
1338 testutil.AddDependsOn(t,
1339 testutil.ToIdentifier(t, resources["secret"]),
1340 ),
1341 ),
1342 },
1343 expected: []Edge{},
1344 expectedError: multierror.New(
1345 validation.NewError(
1346 object.InvalidAnnotationError{
1347 Annotation: dependson.Annotation,
1348 Cause: errors.New("failed to parse object reference (index: 0): " +
1349 `expected 3 or 5 fields, found 1: "invalid-obj-ref"`),
1350 },
1351 object.ObjMetadata{
1352 GroupKind: schema.GroupKind{
1353 Group: "apps",
1354 Kind: "Deployment",
1355 },
1356 Name: "foo",
1357 Namespace: "default",
1358 },
1359 ),
1360 validation.NewError(
1361 object.InvalidAnnotationError{
1362 Annotation: dependson.Annotation,
1363 Cause: ExternalDependencyError{
1364 Edge: Edge{
1365 From: testutil.ToIdentifier(t, resources["pod"]),
1366 To: testutil.ToIdentifier(t, resources["secret"]),
1367 },
1368 },
1369 },
1370 object.ObjMetadata{
1371 GroupKind: schema.GroupKind{
1372 Group: "",
1373 Kind: "Pod",
1374 },
1375 Name: "test-pod",
1376 Namespace: "test-namespace",
1377 },
1378 ),
1379 ),
1380 },
1381 "error: one object with two errors": {
1382 objs: []*unstructured.Unstructured{
1383 testutil.Unstructured(t, resources["pod"],
1384 testutil.AddDependsOn(t,
1385 testutil.ToIdentifier(t, resources["deployment"]),
1386 testutil.ToIdentifier(t, resources["deployment"]),
1387 ),
1388 ),
1389 },
1390 expected: []Edge{},
1391 expectedError: validation.NewError(
1392 multierror.New(
1393 object.InvalidAnnotationError{
1394 Annotation: dependson.Annotation,
1395 Cause: ExternalDependencyError{
1396 Edge: Edge{
1397 From: testutil.ToIdentifier(t, resources["pod"]),
1398 To: testutil.ToIdentifier(t, resources["deployment"]),
1399 },
1400 },
1401 },
1402 object.InvalidAnnotationError{
1403 Annotation: dependson.Annotation,
1404 Cause: DuplicateDependencyError{
1405 Edge: Edge{
1406 From: testutil.ToIdentifier(t, resources["pod"]),
1407 To: testutil.ToIdentifier(t, resources["deployment"]),
1408 },
1409 },
1410 },
1411 ),
1412 object.ObjMetadata{
1413 GroupKind: schema.GroupKind{
1414 Group: "",
1415 Kind: "Pod",
1416 },
1417 Name: "test-pod",
1418 Namespace: "test-namespace",
1419 },
1420 ),
1421 },
1422 }
1423
1424 for tn, tc := range testCases {
1425 t.Run(tn, func(t *testing.T) {
1426 g := New()
1427 ids := object.UnstructuredSetToObjMetadataSet(tc.objs)
1428 err := addDependsOnEdges(g, tc.objs, ids)
1429 if tc.expectedError != nil {
1430 assert.EqualError(t, err, tc.expectedError.Error())
1431 } else {
1432 assert.NoError(t, err)
1433 }
1434 actual := edgeMapToList(g.edges)
1435 verifyEdges(t, tc.expected, actual)
1436 })
1437 }
1438 }
1439
1440 func TestAddNamespaceEdges(t *testing.T) {
1441 testCases := map[string]struct {
1442 objs []*unstructured.Unstructured
1443 expected []Edge
1444 }{
1445 "no namespace objects adds no graph edges": {
1446 objs: []*unstructured.Unstructured{},
1447 expected: []Edge{},
1448 },
1449 "single namespace adds no graph edges": {
1450 objs: []*unstructured.Unstructured{
1451 testutil.Unstructured(t, resources["namespace"]),
1452 },
1453 expected: []Edge{},
1454 },
1455 "pod within namespace adds one edge": {
1456 objs: []*unstructured.Unstructured{
1457 testutil.Unstructured(t, resources["namespace"]),
1458 testutil.Unstructured(t, resources["pod"]),
1459 },
1460 expected: []Edge{
1461 {
1462 From: testutil.ToIdentifier(t, resources["pod"]),
1463 To: testutil.ToIdentifier(t, resources["namespace"]),
1464 },
1465 },
1466 },
1467 "pod not in namespace does not add edge": {
1468 objs: []*unstructured.Unstructured{
1469 testutil.Unstructured(t, resources["namespace"]),
1470 testutil.Unstructured(t, resources["default-pod"]),
1471 },
1472 expected: []Edge{},
1473 },
1474 "pod, secret, and namespace adds two edges": {
1475 objs: []*unstructured.Unstructured{
1476 testutil.Unstructured(t, resources["namespace"]),
1477 testutil.Unstructured(t, resources["secret"]),
1478 testutil.Unstructured(t, resources["pod"]),
1479 },
1480 expected: []Edge{
1481 {
1482 From: testutil.ToIdentifier(t, resources["pod"]),
1483 To: testutil.ToIdentifier(t, resources["namespace"]),
1484 },
1485 {
1486 From: testutil.ToIdentifier(t, resources["secret"]),
1487 To: testutil.ToIdentifier(t, resources["namespace"]),
1488 },
1489 },
1490 },
1491 "one pod in namespace, one not, adds only one edge": {
1492 objs: []*unstructured.Unstructured{
1493 testutil.Unstructured(t, resources["namespace"]),
1494 testutil.Unstructured(t, resources["default-pod"]),
1495 testutil.Unstructured(t, resources["pod"]),
1496 },
1497 expected: []Edge{
1498 {
1499 From: testutil.ToIdentifier(t, resources["pod"]),
1500 To: testutil.ToIdentifier(t, resources["namespace"]),
1501 },
1502 },
1503 },
1504 }
1505
1506 for tn, tc := range testCases {
1507 t.Run(tn, func(t *testing.T) {
1508 g := New()
1509 ids := object.UnstructuredSetToObjMetadataSet(tc.objs)
1510 addNamespaceEdges(g, tc.objs, ids)
1511 actual := edgeMapToList(g.edges)
1512 verifyEdges(t, tc.expected, actual)
1513 })
1514 }
1515 }
1516
1517 func TestAddCRDEdges(t *testing.T) {
1518 testCases := map[string]struct {
1519 objs []*unstructured.Unstructured
1520 expected []Edge
1521 }{
1522 "no CRD objects adds no graph edges": {
1523 objs: []*unstructured.Unstructured{},
1524 expected: []Edge{},
1525 },
1526 "single namespace adds no graph edges": {
1527 objs: []*unstructured.Unstructured{
1528 testutil.Unstructured(t, resources["crd"]),
1529 },
1530 expected: []Edge{},
1531 },
1532 "two custom resources adds no graph edges": {
1533 objs: []*unstructured.Unstructured{
1534 testutil.Unstructured(t, resources["crontab1"]),
1535 testutil.Unstructured(t, resources["crontab2"]),
1536 },
1537 expected: []Edge{},
1538 },
1539 "two custom resources with crd adds two edges": {
1540 objs: []*unstructured.Unstructured{
1541 testutil.Unstructured(t, resources["crd"]),
1542 testutil.Unstructured(t, resources["crontab1"]),
1543 testutil.Unstructured(t, resources["crontab2"]),
1544 },
1545 expected: []Edge{
1546 {
1547 From: testutil.ToIdentifier(t, resources["crontab1"]),
1548 To: testutil.ToIdentifier(t, resources["crd"]),
1549 },
1550 {
1551 From: testutil.ToIdentifier(t, resources["crontab2"]),
1552 To: testutil.ToIdentifier(t, resources["crd"]),
1553 },
1554 },
1555 },
1556 }
1557
1558 for tn, tc := range testCases {
1559 t.Run(tn, func(t *testing.T) {
1560 g := New()
1561 ids := object.UnstructuredSetToObjMetadataSet(tc.objs)
1562 addCRDEdges(g, tc.objs, ids)
1563 actual := edgeMapToList(g.edges)
1564 verifyEdges(t, tc.expected, actual)
1565 })
1566 }
1567 }
1568
1569
1570
1571 func verifyObjSets(t *testing.T, expected []object.UnstructuredSet, actual []object.UnstructuredSet) {
1572 if len(expected) != len(actual) {
1573 t.Fatalf("expected (%d) object sets, got (%d)", len(expected), len(actual))
1574 return
1575 }
1576
1577 for i := range expected {
1578 expectedSet := expected[i]
1579 actualSet := actual[i]
1580 if len(expectedSet) != len(actualSet) {
1581 t.Fatalf("set %d: expected object size (%d), got (%d)", i, len(expectedSet), len(actualSet))
1582 return
1583 }
1584 for _, actualObj := range actualSet {
1585 if !containsObjs(expectedSet, actualObj) {
1586 t.Fatalf("set #%d: actual object (%v) not found in set of expected objects", i, actualObj)
1587 return
1588 }
1589 }
1590 }
1591 }
1592
1593
1594
1595 func containsObjs(objs []*unstructured.Unstructured, obj *unstructured.Unstructured) bool {
1596 ids := object.UnstructuredSetToObjMetadataSet(objs)
1597 id := object.UnstructuredToObjMetadata(obj)
1598 for _, i := range ids {
1599 if i == id {
1600 return true
1601 }
1602 }
1603 return false
1604 }
1605
1606
1607
1608 func verifyEdges(t *testing.T, expected []Edge, actual []Edge) {
1609 if len(expected) != len(actual) {
1610 t.Fatalf("expected (%d) edges, got (%d)", len(expected), len(actual))
1611 return
1612 }
1613 for _, actualEdge := range actual {
1614 if !containsEdge(expected, actualEdge) {
1615 t.Errorf("actual Edge (%v) not found in expected Edges", actualEdge)
1616 return
1617 }
1618 }
1619 }
1620
1621
1622
1623 func containsEdge(edges []Edge, edge Edge) bool {
1624 for _, e := range edges {
1625 if e.To == edge.To && e.From == edge.From {
1626 return true
1627 }
1628 }
1629 return false
1630 }
1631
1632
1633 func graphComparer() cmp.Option {
1634 return cmp.Comparer(func(x, y *Graph) bool {
1635 if x == nil {
1636 return y == nil
1637 }
1638 if y == nil {
1639 return false
1640 }
1641 return cmp.Equal(x.edges, y.edges) &&
1642 cmp.Equal(x.reverseEdges, y.reverseEdges)
1643 })
1644 }
1645
View as plain text