in pkg/resourceoverrides/utils.go [93:229]
func PreserveMutuallyExclusiveNonReferenceField(crd *apiextensions.CustomResourceDefinition, parentPath []string, referenceFieldName, nonReferenceFieldName string) error {
if referenceFieldName == "" || nonReferenceFieldName == "" {
return fmt.Errorf("both 'referenceFieldName' and 'nonReferenceFieldName' must be specified")
}
// 1. Get the parent schema of the fields.
schema := k8s.GetOpenAPIV3SchemaFromCRD(crd)
var err error
var parent *apiextensions.JSONSchemaProps
// Prepend the top-level 'spec' field into path.
if len(parentPath) == 0 {
parentPath = []string{"spec"}
} else {
parentPath = append([]string{"spec"}, parentPath...)
}
parentPathStr := strings.Join(parentPath, ".")
parent, err = getSchemaForPath(schema, parentPath)
if err != nil {
return fmt.Errorf("can't get schema for path '%v' in CRD %s: %w", parentPathStr, crd.Name, err)
}
// 2. Check if the reference field is required.
requiredFields, err := crdutil.GetRequiredRuleForObjectOrArray(parent)
if err != nil {
return fmt.Errorf("error getting the required rule under path %s for CRD %s: %w", parentPath, crd.Name, err)
}
var required bool
for _, field := range requiredFields {
if field == referenceFieldName {
required = true
break
}
}
// 3. Set the `oneOf` or `not` rule based on whether the reference field is
// required.
if required {
oneOfRule, err := crdutil.GetOneOfRuleForObjectOrArray(parent)
if err != nil {
return fmt.Errorf("error getting the oneOf rule under path %s for CRD %s: %w", parentPath, crd.Name, err)
}
// TODO(b/223688758): Handle multiple oneOf rules.
if oneOfRule != nil {
return fmt.Errorf("can't handle multiple pairs of required mutually exclusive fields under %s for field %s and %s in CRD %s", parentPath, referenceFieldName, referenceFieldName, crd.Name)
}
oneOfRule = []*apiextensions.JSONSchemaProps{
{
Required: []string{nonReferenceFieldName},
},
{
Required: []string{referenceFieldName},
},
}
if err := crdutil.SetOneOfRuleForObjectOrArray(parent, oneOfRule); err != nil {
return fmt.Errorf("error setting the oneOf rule under path %s for CRD %s: %w", parentPath, crd.Name, err)
}
var updatedRequiredFields []string
for _, field := range requiredFields {
if field != referenceFieldName {
updatedRequiredFields = append(updatedRequiredFields, field)
}
}
if err := crdutil.SetRequiredRuleForObjectOrArray(parent, updatedRequiredFields); err != nil {
return fmt.Errorf("error setting the required rule under path %s for CRD %s: %w", parentPath, crd.Name, err)
}
} else {
notRule, err := crdutil.GetNotRuleForObjectOrArray(parent)
if err != nil {
return fmt.Errorf("error getting the not rule under path %s for CRD %s: %w", parentPath, crd.Name, err)
}
// TODO(b/223688758): Handle multiple not rules.
if notRule != nil {
return fmt.Errorf("can't handling multiple pairs of optional mutually exclusive fields for %s in %s", referenceFieldName, crd.Name)
}
notRule = &apiextensions.JSONSchemaProps{
Required: []string{nonReferenceFieldName, referenceFieldName},
}
if err := crdutil.SetNotRuleForObjectOrArray(parent, notRule); err != nil {
return fmt.Errorf("error setting the not rule under path %s for CRD %s: %w", parentPath, crd.Name, err)
}
}
// 4. Add the non-reference field into the schema.
referenceFieldSchema, ok, err := crdutil.GetSchemaForFieldUnderObjectOrArray(referenceFieldName, parent)
if err != nil {
return fmt.Errorf("error getting schema for reference field %s under path %s for CRD %s: %w", referenceFieldName, parentPath, crd.Name, err)
}
if !ok {
return fmt.Errorf("can't find reference field %s under path %s for CRD %s", referenceFieldName, parentPath, crd.Name)
}
fieldType := referenceFieldSchema.Type
if fieldType != "object" && fieldType != "array" {
return fmt.Errorf("wrong type for reference field %s under path %s for CRD %s: %s", referenceFieldName, parentPath, crd.Name, fieldType)
}
var nonReferenceFieldSchema *apiextensions.JSONSchemaProps
description := fmt.Sprintf("DEPRECATED. Although this field is still available, there is limited support. "+
"We recommend that you use `%s.%s` instead.", parentPathStr, referenceFieldName)
if fieldType == "object" {
nonReferenceFieldSchema = &apiextensions.JSONSchemaProps{
Description: description,
// When the type of a reference field is object, it means that the
// original field type is string.
Type: "string",
}
} else if fieldType == "array" {
nonReferenceFieldSchema = &apiextensions.JSONSchemaProps{
Description: description,
// When the type of a reference field is an array, it means that the
// original field type is an array of strings.
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
}
}
if err := crdutil.SetSchemaForFieldUnderObjectOrArray(nonReferenceFieldName, parent, nonReferenceFieldSchema); err != nil {
return fmt.Errorf("error setting schema for non-reference field %s under path %s for CRD %s: %w", nonReferenceFieldName, parentPath, crd.Name, err)
}
// 5. Set the updated schema.
updatedSchema, err := getSchemaForPath(schema, parentPath[:len(parentPath)-1])
if err != nil {
return fmt.Errorf("can't get schema for path '%v' in CRD %s: %w", parentPathStr, crd.Name, err)
}
if err := crdutil.SetSchemaForFieldUnderObjectOrArray(parentPath[len(parentPath)-1], updatedSchema, parent); err != nil {
return fmt.Errorf("error setting updated schema for parent path '%v' for CRD %s: %w", parentPathStr, crd.Name, err)
}
return nil
}