Skip to content

Commit ac6404e

Browse files
author
Alexander Zielenski
committed
add wrapper around gnostic v3 document
1 parent 8efd2ac commit ac6404e

File tree

9 files changed

+469
-4
lines changed

9 files changed

+469
-4
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ require (
2323
github.com/stretchr/testify v1.5.1
2424
golang.org/x/tools v0.1.5 // indirect
2525
gopkg.in/yaml.v2 v2.3.0
26-
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
26+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
2727
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c
2828
k8s.io/klog/v2 v2.2.0
2929
k8s.io/utils v0.0.0-20210802155522-efc7438f0176

pkg/util/proto/document_v3.go

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

0 commit comments

Comments
 (0)