sigs.k8s.io/cluster-api@v1.7.1/internal/topology/variables/schema.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package variables
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  
    23  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    24  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	"k8s.io/utils/ptr"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  )
    30  
    31  // convertToAPIExtensionsJSONSchemaProps converts a clusterv1.JSONSchemaProps to apiextensions.JSONSchemaProp.
    32  // NOTE: This is used whenever we want to use one of the upstream libraries, as they use apiextensions.JSONSchemaProp.
    33  // NOTE: If new fields are added to clusterv1.JSONSchemaProps (e.g. to support complex types), the corresponding
    34  // schema validation must be added to validateRootSchema too.
    35  func convertToAPIExtensionsJSONSchemaProps(schema *clusterv1.JSONSchemaProps, fldPath *field.Path) (*apiextensions.JSONSchemaProps, field.ErrorList) {
    36  	var allErrs field.ErrorList
    37  
    38  	props := &apiextensions.JSONSchemaProps{
    39  		Type:             schema.Type,
    40  		Required:         schema.Required,
    41  		MaxItems:         schema.MaxItems,
    42  		MinItems:         schema.MinItems,
    43  		UniqueItems:      schema.UniqueItems,
    44  		Format:           schema.Format,
    45  		MaxLength:        schema.MaxLength,
    46  		MinLength:        schema.MinLength,
    47  		Pattern:          schema.Pattern,
    48  		ExclusiveMaximum: schema.ExclusiveMaximum,
    49  		ExclusiveMinimum: schema.ExclusiveMinimum,
    50  	}
    51  
    52  	// Only set XPreserveUnknownFields to true if it's true.
    53  	// apiextensions.JSONSchemaProps only allows setting XPreserveUnknownFields
    54  	// to true or undefined, false is forbidden.
    55  	if schema.XPreserveUnknownFields {
    56  		props.XPreserveUnknownFields = ptr.To(true)
    57  	}
    58  
    59  	if schema.Default != nil && schema.Default.Raw != nil {
    60  		var v interface{}
    61  		if err := json.Unmarshal(schema.Default.Raw, &v); err != nil {
    62  			allErrs = append(allErrs, field.Invalid(fldPath.Child("default"), string(schema.Default.Raw),
    63  				fmt.Sprintf("default is not valid JSON: %v", err)))
    64  		} else {
    65  			var v apiextensions.JSON
    66  			err := apiextensionsv1.Convert_v1_JSON_To_apiextensions_JSON(schema.Default, &v, nil)
    67  			if err != nil {
    68  				allErrs = append(allErrs, field.Invalid(fldPath.Child("default"), string(schema.Default.Raw),
    69  					fmt.Sprintf("failed to convert default: %v", err)))
    70  			} else {
    71  				props.Default = &v
    72  			}
    73  		}
    74  	}
    75  
    76  	if len(schema.Enum) > 0 {
    77  		for i, enum := range schema.Enum {
    78  			if enum.Raw == nil {
    79  				continue
    80  			}
    81  
    82  			var v interface{}
    83  			if err := json.Unmarshal(enum.Raw, &v); err != nil {
    84  				allErrs = append(allErrs, field.Invalid(fldPath.Child("enum").Index(i), string(enum.Raw),
    85  					fmt.Sprintf("enum value is not valid JSON: %v", err)))
    86  			} else {
    87  				var v apiextensions.JSON
    88  				err := apiextensionsv1.Convert_v1_JSON_To_apiextensions_JSON(&schema.Enum[i], &v, nil)
    89  				if err != nil {
    90  					allErrs = append(allErrs, field.Invalid(fldPath.Child("enum").Index(i), string(enum.Raw),
    91  						fmt.Sprintf("failed to convert enum value: %v", err)))
    92  				} else {
    93  					props.Enum = append(props.Enum, v)
    94  				}
    95  			}
    96  		}
    97  	}
    98  
    99  	if schema.Example != nil && schema.Example.Raw != nil {
   100  		var v interface{}
   101  		if err := json.Unmarshal(schema.Example.Raw, &v); err != nil {
   102  			allErrs = append(allErrs, field.Invalid(fldPath.Child("example"), string(schema.Example.Raw),
   103  				fmt.Sprintf("example is not valid JSON: %v", err)))
   104  		} else {
   105  			var value apiextensions.JSON
   106  			err := apiextensionsv1.Convert_v1_JSON_To_apiextensions_JSON(schema.Example, &value, nil)
   107  			if err != nil {
   108  				allErrs = append(allErrs, field.Invalid(fldPath.Child("example"), string(schema.Example.Raw),
   109  					fmt.Sprintf("failed to convert example value: %v", err)))
   110  			} else {
   111  				props.Example = &value
   112  			}
   113  		}
   114  	}
   115  	if schema.Maximum != nil {
   116  		f := float64(*schema.Maximum)
   117  		props.Maximum = &f
   118  	}
   119  
   120  	if schema.Minimum != nil {
   121  		f := float64(*schema.Minimum)
   122  		props.Minimum = &f
   123  	}
   124  
   125  	if schema.AdditionalProperties != nil {
   126  		apiExtensionsSchema, err := convertToAPIExtensionsJSONSchemaProps(schema.AdditionalProperties, fldPath.Child("additionalProperties"))
   127  		if err != nil {
   128  			allErrs = append(allErrs, field.Invalid(fldPath.Child("additionalProperties"), "",
   129  				fmt.Sprintf("failed to convert schema: %v", err)))
   130  		} else {
   131  			props.AdditionalProperties = &apiextensions.JSONSchemaPropsOrBool{
   132  				// Allows must be true to allow "additional properties".
   133  				// Otherwise only the ones from .Properties are allowed.
   134  				Allows: true,
   135  				Schema: apiExtensionsSchema,
   136  			}
   137  		}
   138  	}
   139  
   140  	if len(schema.Properties) > 0 {
   141  		props.Properties = map[string]apiextensions.JSONSchemaProps{}
   142  		for propertyName, propertySchema := range schema.Properties {
   143  			p := propertySchema
   144  			apiExtensionsSchema, err := convertToAPIExtensionsJSONSchemaProps(&p, fldPath.Child("properties").Key(propertyName))
   145  			if err != nil {
   146  				allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key(propertyName), "",
   147  					fmt.Sprintf("failed to convert schema: %v", err)))
   148  			} else {
   149  				props.Properties[propertyName] = *apiExtensionsSchema
   150  			}
   151  		}
   152  	}
   153  
   154  	if schema.Items != nil {
   155  		apiExtensionsSchema, err := convertToAPIExtensionsJSONSchemaProps(schema.Items, fldPath.Child("items"))
   156  		if err != nil {
   157  			allErrs = append(allErrs, field.Invalid(fldPath.Child("items"), "",
   158  				fmt.Sprintf("failed to convert schema: %v", err)))
   159  		} else {
   160  			props.Items = &apiextensions.JSONSchemaPropsOrArray{
   161  				Schema: apiExtensionsSchema,
   162  			}
   163  		}
   164  	}
   165  
   166  	return props, allErrs
   167  }