Make Kubernetes OpenAPI operation IDs unique

This commit is contained in:
mbohlool
2016-10-08 02:36:37 -07:00
parent 221a620a14
commit 5ba06cf2bc
12 changed files with 244 additions and 153 deletions

View File

@@ -26,47 +26,26 @@ import (
"github.com/go-openapi/spec"
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
"k8s.io/kubernetes/pkg/util/json"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/json"
)
const (
OpenAPIVersion = "2.0"
)
// Config is set of configuration for openAPI spec generation.
type Config struct {
// Path to the spec file. by convention, it should name [.*/]*/swagger.json
OpenAPIServePath string
// List of web services for this API spec
WebServices []*restful.WebService
// List of supported protocols such as https, http, etc.
ProtocolList []string
// Info is general information about the API.
Info *spec.Info
// DefaultResponse will be used if an operation does not have any responses listed. It
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
DefaultResponse *spec.Response
// List of webservice's path prefixes to ignore
IgnorePrefixes []string
// OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map
// or any of the models will result in spec generation failure.
OpenAPIDefinitions *common.OpenAPIDefinitions
}
type openAPI struct {
config *Config
config *common.Config
swagger *spec.Swagger
protocolList []string
servePath string
}
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
func RegisterOpenAPIService(config *Config, containers *restful.Container) (err error) {
func RegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *common.Config, containers *restful.Container) (err error) {
o := openAPI{
config: config,
config: config,
servePath: servePath,
swagger: &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Swagger: OpenAPIVersion,
@@ -77,14 +56,14 @@ func RegisterOpenAPIService(config *Config, containers *restful.Container) (err
},
}
err = o.init()
err = o.init(webServices)
if err != nil {
return err
}
containers.ServeMux.HandleFunc(config.OpenAPIServePath, func(w http.ResponseWriter, r *http.Request) {
containers.ServeMux.HandleFunc(servePath, func(w http.ResponseWriter, r *http.Request) {
resp := restful.NewResponse(w)
if r.URL.Path != config.OpenAPIServePath {
if r.URL.Path != servePath {
resp.WriteErrorString(http.StatusNotFound, "Path not found!")
}
// TODO: we can cache json string and return it here.
@@ -93,8 +72,13 @@ func RegisterOpenAPIService(config *Config, containers *restful.Container) (err
return nil
}
func (o *openAPI) init() error {
err := o.buildPaths()
func (o *openAPI) init(webServices []*restful.WebService) error {
if o.config.GetOperationID == nil {
o.config.GetOperationID = func(_ string, r *restful.Route) (string, error) {
return r.Operation, nil
}
}
err := o.buildPaths(webServices)
if err != nil {
return err
}
@@ -105,7 +89,7 @@ func (o *openAPI) buildDefinitionRecursively(name string) error {
if _, ok := o.swagger.Definitions[name]; ok {
return nil
}
if item, ok := (*o.config.OpenAPIDefinitions)[name]; ok {
if item, ok := (*o.config.Definitions)[name]; ok {
o.swagger.Definitions[name] = item.Schema
for _, v := range item.Dependencies {
if err := o.buildDefinitionRecursively(v); err != nil {
@@ -134,20 +118,10 @@ func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
}
// buildPaths builds OpenAPI paths using go-restful's web services.
func (o *openAPI) buildPaths() error {
func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
pathsToIgnore := util.CreateTrie(o.config.IgnorePrefixes)
duplicateOpId := make(map[string]bool)
// Find duplicate operation IDs.
for _, service := range o.config.WebServices {
if pathsToIgnore.HasPrefix(service.RootPath()) {
continue
}
for _, route := range service.Routes() {
_, exists := duplicateOpId[route.Operation]
duplicateOpId[route.Operation] = exists
}
}
for _, w := range o.config.WebServices {
duplicateOpId := make(map[string]string)
for _, w := range webServices {
rootPath := w.RootPath()
if pathsToIgnore.HasPrefix(rootPath) {
continue
@@ -191,11 +165,11 @@ func (o *openAPI) buildPaths() error {
if err != nil {
return err
}
if duplicateOpId[op.ID] {
// Repeated Operation IDs are not allowed in OpenAPI spec but if
// an OperationID is empty, client generators will infer the ID
// from the path and method of operation.
op.ID = ""
dpath, exists := duplicateOpId[op.ID]
if exists {
return fmt.Errorf("Duplicate Operation ID %v for path %v and %v.", op.ID, dpath, path)
} else {
duplicateOpId[op.ID] = path
}
switch strings.ToUpper(route.Method) {
case "GET":
@@ -227,7 +201,6 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
Description: route.Doc,
Consumes: route.Consumes,
Produces: route.Produces,
ID: route.Operation,
Schemes: o.config.ProtocolList,
Responses: &spec.Responses{
ResponsesProps: spec.ResponsesProps{
@@ -236,6 +209,9 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
},
},
}
if ret.ID, err = o.config.GetOperationID(o.servePath, &route); err != nil {
return ret, err
}
// Build responses
for _, resp := range route.ResponseErrors {