k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/util/proto/document_v3.go (about) 1 /* 2 Copyright 2022 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 proto 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 24 openapi_v3 "github.com/google/gnostic-models/openapiv3" 25 "gopkg.in/yaml.v3" 26 ) 27 28 // Temporary parse implementation to be used until gnostic->kube-openapi conversion 29 // is possible. 30 func NewOpenAPIV3Data(doc *openapi_v3.Document) (Models, error) { 31 definitions := Definitions{ 32 models: map[string]Schema{}, 33 } 34 35 schemas := doc.GetComponents().GetSchemas() 36 if schemas == nil { 37 return &definitions, nil 38 } 39 40 // Save the list of all models first. This will allow us to 41 // validate that we don't have any dangling reference. 42 for _, namedSchema := range schemas.GetAdditionalProperties() { 43 definitions.models[namedSchema.GetName()] = nil 44 } 45 46 // Now, parse each model. We can validate that references exists. 47 for _, namedSchema := range schemas.GetAdditionalProperties() { 48 path := NewPath(namedSchema.GetName()) 49 val := namedSchema.GetValue() 50 51 if val == nil { 52 continue 53 } 54 55 if schema, err := definitions.ParseV3SchemaOrReference(namedSchema.GetValue(), &path); err != nil { 56 return nil, err 57 } else if schema != nil { 58 // Schema may be nil if we hit incompleteness in the conversion, 59 // but not a fatal error 60 definitions.models[namedSchema.GetName()] = schema 61 } 62 } 63 64 return &definitions, nil 65 } 66 67 func (d *Definitions) ParseV3SchemaReference(s *openapi_v3.Reference, path *Path) (Schema, error) { 68 base := &BaseSchema{ 69 Description: s.Description, 70 } 71 72 if !strings.HasPrefix(s.GetXRef(), "#/components/schemas") { 73 // Only resolve references to components/schemas. We may add support 74 // later for other in-spec paths, but otherwise treat unrecognized 75 // refs as arbitrary/unknown values. 76 return &Arbitrary{ 77 BaseSchema: *base, 78 }, nil 79 } 80 81 reference := strings.TrimPrefix(s.GetXRef(), "#/components/schemas/") 82 if _, ok := d.models[reference]; !ok { 83 return nil, newSchemaError(path, "unknown model in reference: %q", reference) 84 } 85 86 return &Ref{ 87 BaseSchema: BaseSchema{ 88 Description: s.Description, 89 }, 90 reference: reference, 91 definitions: d, 92 }, nil 93 } 94 95 func (d *Definitions) ParseV3SchemaOrReference(s *openapi_v3.SchemaOrReference, path *Path) (Schema, error) { 96 var schema Schema 97 var err error 98 99 switch v := s.GetOneof().(type) { 100 case *openapi_v3.SchemaOrReference_Reference: 101 // Any references stored in #!/components/... are bound to refer 102 // to external documents. This API does not support such a 103 // feature. 104 // 105 // In the weird case that this is a reference to a schema that is 106 // not external, we attempt to parse anyway 107 schema, err = d.ParseV3SchemaReference(v.Reference, path) 108 case *openapi_v3.SchemaOrReference_Schema: 109 schema, err = d.ParseSchemaV3(v.Schema, path) 110 default: 111 panic("unexpected type") 112 } 113 114 return schema, err 115 } 116 117 // ParseSchema creates a walkable Schema from an openapi v3 schema. While 118 // this function is public, it doesn't leak through the interface. 119 func (d *Definitions) ParseSchemaV3(s *openapi_v3.Schema, path *Path) (Schema, error) { 120 switch s.GetType() { 121 case object: 122 for _, extension := range s.GetSpecificationExtension() { 123 if extension.Name == "x-kubernetes-group-version-kind" { 124 // Objects with x-kubernetes-group-version-kind are always top 125 // level types. 126 return d.parseV3Kind(s, path) 127 } 128 } 129 130 if len(s.GetProperties().GetAdditionalProperties()) > 0 { 131 return d.parseV3Kind(s, path) 132 } 133 return d.parseV3Map(s, path) 134 case array: 135 return d.parseV3Array(s, path) 136 case String, Number, Integer, Boolean: 137 return d.parseV3Primitive(s, path) 138 default: 139 return d.parseV3Arbitrary(s, path) 140 } 141 } 142 143 func (d *Definitions) parseV3Kind(s *openapi_v3.Schema, path *Path) (Schema, error) { 144 if s.GetType() != object { 145 return nil, newSchemaError(path, "invalid object type") 146 } else if s.GetProperties() == nil { 147 return nil, newSchemaError(path, "object doesn't have properties") 148 } 149 150 fields := map[string]Schema{} 151 fieldOrder := []string{} 152 153 for _, namedSchema := range s.GetProperties().GetAdditionalProperties() { 154 var err error 155 name := namedSchema.GetName() 156 path := path.FieldPath(name) 157 fields[name], err = d.ParseV3SchemaOrReference(namedSchema.GetValue(), &path) 158 if err != nil { 159 return nil, err 160 } 161 fieldOrder = append(fieldOrder, name) 162 } 163 164 base, err := d.parseV3BaseSchema(s, path) 165 if err != nil { 166 return nil, err 167 } 168 169 return &Kind{ 170 BaseSchema: *base, 171 RequiredFields: s.GetRequired(), 172 Fields: fields, 173 FieldOrder: fieldOrder, 174 }, nil 175 } 176 177 func (d *Definitions) parseV3Arbitrary(s *openapi_v3.Schema, path *Path) (Schema, error) { 178 base, err := d.parseV3BaseSchema(s, path) 179 if err != nil { 180 return nil, err 181 } 182 return &Arbitrary{ 183 BaseSchema: *base, 184 }, nil 185 } 186 187 func (d *Definitions) parseV3Primitive(s *openapi_v3.Schema, path *Path) (Schema, error) { 188 switch s.GetType() { 189 case String: // do nothing 190 case Number: // do nothing 191 case Integer: // do nothing 192 case Boolean: // do nothing 193 default: 194 // Unsupported primitive type. Treat as arbitrary type 195 return d.parseV3Arbitrary(s, path) 196 } 197 198 base, err := d.parseV3BaseSchema(s, path) 199 if err != nil { 200 return nil, err 201 } 202 203 return &Primitive{ 204 BaseSchema: *base, 205 Type: s.GetType(), 206 Format: s.GetFormat(), 207 }, nil 208 } 209 210 func (d *Definitions) parseV3Array(s *openapi_v3.Schema, path *Path) (Schema, error) { 211 if s.GetType() != array { 212 return nil, newSchemaError(path, `array should have type "array"`) 213 } else if len(s.GetItems().GetSchemaOrReference()) != 1 { 214 // This array can have multiple types in it (or no types at all) 215 // This is not supported by this conversion. 216 // Just return an arbitrary type 217 return d.parseV3Arbitrary(s, path) 218 } 219 220 sub, err := d.ParseV3SchemaOrReference(s.GetItems().GetSchemaOrReference()[0], path) 221 if err != nil { 222 return nil, err 223 } 224 225 base, err := d.parseV3BaseSchema(s, path) 226 if err != nil { 227 return nil, err 228 } 229 return &Array{ 230 BaseSchema: *base, 231 SubType: sub, 232 }, nil 233 } 234 235 // We believe the schema is a map, verify and return a new schema 236 func (d *Definitions) parseV3Map(s *openapi_v3.Schema, path *Path) (Schema, error) { 237 if s.GetType() != object { 238 return nil, newSchemaError(path, "invalid object type") 239 } 240 var sub Schema 241 242 switch p := s.GetAdditionalProperties().GetOneof().(type) { 243 case *openapi_v3.AdditionalPropertiesItem_Boolean: 244 // What does this boolean even mean? 245 base, err := d.parseV3BaseSchema(s, path) 246 if err != nil { 247 return nil, err 248 } 249 sub = &Arbitrary{ 250 BaseSchema: *base, 251 } 252 case *openapi_v3.AdditionalPropertiesItem_SchemaOrReference: 253 if schema, err := d.ParseV3SchemaOrReference(p.SchemaOrReference, path); err != nil { 254 return nil, err 255 } else { 256 sub = schema 257 } 258 case nil: 259 // no subtype? 260 sub = &Arbitrary{} 261 default: 262 panic("unrecognized type " + reflect.TypeOf(p).Name()) 263 } 264 265 base, err := d.parseV3BaseSchema(s, path) 266 if err != nil { 267 return nil, err 268 } 269 return &Map{ 270 BaseSchema: *base, 271 SubType: sub, 272 }, nil 273 } 274 275 func parseV3Interface(def *yaml.Node) (interface{}, error) { 276 if def == nil { 277 return nil, nil 278 } 279 var i interface{} 280 if err := def.Decode(&i); err != nil { 281 return nil, err 282 } 283 return i, nil 284 } 285 286 func (d *Definitions) parseV3BaseSchema(s *openapi_v3.Schema, path *Path) (*BaseSchema, error) { 287 if s == nil { 288 return nil, fmt.Errorf("cannot initialize BaseSchema from nil") 289 } 290 291 def, err := parseV3Interface(s.GetDefault().ToRawInfo()) 292 if err != nil { 293 return nil, err 294 } 295 296 return &BaseSchema{ 297 Description: s.GetDescription(), 298 Default: def, 299 Extensions: SpecificationExtensionToMap(s.GetSpecificationExtension()), 300 Path: *path, 301 }, nil 302 } 303 304 func SpecificationExtensionToMap(e []*openapi_v3.NamedAny) map[string]interface{} { 305 values := map[string]interface{}{} 306 307 for _, na := range e { 308 if na.GetName() == "" || na.GetValue() == nil { 309 continue 310 } 311 if na.GetValue().GetYaml() == "" { 312 continue 313 } 314 var value interface{} 315 err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value) 316 if err != nil { 317 continue 318 } 319 320 values[na.GetName()] = value 321 } 322 323 return values 324 }