func getChangesOnImmutableFields()

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
}