1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package k8s
16
17 import (
18 "bytes"
19 "fmt"
20
21 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/deepcopy"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
24
25 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
26 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
29 "sigs.k8s.io/structured-merge-diff/v4/schema"
30 "sigs.k8s.io/structured-merge-diff/v4/typed"
31 "sigs.k8s.io/structured-merge-diff/v4/value"
32 )
33
34
89
90 func IsK8sManaged(key string, specObj map[string]interface{}, managedFields *fieldpath.Set) bool {
91 pe := fieldpath.PathElement{FieldName: &key}
92 if managedFields == nil {
93
94
95 _, ok := specObj[key]
96 return ok
97 }
98 if managedFields.Members.Has(pe) {
99 return true
100 }
101
102
103
104
105
106 _, found := managedFields.Children.Get(pe)
107 return found
108 }
109
110
111
112 func ConstructManagedFieldsV1Set(managedFields []v1.ManagedFieldsEntry) (*fieldpath.Set, error) {
113 res := fieldpath.NewSet()
114 for _, managedFieldEntry := range managedFields {
115 if managedFieldEntry.Manager == ControllerManagedFieldManager {
116 continue
117 }
118 if managedFieldEntry.FieldsType != ManagedFieldsTypeFieldsV1 {
119 return nil, fmt.Errorf(
120 "expected managed field entry for manager '%v' and operation '%v' of type '%v', got type '%v'",
121 managedFieldEntry.Manager, managedFieldEntry.Operation, ManagedFieldsTypeFieldsV1,
122 managedFieldEntry.FieldsType)
123 }
124 fieldsV1 := managedFieldEntry.FieldsV1
125 if managedFieldEntry.FieldsV1 == nil {
126 return nil, fmt.Errorf("managed field entry for manager '%v' and operation '%v' has empty fieldsV1",
127 managedFieldEntry.Manager, managedFieldEntry.Operation)
128 }
129 entrySet := fieldpath.NewSet()
130 if err := entrySet.FromJSON(bytes.NewReader(fieldsV1.Raw)); err != nil {
131 return nil, fmt.Errorf("error marshaling managed fields for manager '%v' and operation '%v' from JSON: %w",
132 managedFieldEntry.Manager, managedFieldEntry.Operation, err)
133 }
134 specFieldName := "spec"
135 specSet, found := entrySet.Children.Get(fieldpath.PathElement{FieldName: &specFieldName})
136 if !found {
137 continue
138 }
139 res = res.Union(specSet)
140 }
141 return res, nil
142 }
143
144 func containsUnsupportedFieldTypes(managedFields []v1.ManagedFieldsEntry) bool {
145 for _, entry := range managedFields {
146
147 if entry.FieldsType != ManagedFieldsTypeFieldsV1 {
148 return true
149 }
150 }
151 return false
152 }
153
154 func GetK8sManagedFields(u *unstructured.Unstructured) (*fieldpath.Set, error) {
155 managedFields := u.GetManagedFields()
156 if managedFields != nil && !containsUnsupportedFieldTypes(managedFields) {
157 res, err := ConstructManagedFieldsV1Set(managedFields)
158 if err != nil {
159 return nil, err
160 }
161 return res, nil
162 }
163 return nil, nil
164 }
165
166
167
168
169
170 func OverlayManagedFieldsOntoState(spec, stateAsKRM map[string]interface{}, managedFields *fieldpath.Set,
171 jsonSchema *apiextensions.JSONSchemaProps, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (map[string]interface{}, error) {
172 config, err := overlayManagedFieldsOntoState(spec, stateAsKRM, managedFields, jsonSchema)
173 if err != nil {
174 return nil, err
175 }
176
177
178
179
180
181
182
183
184
185
186
187
188
189 if len(hierarchicalRefs) > 0 {
190 config, err = addHierarchicalReferenceToConfig(config, spec, hierarchicalRefs)
191 if err != nil {
192 return nil, fmt.Errorf("error adding hierarchical reference to config: %v", err)
193 }
194 }
195
196 return config, nil
197 }
198
199 func overlayManagedFieldsOntoState(spec, stateAsKRM map[string]interface{}, managedFields *fieldpath.Set,
200 jsonSchema *apiextensions.JSONSchemaProps) (map[string]interface{}, error) {
201 if jsonSchema == nil {
202 return nil, fmt.Errorf("JSON schema is required")
203 }
204 specSchema, ok := jsonSchema.Properties["spec"]
205 if !ok {
206 if spec != nil && len(spec) > 0 {
207 return nil, fmt.Errorf("cannot parse spec with no spec schema available")
208 }
209 return make(map[string]interface{}), nil
210 }
211
212
213
214
215
216 smdSchema := jsonSchemaToSMDSchema(&specSchema)
217 specAsTyped, err := toTypedValue(spec, smdSchema)
218 if err != nil {
219 return nil, fmt.Errorf("error converting spec to typed value: %w", err)
220 }
221 stateAsTyped, err := toTypedValue(stateAsKRM, smdSchema)
222 if err != nil {
223 return nil, fmt.Errorf("error converting state to typed value: %w", err)
224 }
225
226 if managedFields == nil {
227 managedFields = &fieldpath.Set{}
228 }
229
230
231
232
233
234 managedFields = managedFields.Leaves()
235
236
237
238
239
240 atomicListFields, err := getAtomicListFields(smdSchema)
241 if err != nil {
242 return nil, fmt.Errorf("error getting atomic list fields: %w", err)
243 }
244 specFieldSet, err := specAsTyped.ToFieldSet()
245 if err != nil {
246 return nil, fmt.Errorf("error constructing field set for spec: %w", err)
247 }
248 atomicListFields = atomicListFields.Intersection(specFieldSet)
249 managedFields = managedFields.Union(atomicListFields)
250
251
252
253
254 k8sManagedPartialState := specAsTyped.ExtractItems(managedFields)
255 overlaidState, err := stateAsTyped.Merge(k8sManagedPartialState)
256 if err != nil {
257 return nil, fmt.Errorf("error merging partial managed state with live state: %w", err)
258 }
259
260
261
262 overlaidStateRaw := overlaidState.AsValue().Unstructured()
263 if overlaidStateRaw == nil {
264 return make(map[string]interface{}), nil
265 }
266 res, ok := overlaidStateRaw.(map[string]interface{})
267 if !ok {
268 return nil, fmt.Errorf("overlaid state unstructured not of type map[string]interface{}")
269 }
270 return res, nil
271 }
272
273
274
275
276
277
278
279
280 func ConstructTrimmedSpecWithManagedFields(resource *Resource, jsonSchema *apiextensions.JSONSchemaProps,
281 hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (map[string]interface{}, error) {
282 if resource.ManagedFields == nil {
283
284
285 return resource.Spec, nil
286 }
287 if resource.Spec == nil {
288 return nil, nil
289 }
290
291 emptyState := make(map[string]interface{})
292 trimmed, err := OverlayManagedFieldsOntoState(resource.Spec, emptyState,
293 resource.ManagedFields, jsonSchema, hierarchicalRefs)
294 if err != nil {
295 return nil, fmt.Errorf("error constructing trimmed state with managed fields: %w", err)
296 }
297 return trimmed, nil
298 }
299
300
301
302 func getAtomicListFields(s *schema.Schema) (*fieldpath.Set, error) {
303 if len(s.Types) != 1 {
304 return nil, fmt.Errorf("expected schema to have 1 type, got %v", len(s.Types))
305 }
306 typeDef := s.Types[0]
307 if typeDef.Map == nil {
308 return nil, fmt.Errorf("type definition is not map")
309 }
310 return getAtomicListFieldsFromMap(typeDef.Map), nil
311 }
312
313 func getAtomicListFieldsFromMap(m *schema.Map) *fieldpath.Set {
314 res := &fieldpath.Set{}
315 for _, structField := range m.Fields {
316 fieldName := structField.Name
317 pe := fieldpath.PathElement{FieldName: &fieldName}
318 fieldAtom := structField.Type.Inlined
319 if fieldAtom.List != nil {
320 switch fieldAtom.List.ElementRelationship {
321 case schema.Atomic:
322 res.Members.Insert(pe)
323 case schema.Associative:
324 elemAtom := fieldAtom.List.ElementType.Inlined
325 if elemAtom.Map != nil {
326 nestedSet := getAtomicListFieldsFromMap(elemAtom.Map)
327 if !nestedSet.Empty() {
328 insertChild(res, nestedSet, pe)
329 }
330 }
331 }
332 } else if fieldAtom.Map != nil {
333 nestedSet := getAtomicListFieldsFromMap(fieldAtom.Map)
334 if !nestedSet.Empty() {
335 insertChild(res, nestedSet, pe)
336 }
337 }
338 }
339 return res
340 }
341
342 func insertChild(set, childSet *fieldpath.Set, pe fieldpath.PathElement) {
343 childSetPtr := set.Children.Descend(pe)
344 *childSetPtr = *childSet
345 }
346
347 func toTypedValue(obj map[string]interface{}, smdSchema *schema.Schema) (*typed.TypedValue, error) {
348
349
350
351 name := ""
352 return typed.AsTyped(value.NewValueInterface(obj),
353 smdSchema,
354 schema.TypeRef{
355 NamedType: &name,
356 },
357 )
358 }
359
360
361
362
363
364
365
366
367
368
369
370
371
372 func jsonSchemaToSMDSchema(jsonSchema *apiextensions.JSONSchemaProps) *schema.Schema {
373 return &schema.Schema{
374 Types: []schema.TypeDef{
375 {
376
377
378 Name: "",
379 Atom: jsonSchemaToAtom(jsonSchema),
380 },
381 }}
382 }
383
384 func jsonSchemaToAtom(jsonSchema *apiextensions.JSONSchemaProps) schema.Atom {
385 res := schema.Atom{}
386 var (
387
388
389
390 scalarPtr = func(s schema.Scalar) *schema.Scalar { return &s }
391
392
393
394
395
396 scalarUnknown = schema.Scalar("unknown")
397 )
398 switch jsonSchema.Type {
399 case "object":
400 res.Map = &schema.Map{}
401 if jsonSchema.Properties != nil {
402 for field, fieldSchema := range jsonSchema.Properties {
403 res.Map.Fields = append(res.Map.Fields, schema.StructField{
404 Name: field,
405 Type: schema.TypeRef{
406 Inlined: jsonSchemaToAtom(&fieldSchema),
407 },
408 })
409 }
410 break
411 }
412 if jsonSchema.AdditionalProperties != nil {
413 res.Map.ElementType = schema.TypeRef{
414 Inlined: jsonSchemaToAtom(jsonSchema.AdditionalProperties.Schema),
415 }
416 break
417 }
418 res.Map.ElementType = schema.TypeRef{
419 Inlined: schema.Atom{Scalar: &scalarUnknown},
420 }
421 case "array":
422 res.List = &schema.List{
423 ElementType: schema.TypeRef{
424 Inlined: jsonSchemaToAtom(jsonSchema.Items.Schema),
425 },
426 ElementRelationship: schema.Atomic,
427 }
428 case "integer", "number":
429 res.Scalar = scalarPtr(schema.Numeric)
430 case "string":
431 res.Scalar = scalarPtr(schema.String)
432 case "boolean":
433 res.Scalar = scalarPtr(schema.Boolean)
434 default:
435 panic(fmt.Sprintf("unknown JSON schema type %v", jsonSchema.Type))
436 }
437 return res
438 }
439
440 func addHierarchicalReferenceToConfig(config, spec map[string]interface{}, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (map[string]interface{}, error) {
441 modifiedConfig := deepcopy.MapStringInterface(config)
442 resourceRef, hierarchicalRef, err := GetHierarchicalReferenceFromSpec(spec, hierarchicalRefs)
443 if err != nil {
444 return nil, fmt.Errorf("error getting hierarchical reference: %v", err)
445 }
446 if resourceRef == nil {
447 return modifiedConfig, nil
448 }
449 var resourceRefRaw map[string]interface{}
450 if err := util.Marshal(resourceRef, &resourceRefRaw); err != nil {
451 return nil, fmt.Errorf("error marshalling hierarchical reference to map[string]interface{}: %v", err)
452 }
453 if err := unstructured.SetNestedField(modifiedConfig, resourceRefRaw, hierarchicalRef.Key); err != nil {
454 return nil, fmt.Errorf("error setting hierarchical reference in config: %v", err)
455 }
456 return modifiedConfig, nil
457 }
458
View as plain text