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 }