1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package crdgeneration
16
17 import (
18 "testing"
19
20 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdgeneration/crdboilerplate"
21 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gvks/supportedgvks"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
25 testdclschemaloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/dclschemaloader"
26 testservicemetadataloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemetadataloader"
27
28 "github.com/google/go-cmp/cmp"
29 "github.com/nasa9084/go-openapi"
30 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
31 )
32
33 func TestDCLSchemaToJSONSchema(t *testing.T) {
34 tests := []struct {
35 name string
36 dclSchema *openapi.Schema
37 specSchema *apiextensions.JSONSchemaProps
38 statusSchema *apiextensions.JSONSchemaProps
39 resource dclmetadata.Resource
40 hasErrorOnSpecGeneration bool
41 }{
42 {
43 name: "primitive fields",
44 dclSchema: &openapi.Schema{
45 Type: "object",
46 Properties: map[string]*openapi.Schema{
47 "foo": {
48 Type: "string",
49 },
50 "bar": {
51 Type: "integer",
52 },
53 "baz": {
54 Type: "boolean",
55 },
56 "quz": {
57 Type: "number",
58 },
59 },
60 Required: []string{"foo"},
61 },
62 specSchema: &apiextensions.JSONSchemaProps{
63 Type: "object",
64 Properties: map[string]apiextensions.JSONSchemaProps{
65 "foo": {
66 Type: "string",
67 },
68 "bar": {
69 Type: "integer",
70 },
71 "baz": {
72 Type: "boolean",
73 },
74 "quz": {
75 Type: "number",
76 },
77 },
78 Required: []string{"foo"},
79 },
80 statusSchema: nil,
81 },
82 {
83 name: "primitive fields with read-only fields",
84 dclSchema: &openapi.Schema{
85 Type: "object",
86 Properties: map[string]*openapi.Schema{
87 "foo": {
88 Type: "string",
89 },
90 "bar": {
91 Type: "integer",
92 ReadOnly: true,
93 },
94 },
95 Required: []string{"foo"},
96 },
97 specSchema: &apiextensions.JSONSchemaProps{
98 Type: "object",
99 Properties: map[string]apiextensions.JSONSchemaProps{
100 "foo": {
101 Type: "string",
102 },
103 },
104 Required: []string{"foo"},
105 },
106 statusSchema: &apiextensions.JSONSchemaProps{
107 Type: "object",
108 Properties: map[string]apiextensions.JSONSchemaProps{
109 "bar": {
110 Type: "integer",
111 },
112 },
113 },
114 },
115 {
116 name: "nested fields with read-only fields",
117 dclSchema: &openapi.Schema{
118 Type: "object",
119 Properties: map[string]*openapi.Schema{
120 "foo": {
121 Type: "object",
122 Properties: map[string]*openapi.Schema{
123 "nestedField1": {
124 Type: "boolean",
125 },
126 "nestedField2": {
127 Type: "string",
128 ReadOnly: true,
129 },
130 },
131 Required: []string{"nestedField1"},
132 },
133 "bar": {
134 Type: "integer",
135 },
136 },
137 Required: []string{"foo"},
138 },
139 specSchema: &apiextensions.JSONSchemaProps{
140 Type: "object",
141 Properties: map[string]apiextensions.JSONSchemaProps{
142 "foo": {
143 Type: "object",
144 Properties: map[string]apiextensions.JSONSchemaProps{
145 "nestedField1": {
146 Type: "boolean",
147 },
148 },
149 Required: []string{"nestedField1"},
150 },
151 "bar": {
152 Type: "integer",
153 },
154 },
155 Required: []string{"foo"},
156 },
157 statusSchema: &apiextensions.JSONSchemaProps{
158 Type: "object",
159 Properties: map[string]apiextensions.JSONSchemaProps{
160 "foo": {
161 Type: "object",
162 Properties: map[string]apiextensions.JSONSchemaProps{
163 "nestedField2": {
164 Type: "string",
165 },
166 },
167 },
168 },
169 },
170 },
171 {
172 name: "sensitive field",
173 dclSchema: &openapi.Schema{
174 Type: "object",
175 Properties: map[string]*openapi.Schema{
176 "foo": {
177 Type: "string",
178 Extension: map[string]interface{}{
179 "x-dcl-sensitive": true,
180 },
181 },
182 "bar": {
183 Type: "integer",
184 },
185
186 "baz": {
187 Type: "string",
188 ReadOnly: true,
189 Extension: map[string]interface{}{
190 "x-dcl-sensitive": true,
191 },
192 },
193 },
194 Required: []string{"foo"},
195 },
196 specSchema: &apiextensions.JSONSchemaProps{
197 Type: "object",
198 Properties: map[string]apiextensions.JSONSchemaProps{
199 "foo": crdboilerplate.GetSensitiveFieldSchemaBoilerplate(),
200 "bar": {
201 Type: "integer",
202 },
203 },
204 Required: []string{"foo"},
205 },
206 statusSchema: &apiextensions.JSONSchemaProps{
207 Type: "object",
208 Properties: map[string]apiextensions.JSONSchemaProps{
209 "baz": {
210 Type: "string",
211 },
212 },
213 },
214 },
215 {
216 name: "reference field",
217 dclSchema: &openapi.Schema{
218 Type: "object",
219 Properties: map[string]*openapi.Schema{
220 "foo": {
221 Type: "string",
222 Extension: map[string]interface{}{
223 "x-dcl-references": []interface{}{
224 map[interface{}]interface{}{
225 "resource": "Test1/Foo",
226 "field": "name",
227 },
228 },
229 },
230 },
231 "bar": {
232 Type: "integer",
233 },
234
235 "baz": {
236 Type: "string",
237 ReadOnly: true,
238 Extension: map[string]interface{}{
239 "x-dcl-references": []interface{}{
240 map[interface{}]interface{}{
241 "resource": "FakeService/FakeKind",
242 "field": "name",
243 },
244 },
245 },
246 },
247 },
248 Required: []string{"foo"},
249 },
250 specSchema: &apiextensions.JSONSchemaProps{
251 Type: "object",
252 Properties: map[string]apiextensions.JSONSchemaProps{
253 "fooRef": *crdboilerplate.GetResourceReferenceSchemaBoilerplate(
254 "Allowed value: The Google Cloud resource name of a `Test1Foo` resource (format: `projects/{{project}}/foo/{{name}}`).",
255 ),
256 "bar": {
257 Type: "integer",
258 },
259 },
260 Required: []string{"fooRef"},
261 },
262 statusSchema: &apiextensions.JSONSchemaProps{
263 Type: "object",
264 Properties: map[string]apiextensions.JSONSchemaProps{
265 "baz": {
266 Type: "string",
267 },
268 },
269 },
270 },
271 {
272 name: "reference nested in object",
273 dclSchema: &openapi.Schema{
274 Type: "object",
275 Properties: map[string]*openapi.Schema{
276 "foo": {
277 Type: "object",
278 Properties: map[string]*openapi.Schema{
279 "bar": {
280 Type: "string",
281 Extension: map[string]interface{}{
282 "x-dcl-references": []interface{}{
283 map[interface{}]interface{}{
284 "resource": "Test1/Bar",
285 "field": "name",
286 },
287 },
288 },
289 },
290 "baz": {
291 Type: "integer",
292 },
293 },
294 },
295 },
296 },
297 specSchema: &apiextensions.JSONSchemaProps{
298 Type: "object",
299 Properties: map[string]apiextensions.JSONSchemaProps{
300 "foo": {
301 Type: "object",
302 Properties: map[string]apiextensions.JSONSchemaProps{
303 "barRef": *crdboilerplate.GetResourceReferenceSchemaBoilerplate(
304 "Allowed value: The Google Cloud resource name of a `Test1Bar` resource (format: `projects/{{project}}/bar/{{name}}`).",
305 ),
306 "baz": {
307 Type: "integer",
308 },
309 },
310 },
311 },
312 },
313 statusSchema: nil,
314 },
315 {
316 name: "a list of reference",
317 dclSchema: &openapi.Schema{
318 Type: "object",
319 Properties: map[string]*openapi.Schema{
320 "foos": {
321 Type: "array",
322 Items: &openapi.Schema{
323 Type: "string",
324 Extension: map[string]interface{}{
325 "x-dcl-references": []interface{}{
326 map[interface{}]interface{}{
327 "resource": "Test1/Foo",
328 "field": "name",
329 },
330 },
331 },
332 },
333 },
334 },
335 },
336 specSchema: &apiextensions.JSONSchemaProps{
337 Type: "object",
338 Properties: map[string]apiextensions.JSONSchemaProps{
339 "foos": {
340 Type: "array",
341 Items: &apiextensions.JSONSchemaPropsOrArray{
342 Schema: crdboilerplate.GetResourceReferenceSchemaBoilerplate(
343 "Allowed value: The Google Cloud resource name of a `Test1Foo` resource (format: `projects/{{project}}/foo/{{name}}`).",
344 ),
345 },
346 },
347 },
348 },
349 statusSchema: nil,
350 },
351 {
352 name: "a list of multi-kinds reference",
353 dclSchema: &openapi.Schema{
354 Type: "object",
355 Properties: map[string]*openapi.Schema{
356 "foos": {
357 Type: "array",
358 Items: &openapi.Schema{
359 Type: "string",
360 Extension: map[string]interface{}{
361 "x-dcl-references": []interface{}{
362 map[interface{}]interface{}{
363 "resource": "Test1/Bar",
364 "field": "selfLink",
365 },
366 map[interface{}]interface{}{
367 "resource": "Test2/Baz",
368 "field": "selfLink",
369 },
370 },
371 },
372 },
373 },
374 },
375 },
376 specSchema: &apiextensions.JSONSchemaProps{
377 Type: "object",
378 Properties: map[string]apiextensions.JSONSchemaProps{
379 "foos": {
380 Type: "array",
381 Items: &apiextensions.JSONSchemaPropsOrArray{
382 Schema: crdboilerplate.GetMultiKindResourceReferenceSchemaBoilerplate(
383 "Allowed values:"+
384 "\n* The `selfLink` field of a `Test1Bar` resource."+
385 "\n* The `selfLink` field of a `Test2Baz` resource.",
386 []string{"Test1Bar", "Test2Baz"},
387 ),
388 },
389 },
390 },
391 },
392 statusSchema: nil,
393 },
394 {
395 name: "reference field for multiple kinds",
396 dclSchema: &openapi.Schema{
397 Type: "object",
398 Properties: map[string]*openapi.Schema{
399 "foo": {
400 Type: "string",
401 Extension: map[string]interface{}{
402 "x-dcl-references": []interface{}{
403 map[interface{}]interface{}{
404 "resource": "Test1/Bar",
405 "field": "selfLink",
406 },
407 map[interface{}]interface{}{
408 "resource": "Test2/Baz",
409 "field": "selfLink",
410 },
411 },
412 },
413 },
414 },
415 Required: []string{"foo"},
416 },
417 specSchema: &apiextensions.JSONSchemaProps{
418 Type: "object",
419 Properties: map[string]apiextensions.JSONSchemaProps{
420 "fooRef": *crdboilerplate.GetMultiKindResourceReferenceSchemaBoilerplate(
421 "Allowed values:"+
422 "\n* The `selfLink` field of a `Test1Bar` resource."+
423 "\n* The `selfLink` field of a `Test2Baz` resource.",
424 []string{"Test1Bar", "Test2Baz"},
425 ),
426 },
427 Required: []string{"fooRef"},
428 },
429 statusSchema: nil,
430 },
431 {
432 name: "reference to not-yet-supported resources",
433 dclSchema: &openapi.Schema{
434 Type: "object",
435 Properties: map[string]*openapi.Schema{
436 "foo": {
437 Type: "string",
438 Extension: map[string]interface{}{
439 "x-dcl-references": []interface{}{
440 map[interface{}]interface{}{
441 "resource": "Test1/NotYetSupportedKind",
442 "field": "name",
443 },
444 },
445 },
446 },
447 },
448 Required: []string{"foo"},
449 },
450 specSchema: &apiextensions.JSONSchemaProps{
451 Type: "object",
452 Properties: map[string]apiextensions.JSONSchemaProps{
453 "fooRef": *markReferencedKindsNotSupported(
454 crdboilerplate.GetResourceReferenceSchemaBoilerplate(""),
455 []string{"Test1NotYetSupportedKind"},
456 ),
457 },
458 Required: []string{"fooRef"},
459 },
460 },
461 {
462 name: "the service of referenced resource is not declared",
463 dclSchema: &openapi.Schema{
464 Type: "object",
465 Properties: map[string]*openapi.Schema{
466 "foo": {
467 Type: "string",
468 Extension: map[string]interface{}{
469 "x-dcl-references": []interface{}{
470 map[interface{}]interface{}{
471 "resource": "SomeNotDeclaredService/Foo",
472 "field": "name",
473 },
474 },
475 },
476 },
477 },
478 Required: []string{"foo"},
479 },
480 hasErrorOnSpecGeneration: true,
481 },
482 {
483 name: "referenced resource's (target) field is 'name' but its DCL schema (which contains 'x-dcl-id' extension) is not found",
484 dclSchema: &openapi.Schema{
485 Type: "object",
486 Properties: map[string]*openapi.Schema{
487 "foo": {
488 Type: "string",
489 Extension: map[string]interface{}{
490 "x-dcl-references": []interface{}{
491 map[interface{}]interface{}{
492 "resource": "Test1/FakeTFBasedResource",
493 "field": "name",
494 },
495 },
496 },
497 },
498 },
499 Required: []string{"foo"},
500 },
501 hasErrorOnSpecGeneration: true,
502 },
503 {
504 name: "one hierarchical reference: projectRef",
505 dclSchema: &openapi.Schema{
506 Type: "object",
507 Properties: map[string]*openapi.Schema{
508 "project": {
509 Type: "string",
510 Extension: map[string]interface{}{
511 "x-dcl-references": []interface{}{
512 map[interface{}]interface{}{
513 "resource": "Cloudresourcemanager/Project",
514 "field": "name",
515 "parent": true,
516 },
517 },
518 },
519 },
520 },
521 Required: []string{"project"},
522 },
523
524
525 resource: dclmetadata.Resource{
526 SupportsHierarchicalReferences: true,
527 },
528 specSchema: &apiextensions.JSONSchemaProps{
529 Type: "object",
530 Properties: map[string]apiextensions.JSONSchemaProps{
531 "projectRef": *resourceRefBoilerplateWithDescription(
532 "The Project that this resource belongs to.",
533 "Allowed value: The Google Cloud resource name of a `Project` resource (format: `projects/{{name}}`).",
534 ),
535 },
536 Required: []string{"projectRef"},
537 },
538 statusSchema: nil,
539 },
540 {
541 name: "one hierarchical reference: folderRef",
542 dclSchema: &openapi.Schema{
543 Type: "object",
544 Properties: map[string]*openapi.Schema{
545 "folder": {
546 Type: "string",
547 Extension: map[string]interface{}{
548 "x-dcl-references": []interface{}{
549 map[interface{}]interface{}{
550 "resource": "Cloudresourcemanager/Folder",
551 "field": "name",
552 "parent": true,
553 },
554 },
555 },
556 },
557 },
558 Required: []string{"folder"},
559 },
560
561
562 resource: dclmetadata.Resource{
563 SupportsHierarchicalReferences: true,
564 },
565 specSchema: &apiextensions.JSONSchemaProps{
566 Type: "object",
567 Properties: map[string]apiextensions.JSONSchemaProps{
568 "folderRef": *resourceRefBoilerplateWithDescription(
569 "The Folder that this resource belongs to.",
570 "Allowed value: The Google Cloud resource name of a `Folder` resource (format: `folders/{{name}}`).",
571 ),
572 },
573 Required: []string{"folderRef"},
574 },
575 statusSchema: nil,
576 },
577 {
578 name: "one hierarchical reference: organizationRef",
579 dclSchema: &openapi.Schema{
580 Type: "object",
581 Properties: map[string]*openapi.Schema{
582 "organization": {
583 Type: "string",
584 Extension: map[string]interface{}{
585 "x-dcl-references": []interface{}{
586 map[interface{}]interface{}{
587 "resource": "Cloudresourcemanager/Organization",
588 "field": "name",
589 "parent": true,
590 },
591 },
592 },
593 },
594 },
595 Required: []string{"organization"},
596 },
597
598
599 resource: dclmetadata.Resource{
600 SupportsHierarchicalReferences: true,
601 },
602 specSchema: &apiextensions.JSONSchemaProps{
603 Type: "object",
604 Properties: map[string]apiextensions.JSONSchemaProps{
605 "organizationRef": *markReferencedKindsNotSupported(
606 resourceRefBoilerplateWithDescription(
607 "The Organization that this resource belongs to.",
608 "Allowed value: The Google Cloud resource name of a Google Cloud Organization (format: `organizations/{{name}}`).",
609 ),
610 []string{"Organization"},
611 ),
612 },
613 Required: []string{"organizationRef"},
614 },
615 statusSchema: nil,
616 },
617 {
618 name: "multiple hierarchical references: projectRef, folderRef, and organizationRef",
619 dclSchema: &openapi.Schema{
620 Type: "object",
621 Properties: map[string]*openapi.Schema{
622 "parent": {
623 Type: "string",
624 Extension: map[string]interface{}{
625 "x-dcl-references": []interface{}{
626 map[interface{}]interface{}{
627 "resource": "Cloudresourcemanager/Project",
628 "field": "name",
629 "parent": true,
630 },
631 map[interface{}]interface{}{
632 "resource": "Cloudresourcemanager/Folder",
633 "field": "name",
634 "parent": true,
635 },
636 map[interface{}]interface{}{
637 "resource": "Cloudresourcemanager/Organization",
638 "field": "name",
639 "parent": true,
640 },
641 },
642 },
643 },
644 },
645 Required: []string{"parent"},
646 },
647
648
649 resource: dclmetadata.Resource{
650 SupportsHierarchicalReferences: true,
651 },
652 specSchema: &apiextensions.JSONSchemaProps{
653 Type: "object",
654 Properties: map[string]apiextensions.JSONSchemaProps{
655 "projectRef": *resourceRefBoilerplateWithDescription(
656 "The Project that this resource belongs to. Only one of [projectRef, folderRef, organizationRef] may be specified.",
657 "Allowed value: The Google Cloud resource name of a `Project` resource (format: `projects/{{name}}`).",
658 ),
659 "folderRef": *resourceRefBoilerplateWithDescription(
660 "The Folder that this resource belongs to. Only one of [projectRef, folderRef, organizationRef] may be specified.",
661 "Allowed value: The Google Cloud resource name of a `Folder` resource (format: `folders/{{name}}`).",
662 ),
663 "organizationRef": *markReferencedKindsNotSupported(
664 resourceRefBoilerplateWithDescription(
665 "The Organization that this resource belongs to. Only one of [projectRef, folderRef, organizationRef] may be specified.",
666 "Allowed value: The Google Cloud resource name of a Google Cloud Organization (format: `organizations/{{name}}`).",
667 ),
668 []string{"Organization"},
669 ),
670 },
671 OneOf: []apiextensions.JSONSchemaProps{
672 {Required: []string{"projectRef"}},
673 {Required: []string{"folderRef"}},
674 {Required: []string{"organizationRef"}},
675 },
676 },
677 statusSchema: nil,
678 },
679 {
680 name: "multiple hierarchical references: folderRef and organizationRef",
681 dclSchema: &openapi.Schema{
682 Type: "object",
683 Properties: map[string]*openapi.Schema{
684 "parent": {
685 Type: "string",
686 Extension: map[string]interface{}{
687 "x-dcl-references": []interface{}{
688 map[interface{}]interface{}{
689 "resource": "Cloudresourcemanager/Folder",
690 "field": "name",
691 "parent": true,
692 },
693 map[interface{}]interface{}{
694 "resource": "Cloudresourcemanager/Organization",
695 "field": "name",
696 "parent": true,
697 },
698 },
699 },
700 },
701 },
702 Required: []string{"parent"},
703 },
704
705
706 resource: dclmetadata.Resource{
707 SupportsHierarchicalReferences: true,
708 },
709 specSchema: &apiextensions.JSONSchemaProps{
710 Type: "object",
711 Properties: map[string]apiextensions.JSONSchemaProps{
712 "folderRef": *resourceRefBoilerplateWithDescription(
713 "The Folder that this resource belongs to. Only one of [folderRef, organizationRef] may be specified.",
714 "Allowed value: The Google Cloud resource name of a `Folder` resource (format: `folders/{{name}}`).",
715 ),
716 "organizationRef": *markReferencedKindsNotSupported(
717 resourceRefBoilerplateWithDescription(
718 "The Organization that this resource belongs to. Only one of [folderRef, organizationRef] may be specified.",
719 "Allowed value: The Google Cloud resource name of a Google Cloud Organization (format: `organizations/{{name}}`).",
720 ),
721 []string{"Organization"},
722 ),
723 },
724 OneOf: []apiextensions.JSONSchemaProps{
725 {Required: []string{"folderRef"}},
726 {Required: []string{"organizationRef"}},
727 },
728 },
729 statusSchema: nil,
730 },
731 {
732 name: "resource that supports container annotations and has one hierarchical reference: projectRef",
733 dclSchema: &openapi.Schema{
734 Type: "object",
735 Properties: map[string]*openapi.Schema{
736 "project": {
737 Type: "string",
738 Extension: map[string]interface{}{
739 "x-dcl-references": []interface{}{
740 map[interface{}]interface{}{
741 "resource": "Cloudresourcemanager/Project",
742 "field": "name",
743 "parent": true,
744 },
745 },
746 },
747 },
748 },
749 Required: []string{"project"},
750 },
751 resource: dclmetadata.Resource{
752 SupportsContainerAnnotations: true,
753
754
755 SupportsHierarchicalReferences: true,
756 },
757 specSchema: &apiextensions.JSONSchemaProps{
758 Type: "object",
759 Properties: map[string]apiextensions.JSONSchemaProps{
760 "projectRef": *resourceRefBoilerplateWithDescription(
761 "The Project that this resource belongs to.",
762 "Allowed value: The Google Cloud resource name of a `Project` resource (format: `projects/{{name}}`).",
763 ),
764 },
765 },
766 statusSchema: nil,
767 },
768 {
769 name: "resource that supports container annotations and has multiple hierarchical references: projectRef, folderRef, and organizationRef",
770 dclSchema: &openapi.Schema{
771 Type: "object",
772 Properties: map[string]*openapi.Schema{
773 "parent": {
774 Type: "string",
775 Extension: map[string]interface{}{
776 "x-dcl-references": []interface{}{
777 map[interface{}]interface{}{
778 "resource": "Cloudresourcemanager/Project",
779 "field": "name",
780 "parent": true,
781 },
782 map[interface{}]interface{}{
783 "resource": "Cloudresourcemanager/Folder",
784 "field": "name",
785 "parent": true,
786 },
787 map[interface{}]interface{}{
788 "resource": "Cloudresourcemanager/Organization",
789 "field": "name",
790 "parent": true,
791 },
792 },
793 },
794 },
795 },
796 Required: []string{"parent"},
797 },
798 resource: dclmetadata.Resource{
799 SupportsContainerAnnotations: true,
800
801
802 SupportsHierarchicalReferences: true,
803 },
804 specSchema: &apiextensions.JSONSchemaProps{
805 Type: "object",
806 Properties: map[string]apiextensions.JSONSchemaProps{
807 "projectRef": *resourceRefBoilerplateWithDescription(
808 "The Project that this resource belongs to. Only one of [projectRef, folderRef, organizationRef] may be specified.",
809 "Allowed value: The Google Cloud resource name of a `Project` resource (format: `projects/{{name}}`).",
810 ),
811 "folderRef": *resourceRefBoilerplateWithDescription(
812 "The Folder that this resource belongs to. Only one of [projectRef, folderRef, organizationRef] may be specified.",
813 "Allowed value: The Google Cloud resource name of a `Folder` resource (format: `folders/{{name}}`).",
814 ),
815 "organizationRef": *markReferencedKindsNotSupported(
816 resourceRefBoilerplateWithDescription(
817 "The Organization that this resource belongs to. Only one of [projectRef, folderRef, organizationRef] may be specified.",
818 "Allowed value: The Google Cloud resource name of a Google Cloud Organization (format: `organizations/{{name}}`).",
819 ),
820 []string{"Organization"},
821 ),
822 },
823 OneOf: []apiextensions.JSONSchemaProps{
824 {Required: []string{"projectRef"}},
825 {Required: []string{"folderRef"}},
826 {Required: []string{"organizationRef"}},
827 {
828 Not: &apiextensions.JSONSchemaProps{
829 AnyOf: []apiextensions.JSONSchemaProps{
830 {Required: []string{"projectRef"}},
831 {Required: []string{"folderRef"}},
832 {Required: []string{"organizationRef"}},
833 },
834 },
835 },
836 },
837 },
838 statusSchema: nil,
839 },
840 {
841 name: "container field will be ignored",
842 dclSchema: &openapi.Schema{
843 Type: "object",
844 Properties: map[string]*openapi.Schema{
845 "foo": {
846 Type: "string",
847 },
848 "project": {
849 Type: "string",
850 },
851 },
852 Required: []string{"foo", "project"},
853 },
854 specSchema: &apiextensions.JSONSchemaProps{
855 Type: "object",
856 Properties: map[string]apiextensions.JSONSchemaProps{
857 "foo": {
858 Type: "string",
859 },
860 },
861 Required: []string{"foo"},
862 },
863 statusSchema: nil,
864 },
865 {
866 name: "name field will be converted to ResourceID",
867 dclSchema: &openapi.Schema{
868 Type: "object",
869 Properties: map[string]*openapi.Schema{
870 "foo": {
871 Type: "string",
872 },
873 "name": {
874 Type: "string",
875 },
876 },
877 Required: []string{"foo", "name"},
878 },
879 specSchema: &apiextensions.JSONSchemaProps{
880 Type: "object",
881 Properties: map[string]apiextensions.JSONSchemaProps{
882 "foo": {
883 Type: "string",
884 },
885 "resourceID": {
886 Description: GenerateResourceIDFieldDescription("name", false),
887 Type: "string",
888 },
889 },
890 Required: []string{"foo"},
891 },
892 statusSchema: nil,
893 },
894 {
895 name: "string-object maps",
896 dclSchema: &openapi.Schema{
897 Type: "object",
898 Properties: map[string]*openapi.Schema{
899 "baz": {
900 Type: "object",
901 AdditionalProperties: &openapi.Schema{
902 Type: "object",
903 Properties: map[string]*openapi.Schema{
904 "objectField1": {
905 Type: "string",
906 },
907 "objectField2": {
908 ReadOnly: true,
909 Type: "integer",
910 },
911 "objectField3": {
912 Type: "array",
913 Items: &openapi.Schema{
914 Type: "string",
915 },
916 },
917 "foo": {
918 Type: "string",
919 Extension: map[string]interface{}{
920 "x-dcl-references": []interface{}{
921 map[interface{}]interface{}{
922 "resource": "Test1/Foo",
923 "field": "name",
924 },
925 },
926 },
927 },
928 },
929 },
930 },
931 },
932 },
933 specSchema: &apiextensions.JSONSchemaProps{
934 Type: "object",
935 Properties: map[string]apiextensions.JSONSchemaProps{
936 "baz": {
937 Type: "object",
938 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
939 Schema: &apiextensions.JSONSchemaProps{
940 Type: "object",
941 Properties: map[string]apiextensions.JSONSchemaProps{
942 "objectField1": {
943 Type: "string",
944 },
945 "objectField2": {
946 Type: "integer",
947 },
948 "objectField3": {
949 Type: "array",
950 Items: &apiextensions.JSONSchemaPropsOrArray{
951 Schema: &apiextensions.JSONSchemaProps{
952 Type: "string",
953 },
954 },
955 },
956 "fooRef": *crdboilerplate.GetResourceReferenceSchemaBoilerplate(
957 "Allowed value: The Google Cloud resource name of a `Test1Foo` resource (format: `projects/{{project}}/foo/{{name}}`).",
958 ),
959 },
960 },
961 },
962 },
963 },
964 },
965 statusSchema: nil,
966 },
967 {
968 name: "array type, additionalProperties",
969 dclSchema: &openapi.Schema{
970 Type: "object",
971 Properties: map[string]*openapi.Schema{
972 "foo": {
973 Type: "array",
974 Items: &openapi.Schema{
975 Type: "string",
976 },
977 },
978 "baz": {
979 Type: "object",
980 AdditionalProperties: &openapi.Schema{
981 Type: "string",
982 },
983 },
984 "qux": {
985 Type: "array",
986 Items: &openapi.Schema{
987 Type: "string",
988 },
989 ReadOnly: true,
990 },
991 },
992 Required: []string{"foo"},
993 },
994 specSchema: &apiextensions.JSONSchemaProps{
995 Type: "object",
996 Properties: map[string]apiextensions.JSONSchemaProps{
997 "foo": {
998 Type: "array",
999 Items: &apiextensions.JSONSchemaPropsOrArray{
1000 Schema: &apiextensions.JSONSchemaProps{
1001 Type: "string",
1002 },
1003 },
1004 },
1005 "baz": {
1006 Type: "object",
1007 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
1008 Schema: &apiextensions.JSONSchemaProps{
1009 Type: "string",
1010 },
1011 },
1012 },
1013 },
1014 Required: []string{"foo"},
1015 },
1016 statusSchema: &apiextensions.JSONSchemaProps{
1017 Type: "object",
1018 Properties: map[string]apiextensions.JSONSchemaProps{
1019 "qux": {
1020 Type: "array",
1021 Items: &apiextensions.JSONSchemaPropsOrArray{
1022 Schema: &apiextensions.JSONSchemaProps{
1023 Type: "string",
1024 },
1025 },
1026 },
1027 },
1028 },
1029 },
1030 {
1031 name: "Enum is not exposed by design in CRD schema",
1032 dclSchema: &openapi.Schema{
1033 Type: "object",
1034 Properties: map[string]*openapi.Schema{
1035 "foo": {
1036 Type: "string",
1037 Enum: []string{"VAL1", "VAL2"},
1038 },
1039 "baz": {
1040 Type: "string",
1041 ReadOnly: true,
1042 Enum: []string{"VAL1", "VAL2"},
1043 },
1044 },
1045 Required: []string{"foo"},
1046 },
1047 specSchema: &apiextensions.JSONSchemaProps{
1048 Type: "object",
1049 Properties: map[string]apiextensions.JSONSchemaProps{
1050 "foo": {
1051 Type: "string",
1052 },
1053 },
1054 Required: []string{"foo"},
1055 },
1056 statusSchema: &apiextensions.JSONSchemaProps{
1057 Type: "object",
1058 Properties: map[string]apiextensions.JSONSchemaProps{
1059 "baz": {
1060 Type: "string",
1061 },
1062 },
1063 },
1064 },
1065 {
1066 name: "read-only fields in arrays will be preserved in spec",
1067 dclSchema: &openapi.Schema{
1068 Type: "object",
1069 Properties: map[string]*openapi.Schema{
1070 "foo": {
1071 Type: "array",
1072 Items: &openapi.Schema{
1073 Type: "object",
1074 Properties: map[string]*openapi.Schema{
1075 "nestedField1": {
1076 Type: "boolean",
1077 },
1078 "nestedField2": {
1079 Type: "string",
1080 ReadOnly: true,
1081 },
1082 "nestedObject": {
1083 Type: "object",
1084 Properties: map[string]*openapi.Schema{
1085 "state": {
1086 Type: "string",
1087 ReadOnly: true,
1088 },
1089 "name": {
1090 Type: "string",
1091 },
1092 },
1093 },
1094 "readOnlySensitiveField": {
1095 Type: "string",
1096 ReadOnly: true,
1097 Extension: map[string]interface{}{
1098 "x-dcl-sensitive": true,
1099 },
1100 },
1101 "readOnlyReferenceField": {
1102 Type: "string",
1103 ReadOnly: true,
1104 Extension: map[string]interface{}{
1105 "x-dcl-references": []interface{}{
1106 map[interface{}]interface{}{
1107 "resource": "FakeService/FakeKind",
1108 "field": "name",
1109 },
1110 },
1111 },
1112 },
1113 },
1114 Required: []string{"nestedField1"},
1115 },
1116 },
1117 "bar": {
1118 Type: "integer",
1119 },
1120 },
1121 Required: []string{"foo"},
1122 },
1123 specSchema: &apiextensions.JSONSchemaProps{
1124 Type: "object",
1125 Properties: map[string]apiextensions.JSONSchemaProps{
1126 "foo": {
1127 Type: "array",
1128 Items: &apiextensions.JSONSchemaPropsOrArray{
1129 Schema: &apiextensions.JSONSchemaProps{
1130 Type: "object",
1131 Properties: map[string]apiextensions.JSONSchemaProps{
1132 "nestedField1": {
1133 Type: "boolean",
1134 },
1135 "nestedField2": {
1136 Type: "string",
1137 },
1138 "nestedObject": {
1139 Type: "object",
1140 Properties: map[string]apiextensions.JSONSchemaProps{
1141 "name": {
1142 Type: "string",
1143 },
1144 "state": {
1145 Type: "string",
1146 },
1147 },
1148 },
1149 "readOnlySensitiveField": {
1150 Type: "string",
1151 },
1152 "readOnlyReferenceField": {
1153 Type: "string",
1154 },
1155 },
1156 Required: []string{"nestedField1"},
1157 },
1158 },
1159 },
1160 "bar": {
1161 Type: "integer",
1162 },
1163 },
1164 Required: []string{"foo"},
1165 },
1166 statusSchema: nil,
1167 },
1168 {
1169 name: "empty spec",
1170 dclSchema: &openapi.Schema{
1171 Type: "object",
1172 },
1173 specSchema: &apiextensions.JSONSchemaProps{
1174 Type: "object",
1175 },
1176 statusSchema: nil,
1177 },
1178 {
1179
1180
1181 name: "field specified as x-dcl-labels",
1182 dclSchema: &openapi.Schema{
1183 Type: "object",
1184 Extension: map[string]interface{}{
1185 "x-dcl-labels": "labels",
1186 },
1187 Properties: map[string]*openapi.Schema{
1188 "labels": {
1189 Type: "object",
1190 AdditionalProperties: &openapi.Schema{
1191 Type: "string",
1192 },
1193 },
1194 },
1195 },
1196 specSchema: &apiextensions.JSONSchemaProps{
1197 Type: "object",
1198 },
1199 statusSchema: nil,
1200 },
1201 {
1202
1203
1204
1205 name: "labels field exists, but not specified as x-dcl-labels",
1206 dclSchema: &openapi.Schema{
1207 Type: "object",
1208 Properties: map[string]*openapi.Schema{
1209 "labels": {
1210 Type: "object",
1211 AdditionalProperties: &openapi.Schema{
1212 Type: "string",
1213 },
1214 },
1215 },
1216 },
1217 specSchema: &apiextensions.JSONSchemaProps{
1218 Type: "object",
1219 Properties: map[string]apiextensions.JSONSchemaProps{
1220 "labels": {
1221 Type: "object",
1222 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
1223 Schema: &apiextensions.JSONSchemaProps{
1224 Type: "string",
1225 },
1226 },
1227 },
1228 },
1229 },
1230 statusSchema: nil,
1231 },
1232 }
1233
1234 smLoader := servicemappingloader.NewFromServiceMappings(test.FakeServiceMappingsWithHierarchicalResources())
1235 serviceMetadataLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources())
1236 dclSchemaLoader := testdclschemaloader.New(dclSchemaMap())
1237 allSupportedGVKs := supportedgvks.All(smLoader, serviceMetadataLoader)
1238 a := New(serviceMetadataLoader, dclSchemaLoader, allSupportedGVKs)
1239 for _, tc := range tests {
1240 tc := tc
1241 t.Run(tc.name, func(t *testing.T) {
1242 t.Parallel()
1243 spec, err := a.generateSpecJSONSchema(tc.dclSchema, tc.resource)
1244 if tc.hasErrorOnSpecGeneration {
1245 if err == nil {
1246 t.Fatalf("got nil, but expect to get error on generating the spec")
1247 }
1248 return
1249 }
1250 if err != nil {
1251 t.Fatalf("error generating spec schema: %v", err)
1252 }
1253 if !test.Equals(t, tc.specSchema, spec) {
1254 t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(tc.specSchema, spec))
1255 }
1256 status, err := generateStatusJSONSchema(tc.dclSchema)
1257 if err != nil {
1258 t.Fatalf("error generating status schema: %v", err)
1259 }
1260 if !test.Equals(t, tc.statusSchema, status) {
1261 t.Fatalf("unexpected status diff (-want +got): \n%v", cmp.Diff(tc.statusSchema, status))
1262 }
1263 })
1264 }
1265 }
1266
1267 func dclSchemaMap() map[string]*openapi.Schema {
1268 return map[string]*openapi.Schema{
1269 "test1_beta_foo": &openapi.Schema{
1270 Extension: map[string]interface{}{
1271 "x-dcl-id": "projects/{{project}}/foo/{{name}}",
1272 },
1273 },
1274 "test1_beta_bar": &openapi.Schema{
1275 Extension: map[string]interface{}{
1276 "x-dcl-id": "projects/{{project}}/bar/{{name}}",
1277 },
1278 },
1279
1280
1281
1282
1283 "cloudresourcemanager_ga_project": &openapi.Schema{
1284 Extension: map[string]interface{}{
1285 "x-dcl-id": "projects/{{name}}",
1286 },
1287 },
1288 "cloudresourcemanager_ga_folder": &openapi.Schema{
1289 Extension: map[string]interface{}{
1290 "x-dcl-id": "folders/{{name}}",
1291 },
1292 },
1293 }
1294 }
1295
1296 func resourceRefBoilerplateWithDescription(description, externalRefDescription string) *apiextensions.JSONSchemaProps {
1297 schema := crdboilerplate.GetResourceReferenceSchemaBoilerplate(externalRefDescription)
1298 schema.Description = description
1299 return schema
1300 }
1301
1302 func markReferencedKindsNotSupported(schema *apiextensions.JSONSchemaProps, kinds []string) *apiextensions.JSONSchemaProps {
1303 s := schema.DeepCopy()
1304 MarkReferencedKindsNotSupported(s, kinds)
1305 return s
1306 }
1307
View as plain text