Skip to content

Commit f212f45

Browse files
committed
Fix errors from breaking changes in kin-openapi
1 parent f096177 commit f212f45

File tree

4 files changed

+287
-58
lines changed

4 files changed

+287
-58
lines changed

manifest/openapi/foundry_v2.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,19 @@ func (f *foapiv2) getTypeByID(id string, h map[string]string, ap tftypes.Attribu
100100
return nil, errors.New("invalid type reference (nil)")
101101
}
102102

103-
sch, err := resolveSchemaRef(swd, f.swagger.Definitions)
103+
sch, err := resolveSchemaRef2(swd, f.swagger.Definitions)
104104
if err != nil {
105105
return nil, fmt.Errorf("failed to resolve schema: %s", err)
106106
}
107107

108-
return getTypeFromSchema(sch, f.recursionDepth, &(f.typeCache), f.swagger.Definitions, ap, h)
108+
return getTypeFromSchema2(sch, f.recursionDepth, &(f.typeCache), f.swagger.Definitions, ap, h)
109109
}
110110

111111
// buildGvkIndex builds the reverse lookup index that associates each GVK
112112
// to its corresponding string key in the swagger.Definitions map
113113
func (f *foapiv2) buildGvkIndex() error {
114114
for did, dRef := range f.swagger.Definitions {
115-
def, err := resolveSchemaRef(dRef, f.swagger.Definitions)
115+
def, err := resolveSchemaRef2(dRef, f.swagger.Definitions)
116116
if err != nil {
117117
return err
118118
}

manifest/openapi/foundry_v3.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ func (f *foapiv3) GetTypeByGVK(_ schema.GroupVersionKind) (tftypes.Type, map[str
5656

5757
sref := f.doc.Components.Schemas[""]
5858

59-
sch, err := resolveSchemaRef(sref, f.doc.Components.Schemas)
59+
sch, err := resolveSchemaRef3(sref, f.doc.Components.Schemas)
6060
if err != nil {
6161
return nil, hints, fmt.Errorf("failed to resolve schema: %s", err)
6262
}
6363

64-
tftype, err := getTypeFromSchema(sch, 50, &(f.typeCache), f.doc.Components.Schemas, ap, hints)
64+
tftype, err := getTypeFromSchema3(sch, 50, &(f.typeCache), f.doc.Components.Schemas, ap, hints)
6565
return tftype, hints, err
6666
}

manifest/openapi/schema_v2.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package openapi
5+
6+
import (
7+
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"strings"
11+
"sync"
12+
13+
"github.com/getkin/kin-openapi/openapi2"
14+
"github.com/getkin/kin-openapi/openapi3"
15+
"github.com/hashicorp/terraform-plugin-go/tftypes"
16+
"github.com/hashicorp/terraform-provider-kubernetes/manifest"
17+
"github.com/mitchellh/hashstructure"
18+
)
19+
20+
func resolveSchemaRef2(ref *openapi2.SchemaRef, defs map[string]*openapi2.SchemaRef) (*openapi2.Schema, error) {
21+
if ref.Value != nil {
22+
return ref.Value, nil
23+
}
24+
25+
rp := strings.Split(ref.Ref, "/")
26+
sid := rp[len(rp)-1]
27+
28+
nref, ok := defs[sid]
29+
30+
if !ok {
31+
return nil, errors.New("schema not found")
32+
}
33+
if nref == nil {
34+
return nil, errors.New("nil schema reference")
35+
}
36+
37+
// These are exceptional situations that require non-standard types.
38+
switch sid {
39+
case "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps":
40+
t := openapi2.Schema{
41+
Type: &openapi3.Types{""},
42+
}
43+
return &t, nil
44+
case "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps":
45+
t := openapi2.Schema{
46+
Type: &openapi3.Types{""},
47+
}
48+
return &t, nil
49+
}
50+
51+
return resolveSchemaRef2(nref, defs)
52+
}
53+
54+
func getTypeFromSchema2(elem *openapi2.Schema, stackdepth uint64, typeCache *sync.Map, defs map[string]*openapi2.SchemaRef, ap tftypes.AttributePath, th map[string]string) (tftypes.Type, error) {
55+
if stackdepth == 0 {
56+
// this is a hack to overcome the inability to express recursion in tftypes
57+
return nil, errors.New("recursion runaway while generating type from OpenAPI spec")
58+
}
59+
60+
if elem == nil {
61+
return nil, errors.New("cannot convert OpenAPI type (nil)")
62+
}
63+
64+
h, herr := hashstructure.Hash(elem, nil)
65+
66+
var t tftypes.Type
67+
68+
// Check if attribute type is tagged as 'x-kubernetes-preserve-unknown-fields' in OpenAPI.
69+
// If so, we add a type hint to indicate this and return DynamicPseudoType for this attribute,
70+
// since we have no further structural information about it.
71+
if xpufJSON, ok := elem.Extensions[manifest.PreserveUnknownFieldsLabel]; ok {
72+
var xpuf bool
73+
v, err := xpufJSON.(json.RawMessage).MarshalJSON()
74+
if err == nil {
75+
err = json.Unmarshal(v, &xpuf)
76+
if err == nil && xpuf {
77+
th[ap.String()] = manifest.PreserveUnknownFieldsLabel
78+
}
79+
}
80+
}
81+
82+
// check if type is in cache
83+
// HACK: this is temporarily disabled to diagnose a cache corruption issue.
84+
// if herr == nil {
85+
// if t, ok := typeCache.Load(h); ok {
86+
// return t.(tftypes.Type), nil
87+
// }
88+
// }
89+
switch {
90+
case elem.Type.Is("string"):
91+
if elem.Format == "int-or-string" {
92+
th[ap.String()] = "io.k8s.apimachinery.pkg.util.intstr.IntOrString"
93+
}
94+
return tftypes.String, nil
95+
96+
case elem.Type.Is("boolean"):
97+
return tftypes.Bool, nil
98+
99+
case elem.Type.Is("number"):
100+
return tftypes.Number, nil
101+
102+
case elem.Type.Is("integer"):
103+
return tftypes.Number, nil
104+
105+
case elem.Type.Is(""):
106+
if xv, ok := elem.Extensions["x-kubernetes-int-or-string"]; ok {
107+
xb, err := xv.(json.RawMessage).MarshalJSON()
108+
if err != nil {
109+
return tftypes.DynamicPseudoType, nil
110+
}
111+
var x bool
112+
err = json.Unmarshal(xb, &x)
113+
if err == nil && x {
114+
th[ap.String()] = "io.k8s.apimachinery.pkg.util.intstr.IntOrString"
115+
return tftypes.String, nil
116+
}
117+
}
118+
return tftypes.DynamicPseudoType, nil // this is where DynamicType is set for when an attribute is tagged as 'x-kubernetes-preserve-unknown-fields'
119+
120+
case elem.Type.Is("array"):
121+
switch {
122+
case elem.Items != nil && elem.AdditionalProperties.Has == nil: // normal array - translates to a tftypes.List
123+
it, err := resolveSchemaRef2(elem.Items, defs)
124+
if err != nil {
125+
return nil, fmt.Errorf("failed to resolve schema for items: %s", err)
126+
}
127+
aap := ap.WithElementKeyInt(-1)
128+
et, err := getTypeFromSchema2(it, stackdepth-1, typeCache, defs, *aap, th)
129+
if err != nil {
130+
return nil, err
131+
}
132+
if !isTypeFullyKnown(et) {
133+
t = tftypes.Tuple{ElementTypes: []tftypes.Type{et}}
134+
} else {
135+
t = tftypes.List{ElementType: et}
136+
}
137+
if herr == nil {
138+
typeCache.Store(h, t)
139+
}
140+
return t, nil
141+
case elem.AdditionalProperties.Has != nil && *elem.AdditionalProperties.Has && elem.Items == nil: // "overriden" array - translates to a tftypes.Tuple
142+
v2ref, err := schemaRefV3toV2(elem.AdditionalProperties.Schema)
143+
if err != nil {
144+
return nil, fmt.Errorf("failed to convert schema from v3 to v2: %s", err)
145+
}
146+
it, err := resolveSchemaRef2(v2ref, defs)
147+
if err != nil {
148+
return nil, fmt.Errorf("failed to resolve schema for items: %s", err)
149+
}
150+
aap := ap.WithElementKeyInt(-1)
151+
et, err := getTypeFromSchema2(it, stackdepth-1, typeCache, defs, *aap, th)
152+
if err != nil {
153+
return nil, err
154+
}
155+
t = tftypes.Tuple{ElementTypes: []tftypes.Type{et}}
156+
return t, nil
157+
}
158+
159+
case elem.Type.Is("object"):
160+
161+
switch {
162+
case elem.Properties != nil && (elem.AdditionalProperties.Has == nil || !*elem.AdditionalProperties.Has):
163+
// this is a standard OpenAPI object
164+
atts := make(map[string]tftypes.Type, len(elem.Properties))
165+
for p, v := range elem.Properties {
166+
schema, err := resolveSchemaRef2(v, defs)
167+
if err != nil {
168+
return nil, fmt.Errorf("failed to resolve schema: %s", err)
169+
}
170+
aap := ap.WithAttributeName(p)
171+
pType, err := getTypeFromSchema2(schema, stackdepth-1, typeCache, defs, *aap, th)
172+
if err != nil {
173+
return nil, err
174+
}
175+
atts[p] = pType
176+
}
177+
t = tftypes.Object{AttributeTypes: atts}
178+
if herr == nil {
179+
typeCache.Store(h, t)
180+
}
181+
return t, nil
182+
183+
case elem.Properties == nil && (elem.AdditionalProperties.Has != nil && *elem.AdditionalProperties.Has):
184+
// this is how OpenAPI defines associative arrays
185+
v2ref, err := schemaRefV3toV2(elem.AdditionalProperties.Schema)
186+
if err != nil {
187+
return nil, fmt.Errorf("failed to convert schema from v3 to v2: %s", err)
188+
}
189+
s, err := resolveSchemaRef2(v2ref, defs)
190+
if err != nil {
191+
return nil, fmt.Errorf("failed to resolve schema: %s", err)
192+
}
193+
aap := ap.WithElementKeyString("#")
194+
pt, err := getTypeFromSchema2(s, stackdepth-1, typeCache, defs, *aap, th)
195+
if err != nil {
196+
return nil, err
197+
}
198+
t = tftypes.Map{ElementType: pt}
199+
if herr == nil {
200+
typeCache.Store(h, t)
201+
}
202+
return t, nil
203+
204+
case elem.Properties == nil && (elem.AdditionalProperties.Has == nil || !*elem.AdditionalProperties.Has):
205+
// this is a strange case, encountered with io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 and also io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceSubresourceStatus
206+
t = tftypes.DynamicPseudoType
207+
if herr == nil {
208+
typeCache.Store(h, t)
209+
}
210+
return t, nil
211+
212+
}
213+
}
214+
215+
return nil, fmt.Errorf("unknown type: %s", elem.Type)
216+
}
217+
218+
func isTypeFullyKnown(t tftypes.Type) bool {
219+
if t.Is(tftypes.DynamicPseudoType) {
220+
return false
221+
}
222+
switch {
223+
case t.Is(tftypes.Object{}):
224+
for _, att := range t.(tftypes.Object).AttributeTypes {
225+
if !isTypeFullyKnown(att) {
226+
return false
227+
}
228+
}
229+
case t.Is(tftypes.Tuple{}):
230+
for _, ett := range t.(tftypes.Tuple).ElementTypes {
231+
if !isTypeFullyKnown(ett) {
232+
return false
233+
}
234+
}
235+
case t.Is(tftypes.List{}):
236+
return isTypeFullyKnown(t.(tftypes.List).ElementType)
237+
case t.Is(tftypes.Set{}):
238+
return isTypeFullyKnown(t.(tftypes.Set).ElementType)
239+
case t.Is(tftypes.Map{}):
240+
return isTypeFullyKnown(t.(tftypes.Map).ElementType)
241+
}
242+
return true
243+
}
244+
245+
func schemaRefV3toV2(o *openapi3.SchemaRef) (*openapi2.SchemaRef, error) {
246+
j, err := o.MarshalJSON()
247+
if err != nil {
248+
return nil, err
249+
}
250+
r := &openapi2.SchemaRef{}
251+
err = r.UnmarshalJSON(j)
252+
if err != nil {
253+
return nil, err
254+
}
255+
return r, nil
256+
}

0 commit comments

Comments
 (0)