-
Couldn't load subscription status.
- Fork 230
Add OpenAPI V2 to V3 Converter #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,15 +20,14 @@ import ( | |
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "reflect" | ||
| "strings" | ||
|
|
||
| restful "github.com/emicklei/go-restful" | ||
|
|
||
| "k8s.io/kube-openapi/pkg/common" | ||
| "k8s.io/kube-openapi/pkg/common/restfuladapter" | ||
| "k8s.io/kube-openapi/pkg/schemamutation" | ||
| "k8s.io/kube-openapi/pkg/spec3" | ||
| builderutil "k8s.io/kube-openapi/pkg/builder3/util" | ||
| "k8s.io/kube-openapi/pkg/util" | ||
| "k8s.io/kube-openapi/pkg/validation/spec" | ||
| ) | ||
|
|
@@ -38,7 +37,7 @@ const ( | |
| ) | ||
|
|
||
| type openAPI struct { | ||
| config *common.Config | ||
| config *common.OpenAPIV3Config | ||
| spec *spec3.OpenAPI | ||
| definitions map[string]common.OpenAPIDefinition | ||
| } | ||
|
|
@@ -85,6 +84,15 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[ | |
| }, | ||
| }, | ||
| } | ||
| for k, v := range route.Metadata() { | ||
| if strings.HasPrefix(k, common.ExtensionPrefix) { | ||
| if ret.Extensions == nil { | ||
| ret.Extensions = spec.Extensions{} | ||
| } | ||
| ret.Extensions.Add(k, v) | ||
| } | ||
| } | ||
|
|
||
| var err error | ||
| if ret.OperationId, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil { | ||
| return ret, err | ||
|
|
@@ -106,9 +114,16 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[ | |
| } | ||
| } | ||
|
|
||
| // TODO: Default response if needed. Common Response config | ||
| for code, resp := range o.config.CommonResponses { | ||
| if _, exists := ret.Responses.StatusCodeResponses[code]; !exists { | ||
| ret.Responses.StatusCodeResponses[code] = resp | ||
Jefftree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| if len(ret.Responses.StatusCodeResponses) == 0 { | ||
Jefftree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ret.Responses.Default = o.config.DefaultResponse | ||
| } | ||
|
|
||
| ret.Parameters = make([]*spec3.Parameter, 0) | ||
| params := route.Parameters() | ||
| for _, param := range params { | ||
| _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)] | ||
|
|
@@ -121,7 +136,7 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[ | |
| } | ||
| } | ||
|
|
||
| body, err := o.buildRequestBody(params, route.RequestPayloadSample()) | ||
| body, err := o.buildRequestBody(params, route.Consumes(), route.RequestPayloadSample()) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -132,7 +147,7 @@ func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[ | |
| return ret, nil | ||
| } | ||
|
|
||
| func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample interface{}) (*spec3.RequestBody, error) { | ||
| func (o *openAPI) buildRequestBody(parameters []common.Parameter, consumes []string, bodySample interface{}) (*spec3.RequestBody, error) { | ||
| for _, param := range parameters { | ||
| if param.Kind() == common.BodyParameterKind && bodySample != nil { | ||
| schema, err := o.toSchema(util.GetCanonicalTypeName(bodySample)) | ||
|
|
@@ -141,15 +156,16 @@ func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample int | |
| } | ||
| r := &spec3.RequestBody{ | ||
| RequestBodyProps: spec3.RequestBodyProps{ | ||
| Content: map[string]*spec3.MediaType{ | ||
| "application/json": &spec3.MediaType{ | ||
| MediaTypeProps: spec3.MediaTypeProps{ | ||
| Schema: schema, | ||
| }, | ||
| }, | ||
| }, | ||
| Content: map[string]*spec3.MediaType{}, | ||
| }, | ||
| } | ||
| for _, consume := range consumes { | ||
| r.Content[consume] = &spec3.MediaType{ | ||
| MediaTypeProps: spec3.MediaTypeProps{ | ||
| Schema: schema, | ||
| }, | ||
| } | ||
| } | ||
| return r, nil | ||
| } | ||
| } | ||
|
|
@@ -158,7 +174,7 @@ func (o *openAPI) buildRequestBody(parameters []common.Parameter, bodySample int | |
|
|
||
| func newOpenAPI(config *common.Config) openAPI { | ||
| o := openAPI{ | ||
| config: config, | ||
| config: common.ConvertConfigToV3(config), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is I am concerned that over time the OpenAPI v3 config might diverge and no longer be automatically convertible from the Are we able guarantee to our users the the OpenAPI config will always be automatically convertible from the OpenAPI v2 config? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we should have separate configs for OpenAPI V2 and V3. This should apply for structs as well (spec.Info, Schema, etc). I think making that change would be too big given our current timeline, so as a compromise I'm starting to create the V3 config but generating that from the common Config. |
||
| spec: &spec3.OpenAPI{ | ||
| Version: "3.0.0", | ||
| Info: config.Info, | ||
|
|
@@ -170,6 +186,21 @@ func newOpenAPI(config *common.Config) openAPI { | |
| }, | ||
| }, | ||
| } | ||
| if len(o.config.ResponseDefinitions) > 0 { | ||
| o.spec.Components.Responses = make(map[string]*spec3.Response) | ||
|
|
||
| } | ||
| for k, response := range o.config.ResponseDefinitions { | ||
| o.spec.Components.Responses[k] = response | ||
Jefftree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if len(o.config.SecuritySchemes) > 0 { | ||
| o.spec.Components.SecuritySchemes = make(spec3.SecuritySchemes) | ||
|
|
||
| } | ||
| for k, securityScheme := range o.config.SecuritySchemes { | ||
| o.spec.Components.SecuritySchemes[k] = securityScheme | ||
| } | ||
|
|
||
| if o.config.GetOperationIDAndTagsFromRoute == nil { | ||
| // Map the deprecated handler to the common interface, if provided. | ||
|
|
@@ -237,9 +268,7 @@ func (o *openAPI) buildOpenAPISpec(webServices []common.RouteContainer) error { | |
| } | ||
|
|
||
| pathItem = &spec3.Path{ | ||
| PathProps: spec3.PathProps{ | ||
| Parameters: make([]*spec3.Parameter, 0), | ||
| }, | ||
| PathProps: spec3.PathProps{}, | ||
| } | ||
|
|
||
| // 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 { | |
|
|
||
| for _, route := range routes { | ||
| op, _ := o.buildOperations(route, inPathCommonParamsMap) | ||
| sortParameters(op.Parameters) | ||
|
|
||
| switch strings.ToUpper(route.Method()) { | ||
| case "GET": | ||
|
|
@@ -398,7 +428,7 @@ func (o *openAPI) buildDefinitionRecursively(name string) error { | |
| } | ||
| // delete the embedded v2 schema if exists, otherwise no-op | ||
| delete(schema.VendorExtensible.Extensions, common.ExtensionV2Schema) | ||
| schema = wrapRefs(schema) | ||
| schema = builderutil.WrapRefs(schema) | ||
| o.spec.Components.Schemas[uniqueName] = schema | ||
| for _, v := range item.Dependencies { | ||
| if err := o.buildDefinitionRecursively(v); err != nil { | ||
|
|
@@ -439,30 +469,3 @@ func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) { | |
| }, nil | ||
| } | ||
| } | ||
|
|
||
| // wrapRefs wraps OpenAPI V3 Schema refs that contain sibling elements. | ||
| // AllOf is used to wrap the Ref to prevent references from having sibling elements | ||
| // Please see https://github.com/kubernetes/kubernetes/issues/106387#issuecomment-967640388 | ||
| func wrapRefs(schema *spec.Schema) *spec.Schema { | ||
| walker := schemamutation.Walker{ | ||
| SchemaCallback: func(schema *spec.Schema) *spec.Schema { | ||
| orig := schema | ||
| clone := func() { | ||
| if orig == schema { | ||
| schema = new(spec.Schema) | ||
| *schema = *orig | ||
| } | ||
| } | ||
| if schema.Ref.String() != "" && !reflect.DeepEqual(*schema, spec.Schema{SchemaProps: spec.SchemaProps{Ref: schema.Ref}}) { | ||
| clone() | ||
| refSchema := new(spec.Schema) | ||
| refSchema.Ref = schema.Ref | ||
| schema.Ref = spec.Ref{} | ||
| schema.AllOf = []spec.Schema{*refSchema} | ||
| } | ||
| return schema | ||
| }, | ||
| RefCallback: schemamutation.RefCallbackNoop, | ||
| } | ||
| return walker.WalkSchema(schema) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /* | ||
| Copyright 2022 The Kubernetes Authors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package util | ||
|
|
||
| import ( | ||
| "reflect" | ||
|
|
||
| "k8s.io/kube-openapi/pkg/schemamutation" | ||
| "k8s.io/kube-openapi/pkg/validation/spec" | ||
| ) | ||
|
|
||
| // wrapRefs wraps OpenAPI V3 Schema refs that contain sibling elements. | ||
| // AllOf is used to wrap the Ref to prevent references from having sibling elements | ||
| // Please see https://github.com/kubernetes/kubernetes/issues/106387#issuecomment-967640388 | ||
| func WrapRefs(schema *spec.Schema) *spec.Schema { | ||
| walker := schemamutation.Walker{ | ||
| SchemaCallback: func(schema *spec.Schema) *spec.Schema { | ||
| orig := schema | ||
| clone := func() { | ||
| if orig == schema { | ||
| schema = new(spec.Schema) | ||
| *schema = *orig | ||
| } | ||
| } | ||
| if schema.Ref.String() != "" && !reflect.DeepEqual(*schema, spec.Schema{SchemaProps: spec.SchemaProps{Ref: schema.Ref}}) { | ||
| clone() | ||
| refSchema := new(spec.Schema) | ||
| refSchema.Ref = schema.Ref | ||
| schema.Ref = spec.Ref{} | ||
| schema.AllOf = []spec.Schema{*refSchema} | ||
| } | ||
| return schema | ||
| }, | ||
| RefCallback: schemamutation.RefCallbackNoop, | ||
| } | ||
| return walker.WalkSchema(schema) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.