Update main repo references to new kube-openapi repo
Kubernetes-commit: 400b77b48f972b1e10854980586559d5852088c7
This commit is contained in:
parent
72a8a7817c
commit
e671fe20d7
|
|
@ -30,10 +30,10 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/util/trie"
|
"k8s.io/kube-openapi/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var verbs = trie.New([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
|
var verbs = util.NewTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
|
||||||
|
|
||||||
const (
|
const (
|
||||||
extensionGVK = "x-kubernetes-group-version-kind"
|
extensionGVK = "x-kubernetes-group-version-kind"
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
|
|
||||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
@ -61,6 +60,7 @@ import (
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
|
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apimachinery"
|
"k8s.io/apimachinery/pkg/apimachinery"
|
||||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
|
@ -44,6 +43,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
"k8s.io/apiserver/pkg/server/routes"
|
"k8s.io/apiserver/pkg/server/routes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info about an API group.
|
// Info about an API group.
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/apimachinery"
|
"k8s.io/apimachinery/pkg/apimachinery"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
|
@ -51,6 +50,7 @@ import (
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
reviewers:
|
|
||||||
- yujuhong
|
|
||||||
- gmarek
|
|
||||||
- mbohlool
|
|
||||||
- philips
|
|
||||||
approvers:
|
|
||||||
- mbohlool
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi contains code to generate OpenAPI discovery spec (which
|
|
||||||
// initial version of it also known as Swagger 2.0).
|
|
||||||
// For more details: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
|
|
||||||
package openapi
|
|
||||||
|
|
@ -1,519 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
"github.com/googleapis/gnostic/compiler"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
genericmux "k8s.io/apiserver/pkg/server/mux"
|
|
||||||
"k8s.io/apiserver/pkg/util/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
OpenAPIVersion = "2.0"
|
|
||||||
extensionPrefix = "x-kubernetes-"
|
|
||||||
|
|
||||||
JSON_EXT = ".json"
|
|
||||||
|
|
||||||
MIME_JSON = "application/json"
|
|
||||||
// TODO(mehdy): change @68f4ded to a version tag when gnostic add version tags.
|
|
||||||
MIME_PB = "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf"
|
|
||||||
MIME_PB_GZ = "application/x-gzip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type openAPI struct {
|
|
||||||
config *openapi.Config
|
|
||||||
swagger *spec.Swagger
|
|
||||||
swaggerBytes []byte
|
|
||||||
swaggerPb []byte
|
|
||||||
swaggerPbGz []byte
|
|
||||||
lastModified time.Time
|
|
||||||
protocolList []string
|
|
||||||
definitions map[string]openapi.OpenAPIDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
func computeEtag(data []byte) string {
|
|
||||||
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
|
|
||||||
func RegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *openapi.Config, mux *genericmux.PathRecorderMux) (err error) {
|
|
||||||
|
|
||||||
if !strings.HasSuffix(servePath, JSON_EXT) {
|
|
||||||
return fmt.Errorf("Serving path must ends with \"%s\".", JSON_EXT)
|
|
||||||
}
|
|
||||||
|
|
||||||
servePathBase := servePath[:len(servePath)-len(JSON_EXT)]
|
|
||||||
|
|
||||||
o := openAPI{
|
|
||||||
config: config,
|
|
||||||
swagger: &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Swagger: OpenAPIVersion,
|
|
||||||
Definitions: spec.Definitions{},
|
|
||||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
|
||||||
Info: config.Info,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.init(webServices)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mime.AddExtensionType(".json", MIME_JSON)
|
|
||||||
mime.AddExtensionType(".pb-v1", MIME_PB)
|
|
||||||
mime.AddExtensionType(".gz", MIME_PB_GZ)
|
|
||||||
|
|
||||||
type fileInfo struct {
|
|
||||||
ext string
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []fileInfo{
|
|
||||||
{".json", o.swaggerBytes},
|
|
||||||
{"-2.0.0.json", o.swaggerBytes},
|
|
||||||
{"-2.0.0.pb-v1", o.swaggerPb},
|
|
||||||
{"-2.0.0.pb-v1.gz", o.swaggerPbGz},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
path := servePathBase + file.ext
|
|
||||||
data := file.data
|
|
||||||
etag := computeEtag(file.data)
|
|
||||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != path {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
w.Write([]byte("Path not found!"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Etag", etag)
|
|
||||||
// ServeContent will take care of caching using eTag.
|
|
||||||
http.ServeContent(w, r, path, o.lastModified, bytes.NewReader(data))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) init(webServices []*restful.WebService) error {
|
|
||||||
if o.config.GetOperationIDAndTags == nil {
|
|
||||||
o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) {
|
|
||||||
return r.Operation, nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if o.config.GetDefinitionName == nil {
|
|
||||||
o.config.GetDefinitionName = func(name string) (string, spec.Extensions) {
|
|
||||||
return name[strings.LastIndex(name, "/")+1:], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
|
|
||||||
defName, _ := o.config.GetDefinitionName(name)
|
|
||||||
return spec.MustCreateRef("#/definitions/" + openapi.EscapeJsonPointer(defName))
|
|
||||||
})
|
|
||||||
if o.config.CommonResponses == nil {
|
|
||||||
o.config.CommonResponses = map[int]spec.Response{}
|
|
||||||
}
|
|
||||||
err := o.buildPaths(webServices)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if o.config.SecurityDefinitions != nil {
|
|
||||||
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
|
|
||||||
o.swagger.Security = o.config.DefaultSecurity
|
|
||||||
}
|
|
||||||
if o.config.PostProcessSpec != nil {
|
|
||||||
o.swagger, err = o.config.PostProcessSpec(o.swagger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
o.swaggerBytes, err = json.MarshalIndent(o.swagger, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.swaggerPb, err = toProtoBinary(o.swaggerBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.swaggerPbGz = toGzip(o.swaggerPb)
|
|
||||||
o.lastModified = time.Now()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toProtoBinary(spec []byte) ([]byte, error) {
|
|
||||||
var info yaml.MapSlice
|
|
||||||
err := yaml.Unmarshal(spec, &info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
document, err := openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return proto.Marshal(document)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toGzip(data []byte) []byte {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
zw := gzip.NewWriter(&buf)
|
|
||||||
zw.Write(data)
|
|
||||||
zw.Close()
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCanonicalizeTypeName(t reflect.Type) string {
|
|
||||||
if t.PkgPath() == "" {
|
|
||||||
return t.Name()
|
|
||||||
}
|
|
||||||
path := t.PkgPath()
|
|
||||||
if strings.Contains(path, "/vendor/") {
|
|
||||||
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
|
|
||||||
}
|
|
||||||
return path + "." + t.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildDefinitionRecursively(name string) error {
|
|
||||||
uniqueName, extensions := o.config.GetDefinitionName(name)
|
|
||||||
if _, ok := o.swagger.Definitions[uniqueName]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if item, ok := o.definitions[name]; ok {
|
|
||||||
schema := spec.Schema{
|
|
||||||
VendorExtensible: item.Schema.VendorExtensible,
|
|
||||||
SchemaProps: item.Schema.SchemaProps,
|
|
||||||
SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
|
|
||||||
}
|
|
||||||
if extensions != nil {
|
|
||||||
if schema.Extensions == nil {
|
|
||||||
schema.Extensions = spec.Extensions{}
|
|
||||||
}
|
|
||||||
for k, v := range extensions {
|
|
||||||
schema.Extensions[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.swagger.Definitions[uniqueName] = schema
|
|
||||||
for _, v := range item.Dependencies {
|
|
||||||
if err := o.buildDefinitionRecursively(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again.", name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefinitionForType build a definition for a given type and return a referable name to it's definition.
|
|
||||||
// This is the main function that keep track of definitions used in this spec and is depend on code generated
|
|
||||||
// by k8s.io/kube-gen/cmd/openapi-gen.
|
|
||||||
func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
|
|
||||||
t := reflect.TypeOf(sample)
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
name := getCanonicalizeTypeName(t)
|
|
||||||
if err := o.buildDefinitionRecursively(name); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defName, _ := o.config.GetDefinitionName(name)
|
|
||||||
return "#/definitions/" + openapi.EscapeJsonPointer(defName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildPaths builds OpenAPI paths using go-restful's web services.
|
|
||||||
func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
|
|
||||||
pathsToIgnore := trie.New(o.config.IgnorePrefixes)
|
|
||||||
duplicateOpId := make(map[string]string)
|
|
||||||
for _, w := range webServices {
|
|
||||||
rootPath := w.RootPath()
|
|
||||||
if pathsToIgnore.HasPrefix(rootPath) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
commonParams, err := o.buildParameters(w.PathParameters())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for path, routes := range groupRoutesByPath(w.Routes()) {
|
|
||||||
// go-swagger has special variable definition {$NAME:*} that can only be
|
|
||||||
// used at the end of the path and it is not recognized by OpenAPI.
|
|
||||||
if strings.HasSuffix(path, ":*}") {
|
|
||||||
path = path[:len(path)-3] + "}"
|
|
||||||
}
|
|
||||||
if pathsToIgnore.HasPrefix(path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Aggregating common parameters make API spec (and generated clients) simpler
|
|
||||||
inPathCommonParamsMap, err := o.findCommonParameters(routes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pathItem, exists := o.swagger.Paths.Paths[path]
|
|
||||||
if exists {
|
|
||||||
return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
|
|
||||||
}
|
|
||||||
pathItem = spec.PathItem{
|
|
||||||
PathItemProps: spec.PathItemProps{
|
|
||||||
Parameters: make([]spec.Parameter, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// add web services's parameters as well as any parameters appears in all ops, as common parameters
|
|
||||||
pathItem.Parameters = append(pathItem.Parameters, commonParams...)
|
|
||||||
for _, p := range inPathCommonParamsMap {
|
|
||||||
pathItem.Parameters = append(pathItem.Parameters, p)
|
|
||||||
}
|
|
||||||
sortParameters(pathItem.Parameters)
|
|
||||||
for _, route := range routes {
|
|
||||||
op, err := o.buildOperations(route, inPathCommonParamsMap)
|
|
||||||
sortParameters(op.Parameters)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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":
|
|
||||||
pathItem.Get = op
|
|
||||||
case "POST":
|
|
||||||
pathItem.Post = op
|
|
||||||
case "HEAD":
|
|
||||||
pathItem.Head = op
|
|
||||||
case "PUT":
|
|
||||||
pathItem.Put = op
|
|
||||||
case "DELETE":
|
|
||||||
pathItem.Delete = op
|
|
||||||
case "OPTIONS":
|
|
||||||
pathItem.Options = op
|
|
||||||
case "PATCH":
|
|
||||||
pathItem.Patch = op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.swagger.Paths.Paths[path] = pathItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildOperations builds operations for each webservice path
|
|
||||||
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
|
|
||||||
ret = &spec.Operation{
|
|
||||||
OperationProps: spec.OperationProps{
|
|
||||||
Description: route.Doc,
|
|
||||||
Consumes: route.Consumes,
|
|
||||||
Produces: route.Produces,
|
|
||||||
Schemes: o.config.ProtocolList,
|
|
||||||
Responses: &spec.Responses{
|
|
||||||
ResponsesProps: spec.ResponsesProps{
|
|
||||||
StatusCodeResponses: make(map[int]spec.Response),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for k, v := range route.Metadata {
|
|
||||||
if strings.HasPrefix(k, extensionPrefix) {
|
|
||||||
if ret.Extensions == nil {
|
|
||||||
ret.Extensions = spec.Extensions{}
|
|
||||||
}
|
|
||||||
ret.Extensions.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTags(&route); err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build responses
|
|
||||||
for _, resp := range route.ResponseErrors {
|
|
||||||
ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there is no response but a write sample, assume that write sample is an http.StatusOK response.
|
|
||||||
if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil {
|
|
||||||
ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK")
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for code, resp := range o.config.CommonResponses {
|
|
||||||
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
|
|
||||||
ret.Responses.StatusCodeResponses[code] = resp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there is still no response, use default response provided.
|
|
||||||
if len(ret.Responses.StatusCodeResponses) == 0 {
|
|
||||||
ret.Responses.Default = o.config.DefaultResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build non-common Parameters
|
|
||||||
ret.Parameters = make([]spec.Parameter, 0)
|
|
||||||
for _, param := range route.ParameterDocs {
|
|
||||||
if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
|
|
||||||
openAPIParam, err := o.buildParameter(param.Data(), route.ReadSample)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
ret.Parameters = append(ret.Parameters, openAPIParam)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
|
|
||||||
schema, err := o.toSchema(model)
|
|
||||||
if err != nil {
|
|
||||||
return spec.Response{}, err
|
|
||||||
}
|
|
||||||
return spec.Response{
|
|
||||||
ResponseProps: spec.ResponseProps{
|
|
||||||
Description: description,
|
|
||||||
Schema: schema,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
|
|
||||||
commonParamsMap := make(map[interface{}]spec.Parameter, 0)
|
|
||||||
paramOpsCountByName := make(map[interface{}]int, 0)
|
|
||||||
paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
|
|
||||||
for _, route := range routes {
|
|
||||||
routeParamDuplicateMap := make(map[interface{}]bool)
|
|
||||||
s := ""
|
|
||||||
for _, param := range route.ParameterDocs {
|
|
||||||
m, _ := json.Marshal(param.Data())
|
|
||||||
s += string(m) + "\n"
|
|
||||||
key := mapKeyFromParam(param)
|
|
||||||
if routeParamDuplicateMap[key] {
|
|
||||||
msg, _ := json.Marshal(route.ParameterDocs)
|
|
||||||
return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v.", param.Data().Name, string(msg), s)
|
|
||||||
}
|
|
||||||
routeParamDuplicateMap[key] = true
|
|
||||||
paramOpsCountByName[key]++
|
|
||||||
paramNameKindToDataMap[key] = param.Data()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for key, count := range paramOpsCountByName {
|
|
||||||
paramData := paramNameKindToDataMap[key]
|
|
||||||
if count == len(routes) && paramData.Kind != restful.BodyParameterKind {
|
|
||||||
openAPIParam, err := o.buildParameter(paramData, nil)
|
|
||||||
if err != nil {
|
|
||||||
return commonParamsMap, err
|
|
||||||
}
|
|
||||||
commonParamsMap[key] = openAPIParam
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commonParamsMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) toSchema(model interface{}) (_ *spec.Schema, err error) {
|
|
||||||
if openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(getCanonicalizeTypeName(reflect.TypeOf(model))); openAPIType != "" {
|
|
||||||
return &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{openAPIType},
|
|
||||||
Format: openAPIFormat,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
ref, err := o.buildDefinitionForType(model)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Ref: spec.MustCreateRef(ref),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample interface{}) (ret spec.Parameter, err error) {
|
|
||||||
ret = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: restParam.Name,
|
|
||||||
Description: restParam.Description,
|
|
||||||
Required: restParam.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
switch restParam.Kind {
|
|
||||||
case restful.BodyParameterKind:
|
|
||||||
if bodySample != nil {
|
|
||||||
ret.In = "body"
|
|
||||||
ret.Schema, err = o.toSchema(bodySample)
|
|
||||||
return ret, err
|
|
||||||
} else {
|
|
||||||
// There is not enough information in the body parameter to build the definition.
|
|
||||||
// Body parameter has a data type that is a short name but we need full package name
|
|
||||||
// of the type to create a definition.
|
|
||||||
return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType)
|
|
||||||
}
|
|
||||||
case restful.PathParameterKind:
|
|
||||||
ret.In = "path"
|
|
||||||
if !restParam.Required {
|
|
||||||
return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
|
|
||||||
}
|
|
||||||
case restful.QueryParameterKind:
|
|
||||||
ret.In = "query"
|
|
||||||
case restful.HeaderParameterKind:
|
|
||||||
ret.In = "header"
|
|
||||||
case restful.FormParameterKind:
|
|
||||||
ret.In = "formData"
|
|
||||||
default:
|
|
||||||
return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind)
|
|
||||||
}
|
|
||||||
openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(restParam.DataType)
|
|
||||||
if openAPIType == "" {
|
|
||||||
return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType)
|
|
||||||
}
|
|
||||||
ret.Type = openAPIType
|
|
||||||
ret.Format = openAPIFormat
|
|
||||||
ret.UniqueItems = !restParam.AllowMultiple
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
|
|
||||||
ret = make([]spec.Parameter, len(restParam))
|
|
||||||
for i, v := range restParam {
|
|
||||||
ret[i], err = o.buildParameter(v.Data(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,465 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// setUp is a convenience function for setting up for (most) tests.
|
|
||||||
func setUp(t *testing.T, fullMethods bool) (openAPI, *restful.Container, *assert.Assertions) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
config, container := getConfig(fullMethods)
|
|
||||||
return openAPI{
|
|
||||||
config: config,
|
|
||||||
swagger: &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Swagger: OpenAPIVersion,
|
|
||||||
Definitions: spec.Definitions{},
|
|
||||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
|
||||||
Info: config.Info,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, container, assert
|
|
||||||
}
|
|
||||||
|
|
||||||
func noOp(request *restful.Request, response *restful.Response) {}
|
|
||||||
|
|
||||||
// Test input
|
|
||||||
type TestInput struct {
|
|
||||||
// Name of the input
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
// ID of the input
|
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Tags []string `json:"tags,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test output
|
|
||||||
type TestOutput struct {
|
|
||||||
// Name of the output
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
// Number of outputs
|
|
||||||
Count int `json:"count,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ TestInput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
|
||||||
schema := spec.Schema{}
|
|
||||||
schema.Description = "Test input"
|
|
||||||
schema.Properties = map[string]spec.Schema{
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the input",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "ID of the input",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "",
|
|
||||||
Type: []string{"array"},
|
|
||||||
Items: &spec.SchemaOrArray{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
schema.Extensions = spec.Extensions{"x-test": "test"}
|
|
||||||
return &openapi.OpenAPIDefinition{
|
|
||||||
Schema: schema,
|
|
||||||
Dependencies: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ TestOutput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
|
||||||
schema := spec.Schema{}
|
|
||||||
schema.Description = "Test output"
|
|
||||||
schema.Properties = map[string]spec.Schema{
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the output",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"count": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Number of outputs",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &openapi.OpenAPIDefinition{
|
|
||||||
Schema: schema,
|
|
||||||
Dependencies: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ openapi.OpenAPIDefinitionGetter = TestInput{}
|
|
||||||
var _ openapi.OpenAPIDefinitionGetter = TestOutput{}
|
|
||||||
|
|
||||||
func getTestRoute(ws *restful.WebService, method string, additionalParams bool, opPrefix string) *restful.RouteBuilder {
|
|
||||||
ret := ws.Method(method).
|
|
||||||
Path("/test/{path:*}").
|
|
||||||
Doc(fmt.Sprintf("%s test input", method)).
|
|
||||||
Operation(fmt.Sprintf("%s%sTestInput", method, opPrefix)).
|
|
||||||
Produces(restful.MIME_JSON).
|
|
||||||
Consumes(restful.MIME_JSON).
|
|
||||||
Param(ws.PathParameter("path", "path to the resource").DataType("string")).
|
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
|
||||||
Reads(TestInput{}).
|
|
||||||
Returns(200, "OK", TestOutput{}).
|
|
||||||
Writes(TestOutput{}).
|
|
||||||
To(noOp)
|
|
||||||
if additionalParams {
|
|
||||||
ret.Param(ws.HeaderParameter("hparam", "a test head parameter").DataType("integer"))
|
|
||||||
ret.Param(ws.FormParameter("fparam", "a test form parameter").DataType("number"))
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfig(fullMethods bool) (*openapi.Config, *restful.Container) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
container := restful.NewContainer()
|
|
||||||
container.ServeMux = mux
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path("/foo")
|
|
||||||
ws.Route(getTestRoute(ws, "get", true, "foo"))
|
|
||||||
if fullMethods {
|
|
||||||
ws.Route(getTestRoute(ws, "post", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "put", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "head", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "patch", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "options", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "delete", false, "foo"))
|
|
||||||
|
|
||||||
}
|
|
||||||
ws.Path("/bar")
|
|
||||||
ws.Route(getTestRoute(ws, "get", true, "bar"))
|
|
||||||
if fullMethods {
|
|
||||||
ws.Route(getTestRoute(ws, "post", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "put", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "head", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "patch", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "options", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "delete", false, "bar"))
|
|
||||||
|
|
||||||
}
|
|
||||||
container.Add(ws)
|
|
||||||
return &openapi.Config{
|
|
||||||
ProtocolList: []string{"https"},
|
|
||||||
Info: &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "TestAPI",
|
|
||||||
Description: "Test API",
|
|
||||||
Version: "unversioned",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GetDefinitions: func(_ openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
|
|
||||||
return map[string]openapi.OpenAPIDefinition{
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
|
||||||
// Bazel changes the package name, this is ok for testing, but we need to fix it if it happened
|
|
||||||
// in the main code.
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi/go_default_test.TestInput": *TestInput{}.OpenAPIDefinition(),
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi/go_default_test.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GetDefinitionName: func(name string) (string, spec.Extensions) {
|
|
||||||
friendlyName := name[strings.LastIndex(name, "/")+1:]
|
|
||||||
if strings.HasPrefix(friendlyName, "go_default_test") {
|
|
||||||
friendlyName = "openapi" + friendlyName[len("go_default_test"):]
|
|
||||||
}
|
|
||||||
return friendlyName, spec.Extensions{"x-test2": "test2"}
|
|
||||||
},
|
|
||||||
}, container
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestOperation(method string, opPrefix string) *spec.Operation {
|
|
||||||
return &spec.Operation{
|
|
||||||
OperationProps: spec.OperationProps{
|
|
||||||
Description: fmt.Sprintf("%s test input", method),
|
|
||||||
Consumes: []string{"application/json"},
|
|
||||||
Produces: []string{"application/json"},
|
|
||||||
Schemes: []string{"https"},
|
|
||||||
Parameters: []spec.Parameter{},
|
|
||||||
Responses: getTestResponses(),
|
|
||||||
ID: fmt.Sprintf("%s%sTestInput", method, opPrefix),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem {
|
|
||||||
ret := spec.PathItem{
|
|
||||||
PathItemProps: spec.PathItemProps{
|
|
||||||
Get: getTestOperation("get", opPrefix),
|
|
||||||
Parameters: getTestCommonParameters(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret.Get.Parameters = getAdditionalTestParameters()
|
|
||||||
if allMethods {
|
|
||||||
ret.Put = getTestOperation("put", opPrefix)
|
|
||||||
ret.Put.Parameters = getTestParameters()
|
|
||||||
ret.Post = getTestOperation("post", opPrefix)
|
|
||||||
ret.Post.Parameters = getTestParameters()
|
|
||||||
ret.Head = getTestOperation("head", opPrefix)
|
|
||||||
ret.Head.Parameters = getTestParameters()
|
|
||||||
ret.Patch = getTestOperation("patch", opPrefix)
|
|
||||||
ret.Patch.Parameters = getTestParameters()
|
|
||||||
ret.Delete = getTestOperation("delete", opPrefix)
|
|
||||||
ret.Delete.Parameters = getTestParameters()
|
|
||||||
ret.Options = getTestOperation("options", opPrefix)
|
|
||||||
ret.Options.Parameters = getTestParameters()
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRefSchema(ref string) *spec.Schema {
|
|
||||||
return &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Ref: spec.MustCreateRef(ref),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestResponses() *spec.Responses {
|
|
||||||
ret := spec.Responses{
|
|
||||||
ResponsesProps: spec.ResponsesProps{
|
|
||||||
StatusCodeResponses: map[int]spec.Response{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret.StatusCodeResponses[200] = spec.Response{
|
|
||||||
ResponseProps: spec.ResponseProps{
|
|
||||||
Description: "OK",
|
|
||||||
Schema: getRefSchema("#/definitions/openapi.TestOutput"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestCommonParameters() []spec.Parameter {
|
|
||||||
ret := make([]spec.Parameter, 2)
|
|
||||||
ret[0] = spec.Parameter{
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Description: "path to the resource",
|
|
||||||
Name: "path",
|
|
||||||
In: "path",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret[1] = spec.Parameter{
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Description: "If 'true', then the output is pretty printed.",
|
|
||||||
Name: "pretty",
|
|
||||||
In: "query",
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestParameters() []spec.Parameter {
|
|
||||||
ret := make([]spec.Parameter, 1)
|
|
||||||
ret[0] = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: "body",
|
|
||||||
In: "body",
|
|
||||||
Required: true,
|
|
||||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAdditionalTestParameters() []spec.Parameter {
|
|
||||||
ret := make([]spec.Parameter, 3)
|
|
||||||
ret[0] = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: "body",
|
|
||||||
In: "body",
|
|
||||||
Required: true,
|
|
||||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret[1] = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: "fparam",
|
|
||||||
Description: "a test form parameter",
|
|
||||||
In: "formData",
|
|
||||||
},
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "number",
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret[2] = spec.Parameter{
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "integer",
|
|
||||||
},
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Description: "a test head parameter",
|
|
||||||
Name: "hparam",
|
|
||||||
In: "header",
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestInputDefinition() spec.Schema {
|
|
||||||
return spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Test input",
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"id": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "ID of the input",
|
|
||||||
Type: spec.StringOrArray{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the input",
|
|
||||||
Type: spec.StringOrArray{"string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: spec.StringOrArray{"array"},
|
|
||||||
Items: &spec.SchemaOrArray{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: spec.StringOrArray{"string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-test": "test",
|
|
||||||
"x-test2": "test2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestOutputDefinition() spec.Schema {
|
|
||||||
return spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Test output",
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"count": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Number of outputs",
|
|
||||||
Type: spec.StringOrArray{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the output",
|
|
||||||
Type: spec.StringOrArray{"string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-test2": "test2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildSwaggerSpec(t *testing.T) {
|
|
||||||
o, container, assert := setUp(t, true)
|
|
||||||
expected := &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Info: &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "TestAPI",
|
|
||||||
Description: "Test API",
|
|
||||||
Version: "unversioned",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Swagger: "2.0",
|
|
||||||
Paths: &spec.Paths{
|
|
||||||
Paths: map[string]spec.PathItem{
|
|
||||||
"/foo/test/{path}": getTestPathItem(true, "foo"),
|
|
||||||
"/bar/test/{path}": getTestPathItem(true, "bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Definitions: spec.Definitions{
|
|
||||||
"openapi.TestInput": getTestInputDefinition(),
|
|
||||||
"openapi.TestOutput": getTestOutputDefinition(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := o.init(container.RegisteredWebServices())
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expected_json, err := json.Marshal(expected)
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
actual_json, err := json.Marshal(o.swagger)
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(string(expected_json), string(actual_json))
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type parameters []spec.Parameter
|
|
||||||
|
|
||||||
func (s parameters) Len() int { return len(s) }
|
|
||||||
func (s parameters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
|
|
||||||
// byNameIn used in sorting parameters by Name and In fields.
|
|
||||||
type byNameIn struct {
|
|
||||||
parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s byNameIn) Less(i, j int) bool {
|
|
||||||
return s.parameters[i].Name < s.parameters[j].Name || (s.parameters[i].Name == s.parameters[j].Name && s.parameters[i].In < s.parameters[j].In)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortParameters sorts parameters by Name and In fields.
|
|
||||||
func sortParameters(p []spec.Parameter) {
|
|
||||||
sort.Sort(byNameIn{p})
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route {
|
|
||||||
pathToRoutes := make(map[string][]restful.Route)
|
|
||||||
for _, r := range routes {
|
|
||||||
pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r)
|
|
||||||
}
|
|
||||||
return pathToRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapKeyFromParam(param *restful.Parameter) interface{} {
|
|
||||||
return struct {
|
|
||||||
Name string
|
|
||||||
Kind int
|
|
||||||
}{
|
|
||||||
Name: param.Data().Name,
|
|
||||||
Kind: param.Data().Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,22 +18,21 @@ package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apiserver/pkg/server/mux"
|
|
||||||
apiserveropenapi "k8s.io/apiserver/pkg/server/openapi"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/server/mux"
|
||||||
|
"k8s.io/kube-openapi/pkg/common"
|
||||||
|
"k8s.io/kube-openapi/pkg/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OpenAPI installs spec endpoints for each web service.
|
// OpenAPI installs spec endpoints for each web service.
|
||||||
type OpenAPI struct {
|
type OpenAPI struct {
|
||||||
Config *openapi.Config
|
Config *common.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install adds the SwaggerUI webservice to the given mux.
|
// Install adds the SwaggerUI webservice to the given mux.
|
||||||
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
|
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
|
||||||
err := apiserveropenapi.RegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
|
_, err := handler.BuildAndRegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Failed to register open api spec for root: %v", err)
|
glog.Fatalf("Failed to register open api spec for root: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["trie.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 trie
|
|
||||||
|
|
||||||
// A simple trie implementation with Add an HasPrefix methods only.
|
|
||||||
type Trie struct {
|
|
||||||
children map[byte]*Trie
|
|
||||||
wordTail bool
|
|
||||||
word string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a Trie and add all strings in the provided list to it.
|
|
||||||
func New(list []string) Trie {
|
|
||||||
ret := Trie{
|
|
||||||
children: make(map[byte]*Trie),
|
|
||||||
wordTail: false,
|
|
||||||
}
|
|
||||||
for _, v := range list {
|
|
||||||
ret.Add(v)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a word to this trie
|
|
||||||
func (t *Trie) Add(v string) {
|
|
||||||
root := t
|
|
||||||
for _, b := range []byte(v) {
|
|
||||||
child, exists := root.children[b]
|
|
||||||
if !exists {
|
|
||||||
child = &Trie{
|
|
||||||
children: make(map[byte]*Trie),
|
|
||||||
wordTail: false,
|
|
||||||
}
|
|
||||||
root.children[b] = child
|
|
||||||
}
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
root.wordTail = true
|
|
||||||
root.word = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPrefix returns true of v has any of the prefixes stored in this trie.
|
|
||||||
func (t *Trie) HasPrefix(v string) bool {
|
|
||||||
_, has := t.GetPrefix(v)
|
|
||||||
return has
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrefix is like HasPrefix but return the prefix in case of match or empty string otherwise.
|
|
||||||
func (t *Trie) GetPrefix(v string) (string, bool) {
|
|
||||||
root := t
|
|
||||||
if root.wordTail {
|
|
||||||
return root.word, true
|
|
||||||
}
|
|
||||||
for _, b := range []byte(v) {
|
|
||||||
child, exists := root.children[b]
|
|
||||||
if !exists {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
if child.wordTail {
|
|
||||||
return child.word, true
|
|
||||||
}
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue