1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package dcl
16
17 import (
18 "fmt"
19 "strings"
20
21 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
22 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/schema/dclschemaloader"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/pathslice"
28
29 "github.com/nasa9084/go-openapi"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 )
32
33 var (
34
35
36
37 multiTypeParentReferenceField = "parent"
38 singleTypeParentReferenceFieldProject = "project"
39 singleTypeParentReferenceFieldFolder = "folder"
40 singleTypeParentReferenceFieldOrganization = "organization"
41 singleTypeParentReferenceFields = map[string]bool{
42 singleTypeParentReferenceFieldProject: true,
43 singleTypeParentReferenceFieldFolder: true,
44 singleTypeParentReferenceFieldOrganization: true,
45 }
46
47
48
49 hierarchicalReferenceProject = corekccv1alpha1.HierarchicalReference{
50 Key: "projectRef",
51 Type: corekccv1alpha1.HierarchicalReferenceTypeProject,
52 }
53 hierarchicalReferenceFolder = corekccv1alpha1.HierarchicalReference{
54 Key: "folderRef",
55 Type: corekccv1alpha1.HierarchicalReferenceTypeFolder,
56 }
57 hierarchicalReferenceOrganization = corekccv1alpha1.HierarchicalReference{
58 Key: "organizationRef",
59 Type: corekccv1alpha1.HierarchicalReferenceTypeOrganization,
60 }
61 hierarchicalReferenceBillingAccount = corekccv1alpha1.HierarchicalReference{
62 Key: "billingAccountRef",
63 Type: corekccv1alpha1.HierarchicalReferenceTypeBillingAccount,
64 }
65 )
66
67
80 type dclReferenceExtensionElem struct {
81
82 Resource string `json:"resource"`
83
84 Field string `json:"field"`
85
86
87
88 Parent bool `json:"parent,omitempty"`
89 }
90
91 func GetReferenceTypeConfigs(schema *openapi.Schema, smLoader dclmetadata.ServiceMetadataLoader) ([]corekccv1alpha1.TypeConfig, error) {
92 refExtensionElems, err := getDCLReferenceExtensionElems(schema)
93 if err != nil {
94 return nil, err
95 }
96 res := make([]corekccv1alpha1.TypeConfig, 0)
97 for _, e := range refExtensionElems {
98 tc, err := toTypeConfig(e, smLoader)
99 if err != nil {
100 return nil, err
101 }
102 res = append(res, *tc)
103 }
104 return res, nil
105 }
106
107
108 func ToTypeConfig(rawElem map[interface{}]interface{}, smLoader dclmetadata.ServiceMetadataLoader) (*corekccv1alpha1.TypeConfig, error) {
109 e, err := toDCLReferenceExtensionElem(rawElem)
110 if err != nil {
111 return nil, err
112 }
113 return toTypeConfig(e, smLoader)
114 }
115
116 func toTypeConfig(e dclReferenceExtensionElem, smLoader dclmetadata.ServiceMetadataLoader) (*corekccv1alpha1.TypeConfig, error) {
117 if err := validateDCLReferenceExtensionElem(e); err != nil {
118 return nil, err
119 }
120 tc := &corekccv1alpha1.TypeConfig{}
121 tc.TargetField = e.Field
122 tc.Parent = e.Parent
123 refGVK, err := getReferenceGVK(e.Resource, smLoader)
124 if err != nil {
125 return nil, fmt.Errorf("error resolving the GVK for referenced resource: %w", err)
126 }
127 tc.GVK = refGVK
128 tc.Key = text.LowercaseInitial(k8s.KindWithoutServicePrefix(tc.GVK)) + "Ref"
129 return tc, nil
130 }
131
132 func validateDCLReferenceExtensionElem(e dclReferenceExtensionElem) error {
133 if e.Resource == "" {
134 return fmt.Errorf("required 'resource' attribute is not specified in 'x-dcl-references' extension")
135 }
136 if e.Field == "" {
137 return fmt.Errorf("required 'field' attribute is not specified in 'x-dcl-references' extension")
138 }
139 return nil
140 }
141
142 func convertToStringInterfaceMap(in map[interface{}]interface{}) (map[string]interface{}, error) {
143 out := make(map[string]interface{})
144 for k, v := range in {
145 s, ok := k.(string)
146 if !ok {
147 return nil, fmt.Errorf("wrong type for the key: %T, expect to have string", k)
148 }
149 out[s] = v
150 }
151 return out, nil
152 }
153
154 func getReferenceGVK(resource string, smLoader dclmetadata.ServiceMetadataLoader) (schema.GroupVersionKind, error) {
155
156
157 components := strings.Split(resource, "/")
158 if len(components) != 2 {
159 return schema.GroupVersionKind{}, fmt.Errorf("invalid format for 'resource' attribute in 'x-dcl-references' extension: %v", resource)
160 }
161 service := components[0]
162 dclType := components[1]
163
164 sm, found := smLoader.GetServiceMetadata(service)
165 if !found {
166 return schema.GroupVersionKind{}, fmt.Errorf("ServiceMetadata for service %v is not found", service)
167 }
168 r, found := sm.GetResourceWithType(dclType)
169 if !found {
170 return schema.GroupVersionKind{}, fmt.Errorf("resource with DCL type %v not supported in service %v", dclType, service)
171 }
172 return dclmetadata.GVKForResource(sm, r), nil
173 }
174
175 func getDCLReferenceExtensionElems(schema *openapi.Schema) ([]dclReferenceExtensionElem, error) {
176 extension, ok := schema.Extension["x-dcl-references"]
177 if !ok {
178 return nil, fmt.Errorf("no 'x-dcl-references' extension found")
179 }
180 extensionAsList, ok := extension.([]interface{})
181 if !ok {
182 return nil, fmt.Errorf("wrong type for 'x-dcl-references' extension: %T, expect to have []interface{}", extension)
183 }
184 res := make([]dclReferenceExtensionElem, 0)
185 for _, elem := range extensionAsList {
186 elemAsMap, ok := elem.(map[interface{}]interface{})
187 if !ok {
188 return nil, fmt.Errorf("wrong type for element in 'x-dcl-references' extension %T, expect to have map[interface{}]interface{}", elem)
189 }
190 e, err := toDCLReferenceExtensionElem(elemAsMap)
191 if err != nil {
192 return nil, err
193 }
194 res = append(res, e)
195 }
196 return res, nil
197 }
198
199 func toDCLReferenceExtensionElem(rawElem map[interface{}]interface{}) (dclReferenceExtensionElem, error) {
200 m, err := convertToStringInterfaceMap(rawElem)
201 if err != nil {
202 return dclReferenceExtensionElem{}, fmt.Errorf("error converting 'x-dcl-references' element to map[string]interface{}: %v", err)
203 }
204 e := dclReferenceExtensionElem{}
205 if err := util.Marshal(m, &e); err != nil {
206 return dclReferenceExtensionElem{}, fmt.Errorf("error marshalling 'x-dcl-references' element to struct: %v", err)
207 }
208 return e, nil
209 }
210
211 func GetHierarchicalReferencesForGVK(gvk schema.GroupVersionKind, smLoader dclmetadata.ServiceMetadataLoader, schemaLoader dclschemaloader.DCLSchemaLoader) ([]corekccv1alpha1.HierarchicalReference, error) {
212 r, found := smLoader.GetResourceWithGVK(gvk)
213 if !found {
214 return nil, fmt.Errorf("ServiceMetadata for resource with GroupVersionKind %v not found", gvk)
215 }
216
217
218 if !r.SupportsHierarchicalReferences {
219 return nil, nil
220 }
221 stv, err := dclmetadata.ToServiceTypeVersion(gvk, smLoader)
222 if err != nil {
223 return nil, fmt.Errorf("error getting DCL ServiceTypeVersion for GroupVersionKind %v: %w", gvk, err)
224 }
225 dclSchema, err := schemaLoader.GetDCLSchema(stv)
226 if err != nil {
227 return nil, fmt.Errorf("error getting the DCL Schema for GroupVersionKind %v: %w", gvk, err)
228 }
229 hierarchicalRefs, err := GetHierarchicalReferenceConfigFromDCLSchema(dclSchema, smLoader)
230 if err != nil {
231 return nil, fmt.Errorf("error resolving the hierarchical reference config from DCL schema for GroupVersionKind %v: %w", gvk, err)
232 }
233 return hierarchicalRefs, nil
234 }
235
236 func GetHierarchicalReferenceConfigFromDCLSchema(schema *openapi.Schema, smLoader dclmetadata.ServiceMetadataLoader) ([]corekccv1alpha1.HierarchicalReference, error) {
237
238 if SupportsMultipleParentTypes(schema) {
239 res, err := GetHierarchicalReferenceConfigForMultiParentResource(schema, smLoader)
240 if err != nil {
241 return nil, fmt.Errorf("error getting hierarchical reference config for resource that supports multiple parent types: %w", err)
242 }
243 return res, nil
244 }
245
246
247 field, err := getSingleTypeParentReferenceField(schema)
248 if err != nil {
249 return nil, fmt.Errorf("error getting single-type parent reference field for resource: %w", err)
250 }
251 if field == "" {
252
253 return nil, nil
254 }
255 switch field {
256 case singleTypeParentReferenceFieldProject:
257 return []corekccv1alpha1.HierarchicalReference{hierarchicalReferenceProject}, nil
258 case singleTypeParentReferenceFieldFolder:
259 return []corekccv1alpha1.HierarchicalReference{hierarchicalReferenceFolder}, nil
260 case singleTypeParentReferenceFieldOrganization:
261 return []corekccv1alpha1.HierarchicalReference{hierarchicalReferenceOrganization}, nil
262 default:
263 panic(fmt.Errorf("unrecognized single-type parent reference field: %v", field))
264 }
265 }
266
267 func GetHierarchicalReferenceConfigForMultiParentResource(schema *openapi.Schema, smLoader dclmetadata.ServiceMetadataLoader) ([]corekccv1alpha1.HierarchicalReference, error) {
268 if !SupportsMultipleParentTypes(schema) {
269 return nil, fmt.Errorf("resource does not support multiple parent types")
270 }
271
272 parentFieldSchema := schema.Properties["parent"]
273 tcs, err := GetReferenceTypeConfigs(parentFieldSchema, smLoader)
274 if err != nil {
275 return nil, fmt.Errorf("error getting reference type configs for DCL field 'parent': %w", err)
276 }
277
278 res := make([]corekccv1alpha1.HierarchicalReference, 0)
279 for _, tc := range tcs {
280 switch tc.GVK.Kind {
281 case "Project":
282 res = append(res, hierarchicalReferenceProject)
283 case "Folder":
284 res = append(res, hierarchicalReferenceFolder)
285 case "Organization":
286 res = append(res, hierarchicalReferenceOrganization)
287 case "BillingAccount":
288 res = append(res, hierarchicalReferenceBillingAccount)
289 default:
290 panic(fmt.Errorf("'parent' field references an unsupported resource kind: %v", tc.GVK.Kind))
291 }
292 }
293 return res, nil
294 }
295
296 func getSingleTypeParentReferenceField(schema *openapi.Schema) (string, error) {
297 if SupportsMultipleParentTypes(schema) {
298 return "", fmt.Errorf("resource supports multiple parent types, not one")
299 }
300 var res string
301 for f := range singleTypeParentReferenceFields {
302 _, ok := schema.Properties[f]
303 if !ok {
304 continue
305 }
306 if res != "" {
307 return "", fmt.Errorf("resource unexpectedly has more than one single-type parent reference field")
308 }
309 res = f
310 }
311 return res, nil
312 }
313
314 func ParentReferenceFields() []string {
315 return []string{
316 singleTypeParentReferenceFieldProject,
317 singleTypeParentReferenceFieldFolder,
318 singleTypeParentReferenceFieldOrganization,
319 multiTypeParentReferenceField,
320 }
321 }
322
323 func IsParentReferenceField(path []string) bool {
324 return IsSingleTypeParentReferenceField(path) ||
325 IsMultiTypeParentReferenceField(path)
326 }
327
328 func IsSingleTypeParentReferenceField(path []string) bool {
329 if len(path) > 1 {
330 return false
331 }
332 field := pathslice.Base(path)
333 _, ok := singleTypeParentReferenceFields[field]
334 return ok
335 }
336
337 func IsMultiTypeParentReferenceField(path []string) bool {
338 if len(path) > 1 {
339 return false
340 }
341 field := pathslice.Base(path)
342 return field == multiTypeParentReferenceField
343 }
344
345 func SupportsMultipleParentTypes(schema *openapi.Schema) bool {
346 _, ok := schema.Properties[multiTypeParentReferenceField]
347 return ok
348 }
349
350
351
352
353
354
355 func GetHierarchicalRefFromConfigForMultiParentResource(config map[string]interface{}, schema *openapi.Schema,
356 smLoader dclmetadata.ServiceMetadataLoader) (interface{}, *corekccv1alpha1.TypeConfig, error) {
357 tcs, err := GetReferenceTypeConfigs(schema, smLoader)
358 if err != nil {
359 return nil, nil, fmt.Errorf("error getting reference type configs: %w", err)
360 }
361 var rawVal interface{}
362 var typeConfig corekccv1alpha1.TypeConfig
363 for _, tc := range tcs {
364 if v, ok := config[tc.Key]; ok && v != nil {
365 if rawVal != nil {
366 return nil, nil, fmt.Errorf("multiple hierarchical references found in config: %v and %v", typeConfig.Key, tc.Key)
367 }
368 rawVal = v
369 typeConfig = tc
370 }
371 }
372 if rawVal == nil {
373 return nil, nil, nil
374 }
375 return rawVal, &typeConfig, nil
376 }
377
378
379
380
381
382 func ParentPrefixForKind(kind string) string {
383 switch kind {
384 case "Project", "Folder", "Organization", "BillingAccount":
385 return fmt.Sprintf("%vs/", text.LowercaseInitial(kind))
386 default:
387 panic(fmt.Errorf("tried to get parent prefix for kind %v which is not recognized as a hierarchical resource", kind))
388 }
389 }
390
View as plain text