Skip to content

Commit 168aa7f

Browse files
committed
Add OpenAPIConv
1 parent ddc6692 commit 168aa7f

File tree

6 files changed

+341
-6
lines changed

6 files changed

+341
-6
lines changed

pkg/builder3/openapi.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"k8s.io/kube-openapi/pkg/spec3"
2626
"k8s.io/kube-openapi/pkg/util"
2727
"k8s.io/kube-openapi/pkg/validation/spec"
28+
"k8s.io/kube-openapi/pkg/openapiconv"
2829
"net/http"
2930
"strings"
3031
)
@@ -51,6 +52,7 @@ func (o *openAPI) buildResponse(model interface{}, description string, content [
5152
response := &spec3.Response{
5253
ResponseProps: spec3.ResponseProps{
5354
Description: description,
55+
Headers: make(map[string]*spec3.Header),
5456
Content: make(map[string]*spec3.MediaType),
5557
},
5658
}
@@ -102,7 +104,17 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[
102104
}
103105
}
104106

105-
// TODO: Default response if needed. Common Response config
107+
for code, resp := range o.config.CommonResponses {
108+
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
109+
ret.Responses.StatusCodeResponses[code] = openapiconv.ConvertResponse(&resp, route.Produces())
110+
}
111+
}
112+
113+
// If there is still no response, use default response provided.
114+
if len(ret.Responses.StatusCodeResponses) == 0 {
115+
ret.Responses.Default = openapiconv.ConvertResponse(o.config.DefaultResponse, route.Produces())
116+
}
117+
106118

107119
ret.Parameters = make([]*spec3.Parameter, 0)
108120
params := route.Parameters()
@@ -163,6 +175,7 @@ func newOpenAPI(config *common.Config) openAPI {
163175
},
164176
Components: &spec3.Components{
165177
Schemas: map[string]*spec.Schema{},
178+
SecuritySchemes: make(spec3.SecuritySchemes),
166179
},
167180
},
168181
}

pkg/openapiconv/convert.go

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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 openapiconv
18+
19+
import (
20+
"strings"
21+
22+
klog "k8s.io/klog/v2"
23+
"k8s.io/kube-openapi/pkg/spec3"
24+
"k8s.io/kube-openapi/pkg/validation/spec"
25+
)
26+
27+
var OpenAPIV2DefPrefix = "#/definitions/"
28+
var OpenAPIV3DefPrefix = "#/components/schemas/"
29+
30+
func ConvertV2ToV3(v2Spec *spec.Swagger) *spec3.OpenAPI {
31+
v3Spec := &spec3.OpenAPI{
32+
Version: "3.0.0",
33+
Info: v2Spec.Info,
34+
ExternalDocs: ConvertExternalDocumentation(v2Spec.ExternalDocs),
35+
Paths: ConvertPaths(v2Spec.Paths),
36+
Components: ConvertComponents(v2Spec.SecurityDefinitions, v2Spec.Definitions),
37+
}
38+
39+
return v3Spec
40+
}
41+
42+
func ConvertExternalDocumentation(v2ED *spec.ExternalDocumentation) *spec3.ExternalDocumentation {
43+
if v2ED == nil {
44+
return nil
45+
}
46+
return &spec3.ExternalDocumentation{
47+
ExternalDocumentationProps: spec3.ExternalDocumentationProps{
48+
Description: v2ED.Description,
49+
URL: v2ED.URL,
50+
},
51+
}
52+
}
53+
54+
func ConvertComponents(v2SecurityDefinitions spec.SecurityDefinitions, v2Definitions spec.Definitions) *spec3.Components {
55+
components := &spec3.Components{
56+
Schemas: make(map[string]*spec.Schema),
57+
SecuritySchemes: make(spec3.SecuritySchemes),
58+
}
59+
for s, schema := range v2Definitions {
60+
components.Schemas[s] = ConvertSchema(&schema)
61+
}
62+
63+
for s, securityScheme := range v2SecurityDefinitions {
64+
components.SecuritySchemes[s] = ConvertSecurityScheme(securityScheme)
65+
}
66+
return components
67+
}
68+
69+
func ConvertSchema(v2Schema *spec.Schema) *spec.Schema {
70+
if v2Schema == nil {
71+
return nil
72+
}
73+
var v3Schema spec.Schema
74+
v3Schema = *v2Schema
75+
76+
if refString := v2Schema.Ref.String(); refString != "" {
77+
if idx := strings.Index(refString, OpenAPIV2DefPrefix); idx != -1 {
78+
v3Schema.Ref = spec.MustCreateRef(OpenAPIV3DefPrefix + refString[idx+len(OpenAPIV2DefPrefix):])
79+
} else {
80+
klog.Errorf("Error: Swagger V2 Ref %s does not contain #/definitions\n", refString)
81+
}
82+
}
83+
84+
for key, property := range v2Schema.Properties {
85+
v3Schema.Properties[key] = *ConvertSchema(&property)
86+
}
87+
if v2Schema.Items != nil {
88+
v3Schema.Items.Schema = ConvertSchema(v2Schema.Items.Schema)
89+
if len(v2Schema.Items.Schemas) > 0 {
90+
v3Schema.Items.Schemas = []spec.Schema{}
91+
for _, s := range v2Schema.Items.Schemas {
92+
v3Schema.Items.Schemas = append(v3Schema.Items.Schemas, *ConvertSchema(&s))
93+
}
94+
}
95+
}
96+
97+
if v2Schema.AdditionalProperties != nil {
98+
v3Schema.AdditionalProperties.Schema = ConvertSchema(v2Schema.AdditionalProperties.Schema)
99+
}
100+
if v2Schema.AdditionalItems != nil {
101+
v3Schema.AdditionalProperties.Schema = ConvertSchema(v2Schema.AdditionalProperties.Schema)
102+
}
103+
104+
return &v3Schema
105+
}
106+
107+
func ConvertSecurityScheme(securityScheme *spec.SecurityScheme) *spec3.SecurityScheme {
108+
if securityScheme == nil {
109+
return nil
110+
}
111+
ss := &spec3.SecurityScheme{
112+
VendorExtensible: securityScheme.VendorExtensible,
113+
SecuritySchemeProps: spec3.SecuritySchemeProps{
114+
Description: securityScheme.Description,
115+
Type: securityScheme.Type,
116+
Name: securityScheme.Name,
117+
In: securityScheme.In,
118+
},
119+
}
120+
121+
if securityScheme.Flow != "" {
122+
ss.Flows = make(map[string]*spec3.OAuthFlow)
123+
ss.Flows[securityScheme.Flow] = &spec3.OAuthFlow{
124+
OAuthFlowProps: spec3.OAuthFlowProps{
125+
AuthorizationUrl: securityScheme.AuthorizationURL,
126+
TokenUrl: securityScheme.TokenURL,
127+
Scopes: securityScheme.Scopes,
128+
},
129+
}
130+
}
131+
return ss
132+
}
133+
134+
func ConvertPaths(v2Paths *spec.Paths) *spec3.Paths {
135+
if v2Paths == nil {
136+
return nil
137+
}
138+
paths := &spec3.Paths{
139+
VendorExtensible: v2Paths.VendorExtensible,
140+
Paths: make(map[string]*spec3.Path),
141+
}
142+
143+
for k, v := range v2Paths.Paths {
144+
paths.Paths[k] = ConvertPathItem(v)
145+
}
146+
return paths
147+
}
148+
149+
func ConvertPathItem(pathItem spec.PathItem) *spec3.Path {
150+
path := &spec3.Path{
151+
Refable: pathItem.Refable,
152+
PathProps: spec3.PathProps{
153+
Get: ConvertOperation(pathItem.Get),
154+
Put: ConvertOperation(pathItem.Put),
155+
Post: ConvertOperation(pathItem.Post),
156+
Delete: ConvertOperation(pathItem.Delete),
157+
Options: ConvertOperation(pathItem.Options),
158+
Head: ConvertOperation(pathItem.Head),
159+
Patch: ConvertOperation(pathItem.Patch),
160+
Parameters: []*spec3.Parameter{},
161+
},
162+
VendorExtensible: pathItem.VendorExtensible,
163+
}
164+
for _, param := range pathItem.Parameters {
165+
path.Parameters = append(path.Parameters, ConvertParameter(param))
166+
}
167+
return path
168+
}
169+
170+
func ConvertOperation(op *spec.Operation) *spec3.Operation {
171+
if op == nil {
172+
return nil
173+
}
174+
operation := &spec3.Operation{
175+
VendorExtensible: op.VendorExtensible,
176+
OperationProps: spec3.OperationProps{
177+
Description: op.Description,
178+
ExternalDocs: ConvertExternalDocumentation(op.OperationProps.ExternalDocs),
179+
Tags: op.Tags,
180+
Summary: op.Summary,
181+
Deprecated: op.Deprecated,
182+
OperationId: op.ID,
183+
Parameters: []*spec3.Parameter{},
184+
},
185+
}
186+
187+
for _, param := range op.Parameters {
188+
if param.ParamProps.Name == "body" && param.ParamProps.Schema != nil {
189+
operation.OperationProps.RequestBody = &spec3.RequestBody{
190+
RequestBodyProps: spec3.RequestBodyProps{
191+
Content: make(map[string]*spec3.MediaType),
192+
},
193+
}
194+
for _, consumer := range op.Consumes {
195+
operation.RequestBody.Content[consumer] = &spec3.MediaType{
196+
MediaTypeProps: spec3.MediaTypeProps{
197+
Schema: ConvertSchema(param.ParamProps.Schema),
198+
},
199+
}
200+
}
201+
}
202+
operation.Parameters = append(operation.Parameters, ConvertParameter(param))
203+
}
204+
205+
operation.Responses = &spec3.Responses{ResponsesProps: spec3.ResponsesProps{
206+
Default: ConvertResponse(op.Responses.Default, op.Produces),
207+
StatusCodeResponses: make(map[int]*spec3.Response),
208+
},
209+
VendorExtensible: op.VendorExtensible,
210+
}
211+
212+
for k, v := range op.Responses.StatusCodeResponses {
213+
operation.Responses.StatusCodeResponses[k] = ConvertResponse(&v, op.Produces)
214+
}
215+
return operation
216+
}
217+
218+
func ConvertResponse(res *spec.Response, produces []string) *spec3.Response {
219+
if res == nil {
220+
return nil
221+
}
222+
response := &spec3.Response{
223+
Refable: res.Refable,
224+
VendorExtensible: res.VendorExtensible,
225+
ResponseProps: spec3.ResponseProps{
226+
Description: res.Description,
227+
Headers: make(map[string]*spec3.Header),
228+
Content: make(map[string]*spec3.MediaType),
229+
},
230+
}
231+
232+
for _, producer := range produces {
233+
response.ResponseProps.Content[producer] = &spec3.MediaType{
234+
MediaTypeProps: spec3.MediaTypeProps{
235+
Schema: ConvertSchema(res.Schema),
236+
},
237+
}
238+
239+
}
240+
return response
241+
}
242+
243+
func ConvertParameter(param spec.Parameter) *spec3.Parameter {
244+
param3 := &spec3.Parameter{
245+
Refable: param.Refable,
246+
VendorExtensible: param.VendorExtensible,
247+
ParameterProps: spec3.ParameterProps{
248+
Name: param.Name,
249+
Description: param.Description,
250+
In: param.In,
251+
Required: param.Required,
252+
Schema: ConvertSchema(param.Schema),
253+
AllowEmptyValue: param.AllowEmptyValue,
254+
},
255+
}
256+
return param3
257+
}

test/integration/builder/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func main() {
5353
config := testutil.CreateOpenAPIBuilderConfig()
5454
config.GetDefinitions = generated.GetOpenAPIDefinitions
5555
// Build the Paths using a simple WebService for the final spec
56-
swagger, serr := builderv2.BuildOpenAPISpec(testutil.CreateWebServices(), config)
56+
swagger, serr := builderv2.BuildOpenAPISpec(testutil.CreateWebServices(true), config)
5757
if serr != nil {
5858
log.Fatalf("ERROR: %s", serr.Error())
5959
}

test/integration/builder3/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func main() {
5454
config := testutil.CreateOpenAPIBuilderConfig()
5555
config.GetDefinitions = generated.GetOpenAPIDefinitions
5656
// Build the Paths using a simple WebService for the final spec
57-
swagger, serr := builderv3.BuildOpenAPISpec(testutil.CreateWebServices(), config)
57+
swagger, serr := builderv3.BuildOpenAPISpec(testutil.CreateWebServices(true), config)
5858
if serr != nil {
5959
log.Fatalf("ERROR: %s", serr.Error())
6060
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 openapiconv
18+
19+
import (
20+
"log"
21+
"reflect"
22+
"testing"
23+
24+
builderv2 "k8s.io/kube-openapi/pkg/builder"
25+
builderv3 "k8s.io/kube-openapi/pkg/builder3"
26+
"k8s.io/kube-openapi/pkg/openapiconv"
27+
"k8s.io/kube-openapi/pkg/validation/spec"
28+
"k8s.io/kube-openapi/test/integration/pkg/generated"
29+
"k8s.io/kube-openapi/test/integration/testutil"
30+
)
31+
32+
func TestConvertGolden(t *testing.T) {
33+
// Generate the definition names from the map keys returned
34+
// from GetOpenAPIDefinitions. Anonymous function returning empty
35+
// Ref is not used.
36+
var defNames []string
37+
for name, _ := range generated.GetOpenAPIDefinitions(func(name string) spec.Ref {
38+
return spec.Ref{}
39+
}) {
40+
defNames = append(defNames, name)
41+
}
42+
43+
// Create a minimal builder config, then call the builder with the definition names.
44+
config := testutil.CreateOpenAPIBuilderConfig()
45+
config.GetDefinitions = generated.GetOpenAPIDefinitions
46+
// Build the Paths using a simple WebService for the final spec
47+
openapiv2, serr := builderv2.BuildOpenAPISpec(testutil.CreateWebServices(false), config)
48+
if serr != nil {
49+
log.Fatalf("ERROR: %s", serr.Error())
50+
}
51+
52+
openapiv3, serr := builderv3.BuildOpenAPISpec(testutil.CreateWebServices(false), config)
53+
if serr != nil {
54+
log.Fatalf("ERROR: %s", serr.Error())
55+
}
56+
57+
convertedOpenAPIV3 := openapiconv.ConvertV2ToV3(openapiv2)
58+
if !reflect.DeepEqual(openapiv3, convertedOpenAPIV3) {
59+
t.Errorf("Expected converted OpenAPI to be equal, %v, %v", openapiv3, convertedOpenAPIV3)
60+
}
61+
62+
63+
}

0 commit comments

Comments
 (0)