in pkg/webhook/immutable_fields_validator.go [185:287]
func getChangesOnImmutableFields(spec, oldSpec map[string]interface{}, krmPath, dclPath []string, schema *openapi.Schema, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) ([]string, error) {
if schema.Type != "object" {
return nil, fmt.Errorf("expect the schame type to be 'object', but got %v", schema.Type)
}
ret := make([]string, 0)
for f, s := range schema.Properties {
if s.ReadOnly {
continue
}
isImmutable, err := dclextension.IsImmutableField(s)
if err != nil {
return nil, fmt.Errorf("error determining if field %v is immutable", f)
}
if dclextension.IsReferenceField(s) {
if !isImmutable {
continue
}
// If parent reference field is immutable and resource supports
// multiple parent types, changes to either the hierarchical
// reference key or value should be rejected.
if dcl.IsMultiTypeParentReferenceField(append(dclPath, f)) {
for _, h := range hierarchicalRefs {
if !reflect.DeepEqual(oldSpec[h.Key], spec[h.Key]) {
krmPathToField := pathslice.ToString(append(krmPath, h.Key))
ret = append(ret, krmPathToField)
}
}
continue
}
refField, err := dclextension.GetReferenceFieldName(append(dclPath, f), s)
if err != nil {
return nil, err
}
if !reflect.DeepEqual(oldSpec[refField], spec[refField]) {
krmPathToField := pathslice.ToString(append(krmPath, refField))
ret = append(ret, krmPathToField)
}
continue
}
krmPathToField := pathslice.ToString(append(krmPath, f))
oldVal := oldSpec[f]
newVal := spec[f]
if oldVal == nil && newVal == nil {
continue
}
if isImmutable && (oldVal == nil || newVal == nil) {
ret = append(ret, krmPathToField)
continue
}
switch s.Type {
case "object":
var v1 map[string]interface{}
var v2 map[string]interface{}
if oldVal != nil {
v1 = oldVal.(map[string]interface{})
}
if newVal != nil {
v2 = newVal.(map[string]interface{})
}
// Field is a map of key-value pairs
if s.AdditionalProperties != nil {
// If map field is immutable, reject any mutations.
if isImmutable {
if !reflect.DeepEqual(v1, v2) {
ret = append(ret, krmPathToField)
}
continue
}
if typeutil.IsPrimitiveType(s.AdditionalProperties.Type) {
continue
}
// If map field is mutable, but its key-value pairs have
// non-primitive values (e.g. objects, arrays of objects), the
// values themselves may have immutable fields. For now, let
// such cases pass through the webhook, and let DCL or the GCP
// API handle them instead (see b/216381382).
continue
}
// Field is an object
nestedFields, err := getChangesOnImmutableFields(v1, v2, append(krmPath, f), append(dclPath, f), s, hierarchicalRefs)
if err != nil {
return nil, err
}
ret = append(ret, nestedFields...)
case "array":
if typeutil.IsPrimitiveType(s.Items.Type) {
if isImmutable && !reflect.DeepEqual(oldVal, newVal) {
ret = append(ret, krmPathToField)
}
continue
}
// Kubernetes considers all lists of objects to be atomic, and so all subsequent
// applies will currently wipe out defaulted immutable fields. Temporarily delegate validation to DCL.
case "string", "boolean", "number", "integer":
if isImmutable && !reflect.DeepEqual(oldSpec[f], spec[f]) {
ret = append(ret, krmPathToField)
}
default:
return nil, fmt.Errorf("unknown schema type %v", s.Type)
}
}
return ret, nil
}