Skip to content

Commit 2cbad14

Browse files
committed
Add OpenAPIConv
1 parent e69a86c commit 2cbad14

File tree

17 files changed

+755
-51
lines changed

17 files changed

+755
-51
lines changed

pkg/builder3/openapi.go

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"net/http"
23-
"reflect"
2423
"strings"
2524

2625
restful "github.com/emicklei/go-restful"
2726

2827
"k8s.io/kube-openapi/pkg/common"
2928
"k8s.io/kube-openapi/pkg/common/restfuladapter"
30-
"k8s.io/kube-openapi/pkg/schemamutation"
3129
"k8s.io/kube-openapi/pkg/spec3"
30+
builderutil "k8s.io/kube-openapi/pkg/builder3/util"
3231
"k8s.io/kube-openapi/pkg/util"
3332
"k8s.io/kube-openapi/pkg/validation/spec"
3433
)
@@ -38,7 +37,7 @@ const (
3837
)
3938

4039
type openAPI struct {
41-
config *common.Config
40+
config *common.OpenAPIV3Config
4241
spec *spec3.OpenAPI
4342
definitions map[string]common.OpenAPIDefinition
4443
}
@@ -85,6 +84,15 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[
8584
},
8685
},
8786
}
87+
for k, v := range route.Metadata() {
88+
if strings.HasPrefix(k, common.ExtensionPrefix) {
89+
if ret.Extensions == nil {
90+
ret.Extensions = spec.Extensions{}
91+
}
92+
ret.Extensions.Add(k, v)
93+
}
94+
}
95+
8896
var err error
8997
if ret.OperationId, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil {
9098
return ret, err
@@ -106,9 +114,16 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[
106114
}
107115
}
108116

109-
// TODO: Default response if needed. Common Response config
117+
for code, resp := range o.config.CommonResponses {
118+
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
119+
ret.Responses.StatusCodeResponses[code] = resp
120+
}
121+
}
122+
123+
if len(ret.Responses.StatusCodeResponses) == 0 {
124+
ret.Responses.Default = o.config.DefaultResponse
125+
}
110126

111-
ret.Parameters = make([]*spec3.Parameter, 0)
112127
params := route.Parameters()
113128
for _, param := range params {
114129
_, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]
@@ -121,7 +136,7 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[
121136
}
122137
}
123138

124-
body, err := o.buildRequestBody(params, route.RequestPayloadSample())
139+
body, err := o.buildRequestBody(params, route.Consumes(), route.RequestPayloadSample())
125140
if err != nil {
126141
return nil, err
127142
}
@@ -132,7 +147,7 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[
132147
return ret, nil
133148
}
134149

135-
func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample interface{}) (*spec3.RequestBody, error) {
150+
func (o *openAPI) buildRequestBody(parameters []common.Parameter, consumes []string, bodySample interface{}) (*spec3.RequestBody, error) {
136151
for _, param := range parameters {
137152
if param.Kind() == common.BodyParameterKind && bodySample != nil {
138153
schema, err := o.toSchema(util.GetCanonicalTypeName(bodySample))
@@ -141,15 +156,16 @@ func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample int
141156
}
142157
r := &spec3.RequestBody{
143158
RequestBodyProps: spec3.RequestBodyProps{
144-
Content: map[string]*spec3.MediaType{
145-
"application/json": &spec3.MediaType{
146-
MediaTypeProps: spec3.MediaTypeProps{
147-
Schema: schema,
148-
},
149-
},
150-
},
159+
Content: map[string]*spec3.MediaType{},
151160
},
152161
}
162+
for _, consume := range consumes {
163+
r.Content[consume] = &spec3.MediaType{
164+
MediaTypeProps: spec3.MediaTypeProps{
165+
Schema: schema,
166+
},
167+
}
168+
}
153169
return r, nil
154170
}
155171
}
@@ -158,7 +174,7 @@ func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample int
158174

159175
func newOpenAPI(config *common.Config) openAPI {
160176
o := openAPI{
161-
config: config,
177+
config: common.ConvertConfigToV3(config),
162178
spec: &spec3.OpenAPI{
163179
Version: "3.0.0",
164180
Info: config.Info,
@@ -170,6 +186,21 @@ func newOpenAPI(config *common.Config) openAPI {
170186
},
171187
},
172188
}
189+
if len(o.config.ResponseDefinitions) > 0 {
190+
o.spec.Components.Responses = make(map[string]*spec3.Response)
191+
192+
}
193+
for k, response := range o.config.ResponseDefinitions {
194+
o.spec.Components.Responses[k] = response
195+
}
196+
197+
if len(o.config.SecuritySchemes) > 0 {
198+
o.spec.Components.SecuritySchemes = make(spec3.SecuritySchemes)
199+
200+
}
201+
for k, securityScheme := range o.config.SecuritySchemes {
202+
o.spec.Components.SecuritySchemes[k] = securityScheme
203+
}
173204

174205
if o.config.GetOperationIDAndTagsFromRoute == nil {
175206
// Map the deprecated handler to the common interface, if provided.
@@ -237,9 +268,7 @@ func (o *openAPI) buildOpenAPISpec(webServices []common.RouteContainer) error {
237268
}
238269

239270
pathItem = &spec3.Path{
240-
PathProps: spec3.PathProps{
241-
Parameters: make([]*spec3.Parameter, 0),
242-
},
271+
PathProps: spec3.PathProps{},
243272
}
244273

245274
// add web services's parameters as well as any parameters appears in all ops, as common parameters
@@ -251,6 +280,7 @@ func (o *openAPI) buildOpenAPISpec(webServices []common.RouteContainer) error {
251280

252281
for _, route := range routes {
253282
op, _ := o.buildOperations(route, inPathCommonParamsMap)
283+
sortParameters(op.Parameters)
254284

255285
switch strings.ToUpper(route.Method()) {
256286
case "GET":
@@ -398,7 +428,7 @@ func (o *openAPI) buildDefinitionRecursively(name string) error {
398428
}
399429
// delete the embedded v2 schema if exists, otherwise no-op
400430
delete(schema.VendorExtensible.Extensions, common.ExtensionV2Schema)
401-
schema = wrapRefs(schema)
431+
schema = builderutil.WrapRefs(schema)
402432
o.spec.Components.Schemas[uniqueName] = schema
403433
for _, v := range item.Dependencies {
404434
if err := o.buildDefinitionRecursively(v); err != nil {
@@ -439,30 +469,3 @@ func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) {
439469
}, nil
440470
}
441471
}
442-
443-
// wrapRefs wraps OpenAPI V3 Schema refs that contain sibling elements.
444-
// AllOf is used to wrap the Ref to prevent references from having sibling elements
445-
// Please see https://github.com/kubernetes/kubernetes/issues/106387#issuecomment-967640388
446-
func wrapRefs(schema *spec.Schema) *spec.Schema {
447-
walker := schemamutation.Walker{
448-
SchemaCallback: func(schema *spec.Schema) *spec.Schema {
449-
orig := schema
450-
clone := func() {
451-
if orig == schema {
452-
schema = new(spec.Schema)
453-
*schema = *orig
454-
}
455-
}
456-
if schema.Ref.String() != "" && !reflect.DeepEqual(*schema, spec.Schema{SchemaProps: spec.SchemaProps{Ref: schema.Ref}}) {
457-
clone()
458-
refSchema := new(spec.Schema)
459-
refSchema.Ref = schema.Ref
460-
schema.Ref = spec.Ref{}
461-
schema.AllOf = []spec.Schema{*refSchema}
462-
}
463-
return schema
464-
},
465-
RefCallback: schemamutation.RefCallbackNoop,
466-
}
467-
return walker.WalkSchema(schema)
468-
}

pkg/builder3/util/util.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 util
18+
19+
import (
20+
"reflect"
21+
22+
"k8s.io/kube-openapi/pkg/schemamutation"
23+
"k8s.io/kube-openapi/pkg/validation/spec"
24+
)
25+
26+
// wrapRefs wraps OpenAPI V3 Schema refs that contain sibling elements.
27+
// AllOf is used to wrap the Ref to prevent references from having sibling elements
28+
// Please see https://github.com/kubernetes/kubernetes/issues/106387#issuecomment-967640388
29+
func WrapRefs(schema *spec.Schema) *spec.Schema {
30+
walker := schemamutation.Walker{
31+
SchemaCallback: func(schema *spec.Schema) *spec.Schema {
32+
orig := schema
33+
clone := func() {
34+
if orig == schema {
35+
schema = new(spec.Schema)
36+
*schema = *orig
37+
}
38+
}
39+
if schema.Ref.String() != "" && !reflect.DeepEqual(*schema, spec.Schema{SchemaProps: spec.SchemaProps{Ref: schema.Ref}}) {
40+
clone()
41+
refSchema := new(spec.Schema)
42+
refSchema.Ref = schema.Ref
43+
schema.Ref = spec.Ref{}
44+
schema.AllOf = []spec.Schema{*refSchema}
45+
}
46+
return schema
47+
},
48+
RefCallback: schemamutation.RefCallbackNoop,
49+
}
50+
return walker.WalkSchema(schema)
51+
}

pkg/common/common.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222

2323
"github.com/emicklei/go-restful"
2424

25+
"k8s.io/kube-openapi/pkg/openapiconv"
26+
"k8s.io/kube-openapi/pkg/spec3"
2527
"k8s.io/kube-openapi/pkg/validation/spec"
2628
)
2729

@@ -117,6 +119,86 @@ type Config struct {
117119
DefaultSecurity []map[string][]string
118120
}
119121

122+
// OpenAPIV3Config is set of configuration for OpenAPI V3 spec generation.
123+
type OpenAPIV3Config struct {
124+
// Info is general information about the API.
125+
Info *spec.Info
126+
127+
// DefaultResponse will be used if an operation does not have any responses listed. It
128+
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
129+
DefaultResponse *spec3.Response
130+
131+
// ResponseDefinitions will be added to responses component. This is an object
132+
// that holds responses that can be used across operations.
133+
ResponseDefinitions map[string]*spec3.Response
134+
135+
// CommonResponses will be added as a response to all operation specs. This is a good place to add common
136+
// responses such as authorization failed.
137+
CommonResponses map[int]*spec3.Response
138+
139+
// List of webservice's path prefixes to ignore
140+
IgnorePrefixes []string
141+
142+
// OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map
143+
// or any of the models will result in spec generation failure.
144+
GetDefinitions GetOpenAPIDefinitions
145+
146+
// GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs.
147+
//
148+
// Deprecated: GetOperationIDAndTagsFromRoute should be used instead. This cannot be specified if using the new Route
149+
// interface set of funcs.
150+
GetOperationIDAndTags func(r *restful.Route) (string, []string, error)
151+
152+
// GetOperationIDAndTagsFromRoute returns operation id and tags for a Route. It is an optional function to customize operation IDs.
153+
GetOperationIDAndTagsFromRoute func(r Route) (string, []string, error)
154+
155+
// GetDefinitionName returns a friendly name for a definition base on the serving path. parameter `name` is the full name of the definition.
156+
// It is an optional function to customize model names.
157+
GetDefinitionName func(name string) (string, spec.Extensions)
158+
159+
// SecuritySchemes is list of all security schemes for OpenAPI service.
160+
SecuritySchemes spec3.SecuritySchemes
161+
162+
// DefaultSecurity for all operations.
163+
DefaultSecurity []map[string][]string
164+
}
165+
166+
// ConvertConfigToV3 converts a Config object to an OpenAPIV3Config object
167+
func ConvertConfigToV3(config *Config) *OpenAPIV3Config {
168+
if config == nil {
169+
return nil
170+
}
171+
172+
v3Config := &OpenAPIV3Config{
173+
Info: config.Info,
174+
IgnorePrefixes: config.IgnorePrefixes,
175+
GetDefinitions: config.GetDefinitions,
176+
GetOperationIDAndTags: config.GetOperationIDAndTags,
177+
GetOperationIDAndTagsFromRoute: config.GetOperationIDAndTagsFromRoute,
178+
GetDefinitionName: config.GetDefinitionName,
179+
SecuritySchemes: make(spec3.SecuritySchemes),
180+
DefaultSecurity: config.DefaultSecurity,
181+
DefaultResponse: openapiconv.ConvertResponse(config.DefaultResponse, []string{"application/json"}),
182+
183+
CommonResponses: make(map[int]*spec3.Response),
184+
ResponseDefinitions: make(map[string]*spec3.Response),
185+
}
186+
187+
if config.SecurityDefinitions != nil {
188+
for s, securityScheme := range *config.SecurityDefinitions {
189+
v3Config.SecuritySchemes[s] = openapiconv.ConvertSecurityScheme(securityScheme)
190+
}
191+
}
192+
for k, commonResponse := range config.CommonResponses {
193+
v3Config.CommonResponses[k] = openapiconv.ConvertResponse(&commonResponse, []string{"application/json"})
194+
}
195+
196+
for k, responseDefinition := range config.ResponseDefinitions {
197+
v3Config.ResponseDefinitions[k] = openapiconv.ConvertResponse(&responseDefinition, []string{"application/json"})
198+
}
199+
return v3Config
200+
}
201+
120202
type typeInfo struct {
121203
name string
122204
format string

0 commit comments

Comments
 (0)