1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package crdgeneration
16
17 import (
18 "fmt"
19 "strings"
20
21 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdgeneration/crdboilerplate"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/slice"
26
27 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 )
30
31 const (
32 ApiDomain = "cnrm.cloud.google.com"
33 ManagedByKCCLabel = "cnrm.cloud.google.com/managed-by-kcc"
34 GCPCategory = "gcp"
35 )
36
37
38
39
40 func FileNameForCRD(crd *apiextensions.CustomResourceDefinition) (string, error) {
41 group, err := getGroup(crd)
42 if err != nil {
43 return "", err
44 }
45 version := k8s.GetVersionFromCRD(crd)
46 kind := getKind(crd)
47 fileName := strings.Join([]string{group, version, kind}, "_") + ".yaml"
48 return fileName, nil
49 }
50
51 func getGroup(crd *apiextensions.CustomResourceDefinition) (string, error) {
52 groupSplit := strings.SplitN(crd.Spec.Group, ".", 2)
53 if len(groupSplit) != 2 {
54 return "", fmt.Errorf("unable to parse group %v", crd.Spec.Group)
55 }
56 return groupSplit[0], nil
57 }
58
59 func getKind(crd *apiextensions.CustomResourceDefinition) string {
60 return strings.ToLower(crd.Spec.Names.Kind)
61 }
62
63 func GenerateShortNames(kind string) []string {
64 return []string{
65 formatGCPShortName(kind),
66 formatGCPShortName(text.Pluralize(kind)),
67 }
68 }
69
70 func formatGCPShortName(kind string) string {
71 return fmt.Sprintf("gcp%v", strings.ToLower(kind))
72 }
73
74 func GetCustomResourceDefinition(kind, group, version string, openAPIV3Schema *apiextensions.JSONSchemaProps, engineLabel string) *apiextensions.CustomResourceDefinition {
75 singular := strings.ToLower(kind)
76 plural := text.Pluralize(singular)
77 fullName := plural + "." + group
78 crdVersion := apiextensions.CustomResourceDefinitionVersion{
79 Schema: &apiextensions.CustomResourceValidation{
80 OpenAPIV3Schema: openAPIV3Schema,
81 },
82 AdditionalPrinterColumns: crdboilerplate.GetAdditionalPrinterColumns(),
83 Subresources: &apiextensions.CustomResourceSubresources{
84 Status: &apiextensions.CustomResourceSubresourceStatus{},
85 },
86 Name: version,
87 Served: true,
88 Storage: true,
89 }
90 return &apiextensions.CustomResourceDefinition{
91 TypeMeta: metav1.TypeMeta{
92 APIVersion: "apiextensions.k8s.io/v1",
93 Kind: "CustomResourceDefinition",
94 },
95 ObjectMeta: metav1.ObjectMeta{
96 Name: fullName,
97 Annotations: map[string]string{
98 k8s.KCCVersionLabel: "0.0.0-dev",
99 },
100 Labels: map[string]string{
101 ManagedByKCCLabel: "true",
102 engineLabel: "true",
103 k8s.KCCSystemLabel: "true",
104 },
105 },
106 Spec: apiextensions.CustomResourceDefinitionSpec{
107 Group: group,
108 Versions: []apiextensions.CustomResourceDefinitionVersion{
109 crdVersion,
110 },
111 Scope: apiextensions.NamespaceScoped,
112 Names: apiextensions.CustomResourceDefinitionNames{
113 Singular: singular,
114 Plural: plural,
115 Kind: kind,
116 Categories: []string{GCPCategory},
117 ShortNames: GenerateShortNames(kind),
118 },
119 },
120 Status: apiextensions.CustomResourceDefinitionStatus{
121 Conditions: []apiextensions.CustomResourceDefinitionCondition{},
122 StoredVersions: []string{},
123 },
124 }
125 }
126
127 func GenerateResourceIDFieldDescription(targetField string, isServerGeneratedResourceID bool) string {
128 if isServerGeneratedResourceID {
129 return fmt.Sprintf("Immutable. Optional. The service-generated "+
130 "%s of the resource. Used for acquisition only. Leave unset to "+
131 "create a new resource.", targetField)
132 }
133
134 return fmt.Sprintf("Immutable. Optional. The %s of the resource. "+
135 "Used for creation and acquisition. When unset, the value of "+
136 "`metadata.name` is used as the default.", targetField)
137 }
138
139
140
141 func MarkReferencedKindsNotSupported(refSchema *apiextensions.JSONSchemaProps, kinds []string) {
142 prop := refSchema.Properties["name"]
143 prop.Description = fmt.Sprintf("[WARNING] %v not yet supported in Config Connector, use 'external' field to reference existing resources.\n%v", strings.Join(kinds, ","), prop.Description)
144 refSchema.Properties["name"] = prop
145 }
146
147
148
149
150 func MarkHierarchicalReferencesOptionalButMutuallyExclusive(spec *apiextensions.JSONSchemaProps, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) *apiextensions.JSONSchemaProps {
151 specCopy := spec.DeepCopy()
152 if !resourceSupportsHierarchicalRefs(hierarchicalRefs) {
153 return specCopy
154 }
155
156
157
158 for _, h := range hierarchicalRefs {
159 specCopy.Required = slice.RemoveStringFromStringSlice(specCopy.Required, h.Key)
160 }
161
162
163
164 if len(hierarchicalRefs) == 1 {
165 return specCopy
166 }
167
168
169 for _, h := range hierarchicalRefs {
170 specCopy.OneOf = append(specCopy.OneOf, apiextensions.JSONSchemaProps{
171 Required: []string{h.Key},
172 })
173 }
174 canSpecifyNoRefRule := apiextensions.JSONSchemaProps{
175 Not: &apiextensions.JSONSchemaProps{},
176 }
177 for _, h := range hierarchicalRefs {
178 canSpecifyNoRefRule.Not.AnyOf = append(canSpecifyNoRefRule.Not.AnyOf, apiextensions.JSONSchemaProps{
179 Required: []string{h.Key},
180 })
181 }
182 specCopy.OneOf = append(specCopy.OneOf, canSpecifyNoRefRule)
183 return specCopy
184 }
185
186
187
188
189
190 func MarkHierarchicalReferencesRequiredButMutuallyExclusive(spec *apiextensions.JSONSchemaProps, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) *apiextensions.JSONSchemaProps {
191 specCopy := spec.DeepCopy()
192 if !resourceSupportsHierarchicalRefs(hierarchicalRefs) {
193 return specCopy
194 }
195
196
197
198 if len(hierarchicalRefs) == 1 {
199 specCopy.Required = slice.IncludeString(specCopy.Required, hierarchicalRefs[0].Key)
200 return specCopy
201 }
202
203 for _, h := range hierarchicalRefs {
204 specCopy.OneOf = append(specCopy.OneOf, apiextensions.JSONSchemaProps{
205 Required: []string{h.Key},
206 })
207
208
209
210 specCopy.Required = slice.RemoveStringFromStringSlice(specCopy.Required, h.Key)
211 }
212 return specCopy
213 }
214
215 func resourceSupportsHierarchicalRefs(hierarchicalRefs []corekccv1alpha1.HierarchicalReference) bool {
216 return len(hierarchicalRefs) > 0
217 }
218
View as plain text