Update main repo references to new kube-openapi repo

This commit is contained in:
mbohlool
2017-07-19 23:34:37 -07:00
parent 3bde9a1e8c
commit 400b77b48f
31 changed files with 27 additions and 2433 deletions

View File

@@ -38,7 +38,6 @@ import (
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
utilnet "k8s.io/apimachinery/pkg/util/net"
@@ -53,6 +52,7 @@ import (
serverstorage "k8s.io/apiserver/pkg/server/storage"
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
//aggregatorinformers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
openapi "k8s.io/kube-openapi/pkg/common"
clientgoinformers "k8s.io/client-go/informers"
clientgoclientset "k8s.io/client-go/kubernetes"

View File

@@ -32,7 +32,6 @@ import (
apiv1 "k8s.io/api/core/v1"
extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1"
apimachineryopenapi "k8s.io/apimachinery/pkg/openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
@@ -40,6 +39,7 @@ import (
"k8s.io/apiserver/pkg/server/filters"
serverstorage "k8s.io/apiserver/pkg/server/storage"
clientset "k8s.io/client-go/kubernetes"
openapicommon "k8s.io/kube-openapi/pkg/common"
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options"
"k8s.io/kubernetes/pkg/api"
@@ -452,7 +452,7 @@ func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swag
}
s.Definitions[k] = spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/definitions/" + apimachineryopenapi.EscapeJsonPointer(v)),
Ref: spec.MustCreateRef("#/definitions/" + openapicommon.EscapeJsonPointer(v)),
Description: fmt.Sprintf("Deprecated. Please use %s instead.", v),
},
}

View File

@@ -582,7 +582,6 @@ staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1
staging/src/k8s.io/apimachinery/pkg/conversion
staging/src/k8s.io/apimachinery/pkg/conversion/unstructured
staging/src/k8s.io/apimachinery/pkg/labels
staging/src/k8s.io/apimachinery/pkg/openapi
staging/src/k8s.io/apimachinery/pkg/runtime/schema
staging/src/k8s.io/apimachinery/pkg/runtime/serializer
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf
@@ -654,7 +653,6 @@ staging/src/k8s.io/apiserver/pkg/registry/rest/resttest
staging/src/k8s.io/apiserver/pkg/server
staging/src/k8s.io/apiserver/pkg/server/healthz
staging/src/k8s.io/apiserver/pkg/server/httplog
staging/src/k8s.io/apiserver/pkg/server/openapi
staging/src/k8s.io/apiserver/pkg/server/options
staging/src/k8s.io/apiserver/pkg/server/routes/data/swagger
staging/src/k8s.io/apiserver/pkg/server/storage
@@ -674,7 +672,6 @@ staging/src/k8s.io/apiserver/pkg/util/feature
staging/src/k8s.io/apiserver/pkg/util/flag
staging/src/k8s.io/apiserver/pkg/util/proxy
staging/src/k8s.io/apiserver/pkg/util/trace
staging/src/k8s.io/apiserver/pkg/util/trie
staging/src/k8s.io/apiserver/pkg/util/webhook
staging/src/k8s.io/apiserver/pkg/util/wsstream
staging/src/k8s.io/apiserver/plugin/pkg/audit/log
@@ -782,7 +779,6 @@ staging/src/k8s.io/kube-gen/cmd/conversion-gen/generators
staging/src/k8s.io/kube-gen/cmd/go-to-protobuf/protobuf
staging/src/k8s.io/kube-gen/cmd/informer-gen/generators
staging/src/k8s.io/kube-gen/cmd/lister-gen/generators
staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators
staging/src/k8s.io/metrics/pkg/apis/custom_metrics
staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1
staging/src/k8s.io/metrics/pkg/apis/metrics

View File

@@ -65,15 +65,16 @@ function print_forbidden_imports () {
}
RC=0
print_forbidden_imports apimachinery should_be_leaf || RC=1
print_forbidden_imports apimachinery k8s.io/kube-openapi || RC=1
print_forbidden_imports api k8s.io/apimachinery || RC=1
print_forbidden_imports kube-gen k8s.io/apimachinery k8s.io/client-go k8s.io/gengo || RC=1
print_forbidden_imports kube-gen k8s.io/apimachinery k8s.io/client-go k8s.io/gengo k8s.io/kube-openapi || RC=1
print_forbidden_imports client-go k8s.io/apimachinery k8s.io/api || RC=1
print_forbidden_imports apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/api || RC=1
print_forbidden_imports apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/api k8s.io/kube-openapi || RC=1
print_forbidden_imports metrics k8s.io/apimachinery k8s.io/client-go k8s.io/api || RC=1
print_forbidden_imports kube-aggregator k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
print_forbidden_imports kube-aggregator k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api k8s.io/kube-openapi || RC=1
print_forbidden_imports sample-apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
print_forbidden_imports apiextensions-apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
print_forbidden_imports kube-openapi k8s.io/gengo || RC=1
if [ ${RC} != 0 ]; then
exit ${RC}
fi

View File

@@ -20,7 +20,7 @@ import (
"encoding/json"
"time"
"k8s.io/apimachinery/pkg/openapi"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/go-openapi/spec"
"github.com/google/gofuzz"

View File

@@ -20,7 +20,7 @@ import (
"encoding/json"
"time"
"k8s.io/apimachinery/pkg/openapi"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/go-openapi/spec"
"github.com/google/gofuzz"

View File

@@ -4,7 +4,7 @@ load("@io_kubernetes_build//defs:go.bzl", "go_genrule")
def openapi_library(name, tags, srcs, openapi_targets=[], vendor_targets=[]):
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
] + ["//%s:go_default_library" % target for target in openapi_targets] + ["//vendor/%s:go_default_library" % target for target in vendor_targets]
go_library(
name=name,

View File

@@ -29,7 +29,7 @@ import (
"github.com/go-openapi/spec"
inf "gopkg.in/inf.v0"
"k8s.io/apimachinery/pkg/openapi"
openapi "k8s.io/kube-openapi/pkg/common"
)
// Quantity is a fixed-point representation of a number.

View File

@@ -20,7 +20,7 @@ import (
"encoding/json"
"time"
"k8s.io/apimachinery/pkg/openapi"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/go-openapi/spec"
"github.com/google/gofuzz"

View File

@@ -20,7 +20,7 @@ import (
"encoding/json"
"time"
"k8s.io/apimachinery/pkg/openapi"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/go-openapi/spec"
"github.com/google/gofuzz"

View File

@@ -1,161 +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 (
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"strings"
)
// OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi.
type OpenAPIDefinition struct {
Schema spec.Schema
Dependencies []string
}
type ReferenceCallback func(path string) spec.Ref
// OpenAPIDefinitions is collection of all definitions.
type GetOpenAPIDefinitions func(ReferenceCallback) map[string]OpenAPIDefinition
// OpenAPIDefinitionGetter gets openAPI definitions for a given type. If a type implements this interface,
// the definition returned by it will be used, otherwise the auto-generated definitions will be used. See
// GetOpenAPITypeFormat for more information about trade-offs of using this interface or GetOpenAPITypeFormat method when
// possible.
type OpenAPIDefinitionGetter interface {
OpenAPIDefinition() *OpenAPIDefinition
}
// Config is set of configuration for openAPI spec generation.
type Config struct {
// 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
// CommonResponses will be added as a response to all operation specs. This is a good place to add common
// responses such as authorization failed.
CommonResponses map[int]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.
GetDefinitions GetOpenAPIDefinitions
// GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs.
GetOperationIDAndTags func(r *restful.Route) (string, []string, error)
// GetDefinitionName returns a friendly name for a definition base on the serving path. parameter `name` is the full name of the definition.
// It is an optional function to customize model names.
GetDefinitionName func(name string) (string, spec.Extensions)
// PostProcessSpec runs after the spec is ready to serve. It allows a final modification to the spec before serving.
PostProcessSpec func(*spec.Swagger) (*spec.Swagger, error)
// SecurityDefinitions is list of all security definitions for OpenAPI service. If this is not nil, the user of config
// is responsible to provide DefaultSecurity and (maybe) add unauthorized response to CommonResponses.
SecurityDefinitions *spec.SecurityDefinitions
// DefaultSecurity for all operations. This will pass as spec.SwaggerProps.Security to OpenAPI.
// For most cases, this will be list of acceptable definitions in SecurityDefinitions.
DefaultSecurity []map[string][]string
}
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
// two ways to customize spec for a type. If you add it here, a type will be converted to a simple type and the type
// comment (the comment that is added before type definition) will be lost. The spec will still have the property
// comment. The second way is to implement OpenAPIDefinitionGetter interface. That function can customize the spec (so
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
// Example:
// type Sample struct {
// ...
// // port of the server
// port IntOrString
// ...
// }
// // IntOrString documentation...
// type IntOrString { ... }
//
// Adding IntOrString to this function:
// "port" : {
// format: "string",
// type: "int-or-string",
// Description: "port of the server"
// }
//
// Implement OpenAPIDefinitionGetter for IntOrString:
//
// "port" : {
// $Ref: "#/definitions/IntOrString"
// Description: "port of the server"
// }
// ...
// definitions:
// {
// "IntOrString": {
// format: "string",
// type: "int-or-string",
// Description: "IntOrString documentation..." // new
// }
// }
//
func GetOpenAPITypeFormat(typeName string) (string, string) {
schemaTypeFormatMap := map[string][]string{
"uint": {"integer", "int32"},
"uint8": {"integer", "byte"},
"uint16": {"integer", "int32"},
"uint32": {"integer", "int64"},
"uint64": {"integer", "int64"},
"int": {"integer", "int32"},
"int8": {"integer", "byte"},
"int16": {"integer", "int32"},
"int32": {"integer", "int32"},
"int64": {"integer", "int64"},
"byte": {"integer", "byte"},
"float64": {"number", "double"},
"float32": {"number", "float"},
"bool": {"boolean", ""},
"time.Time": {"string", "date-time"},
"string": {"string", ""},
"integer": {"integer", ""},
"number": {"number", ""},
"boolean": {"boolean", ""},
"[]byte": {"string", "byte"}, // base64 encoded characters
"interface{}": {"object", ""},
}
mapped, ok := schemaTypeFormatMap[typeName]
if !ok {
return "", ""
}
return mapped[0], mapped[1]
}
func EscapeJsonPointer(p string) string {
// Escaping reference name using rfc6901
p = strings.Replace(p, "~", "~0", -1)
p = strings.Replace(p, "/", "~1", -1)
return p
}

View File

@@ -1,18 +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 holds shared codes and types between open API code generator and spec generator.
package openapi

View File

@@ -24,7 +24,7 @@ import (
"strconv"
"strings"
"k8s.io/apimachinery/pkg/openapi"
openapi "k8s.io/kube-openapi/pkg/common"
"github.com/go-openapi/spec"
"github.com/golang/glog"

View File

@@ -30,10 +30,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"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 (
extensionGVK = "x-kubernetes-group-version-kind"

View File

@@ -33,7 +33,6 @@ import (
"github.com/go-openapi/spec"
"github.com/pborman/uuid"
openapicommon "k8s.io/apimachinery/pkg/openapi"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/sets"
@@ -61,6 +60,7 @@ import (
"k8s.io/client-go/informers"
restclient "k8s.io/client-go/rest"
certutil "k8s.io/client-go/util/cert"
openapicommon "k8s.io/kube-openapi/pkg/common"
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
)

View File

@@ -30,7 +30,6 @@ import (
"k8s.io/apimachinery/pkg/apimachinery"
"k8s.io/apimachinery/pkg/apimachinery/registered"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
openapicommon "k8s.io/apimachinery/pkg/openapi"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -44,6 +43,7 @@ import (
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/routes"
restclient "k8s.io/client-go/rest"
openapicommon "k8s.io/kube-openapi/pkg/common"
)
// Info about an API group.

View File

@@ -34,7 +34,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apimachinery"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/openapi"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -51,6 +50,7 @@ import (
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
restclient "k8s.io/client-go/rest"
openapi "k8s.io/kube-openapi/pkg/common"
)
const (

View File

@@ -1,7 +0,0 @@
reviewers:
- yujuhong
- gmarek
- mbohlool
- philips
approvers:
- mbohlool

View File

@@ -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

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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,
}
}

View File

@@ -18,22 +18,21 @@ package routes
import (
"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"
"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.
type OpenAPI struct {
Config *openapi.Config
Config *common.Config
}
// Install adds the SwaggerUI webservice to the given mux.
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 {
glog.Fatalf("Failed to register open api spec for root: %v", err)
}

View File

@@ -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"],
)

View File

@@ -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
}

View File

@@ -206,18 +206,10 @@
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Rev": "f6abca593680b2315d2075e0f5e2a9751e3f431a"
},
{
"ImportPath": "github.com/ugorji/go/codec",
"Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"

View File

@@ -1,26 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "openapi-gen",
library = ":go_default_library",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
tags = ["automanaged"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/gengo/args:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/openapi-gen/generators:go_default_library",
],
)

View File

@@ -1,37 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["openapi.go"],
tags = ["automanaged"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
"//vendor/k8s.io/gengo/args:go_default_library",
"//vendor/k8s.io/gengo/generator:go_default_library",
"//vendor/k8s.io/gengo/namer:go_default_library",
"//vendor/k8s.io/gengo/types:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["openapi_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/gengo/generator:go_default_library",
"//vendor/k8s.io/gengo/namer:go_default_library",
"//vendor/k8s.io/gengo/parser:go_default_library",
"//vendor/k8s.io/gengo/types:go_default_library",
],
)

View File

@@ -1,612 +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 generators
import (
"bytes"
"fmt"
"io"
"path/filepath"
"reflect"
"sort"
"strings"
"k8s.io/apimachinery/pkg/openapi"
"k8s.io/gengo/args"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"github.com/golang/glog"
)
// This is the comment tag that carries parameters for open API generation.
const tagName = "k8s:openapi-gen"
const tagOptional = "optional"
// Known values for the tag.
const (
tagValueTrue = "true"
tagValueFalse = "false"
tagExtensionPrefix = "x-kubernetes-"
tagPatchStrategy = "patchStrategy"
tagPatchMergeKey = "patchMergeKey"
patchStrategyExtensionName = "patch-strategy"
patchMergeKeyExtensionName = "patch-merge-key"
)
func getOpenAPITagValue(comments []string) []string {
return types.ExtractCommentTags("+", comments)[tagName]
}
func getSingleTagsValue(comments []string, tag string) (string, error) {
tags, ok := types.ExtractCommentTags("+", comments)[tag]
if !ok || len(tags) == 0 {
return "", nil
}
if len(tags) > 1 {
return "", fmt.Errorf("Multiple values are not allowed for tag %s", tag)
}
return tags[0], nil
}
func hasOpenAPITagValue(comments []string, value string) bool {
tagValues := getOpenAPITagValue(comments)
for _, val := range tagValues {
if val == value {
return true
}
}
return false
}
// hasOptionalTag returns true if the member has +optional in its comments or
// omitempty in its json tags.
func hasOptionalTag(m *types.Member) bool {
hasOptionalCommentTag := types.ExtractCommentTags(
"+", m.CommentLines)[tagOptional] != nil
hasOptionalJsonTag := strings.Contains(
reflect.StructTag(m.Tags).Get("json"), "omitempty")
return hasOptionalCommentTag || hasOptionalJsonTag
}
type identityNamer struct{}
func (_ identityNamer) Name(t *types.Type) string {
return t.Name.String()
}
var _ namer.Namer = identityNamer{}
// NameSystems returns the name system used by the generators in this package.
func NameSystems() namer.NameSystems {
return namer.NameSystems{
"raw": namer.NewRawNamer("", nil),
"sorting_namer": identityNamer{},
}
}
// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func DefaultNameSystem() string {
return "sorting_namer"
}
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
boilerplate, err := arguments.LoadGoBoilerplate()
if err != nil {
glog.Fatalf("Failed loading boilerplate: %v", err)
}
header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
header = append(header, []byte(
`
// This file was autogenerated by openapi-gen. Do not edit it manually!
`)...)
if err := context.AddDir(arguments.OutputPackagePath); err != nil {
glog.Fatalf("Failed to load output package: %v", err)
}
pkg := context.Universe[arguments.OutputPackagePath]
if pkg == nil {
glog.Fatalf("Got nil output package: %v", err)
}
return generator.Packages{
&generator.DefaultPackage{
PackageName: strings.Split(filepath.Base(pkg.Path), ".")[0],
PackagePath: pkg.Path,
HeaderText: header,
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
return []generator.Generator{NewOpenAPIGen(arguments.OutputFileBaseName, pkg, context)}
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
return false
}
pkg := context.Universe.Package(t.Name.Package)
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
}
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
return true
}
return false
},
},
}
}
const (
specPackagePath = "github.com/go-openapi/spec"
openAPICommonPackagePath = "k8s.io/apimachinery/pkg/openapi"
)
// openApiGen produces a file with auto-generated OpenAPI functions.
type openAPIGen struct {
generator.DefaultGen
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
targetPackage *types.Package
imports namer.ImportTracker
context *generator.Context
}
func NewOpenAPIGen(sanitizedName string, targetPackage *types.Package, context *generator.Context) generator.Generator {
return &openAPIGen{
DefaultGen: generator.DefaultGen{
OptionalName: sanitizedName,
},
imports: generator.NewImportTracker(),
targetPackage: targetPackage,
context: context,
}
}
func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
// Have the raw namer for this file track what it imports.
return namer.NameSystems{
"raw": namer.NewRawNamer(g.targetPackage.Path, g.imports),
}
}
func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool {
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
return false
}
return true
}
func (g *openAPIGen) isOtherPackage(pkg string) bool {
if pkg == g.targetPackage.Path {
return false
}
if strings.HasSuffix(pkg, "\""+g.targetPackage.Path+"\"") {
return false
}
return true
}
func (g *openAPIGen) Imports(c *generator.Context) []string {
importLines := []string{}
for _, singleImport := range g.imports.ImportLines() {
importLines = append(importLines, singleImport)
}
return importLines
}
func argsFromType(t *types.Type) generator.Args {
return generator.Args{
"type": t,
"ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
"OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
"SpecSchemaType": types.Ref(specPackagePath, "Schema"),
}
}
func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
return sw.Error()
}
func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
sw.Do("}\n", nil)
sw.Do("}\n", nil)
return sw.Error()
}
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
glog.V(5).Infof("generating for type %v", t)
sw := generator.NewSnippetWriter(w, c, "$", "$")
err := newOpenAPITypeWriter(sw).generate(t)
if err != nil {
return err
}
return sw.Error()
}
func getJsonTags(m *types.Member) []string {
jsonTag := reflect.StructTag(m.Tags).Get("json")
if jsonTag == "" {
return []string{}
}
return strings.Split(jsonTag, ",")
}
func getPatchTags(m *types.Member) (string, string) {
return reflect.StructTag(m.Tags).Get(tagPatchMergeKey), reflect.StructTag(m.Tags).Get(tagPatchStrategy)
}
func getReferableName(m *types.Member) string {
jsonTags := getJsonTags(m)
if len(jsonTags) > 0 {
if jsonTags[0] == "-" {
return ""
} else {
return jsonTags[0]
}
} else {
return m.Name
}
}
func shouldInlineMembers(m *types.Member) bool {
jsonTags := getJsonTags(m)
return len(jsonTags) > 1 && jsonTags[1] == "inline"
}
type openAPITypeWriter struct {
*generator.SnippetWriter
refTypes map[string]*types.Type
GetDefinitionInterface *types.Type
}
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter {
return openAPITypeWriter{
SnippetWriter: sw,
refTypes: map[string]*types.Type{},
}
}
func hasOpenAPIDefinitionMethod(t *types.Type) bool {
for mn, mt := range t.Methods {
if mn != "OpenAPIDefinition" {
continue
}
if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
return false
}
r := mt.Signature.Results[0]
if r.Name.Name != "OpenAPIDefinition" || r.Name.Package != openAPICommonPackagePath {
return false
}
return true
}
return false
}
// typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
func typeShortName(t *types.Type) string {
return filepath.Base(t.Name.Package) + "." + t.Name.Name
}
func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
var err error
for _, m := range t.Members {
if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
continue
}
if shouldInlineMembers(&m) {
required, err = g.generateMembers(m.Type, required)
if err != nil {
return required, err
}
continue
}
name := getReferableName(&m)
if name == "" {
continue
}
if !hasOptionalTag(&m) {
required = append(required, name)
}
if err = g.generateProperty(&m, t); err != nil {
return required, err
}
}
return required, nil
}
func (g openAPITypeWriter) generate(t *types.Type) error {
// Only generate for struct type and ignore the rest
switch t.Kind {
case types.Struct:
args := argsFromType(t)
g.Do("\"$.$\": ", t.Name)
if hasOpenAPIDefinitionMethod(t) {
g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
return nil
}
g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
g.generateDescription(t.CommentLines)
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
required, err := g.generateMembers(t, []string{})
if err != nil {
return err
}
g.Do("},\n", nil)
if len(required) > 0 {
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
}
g.Do("},\n", nil)
if err := g.generateExtensions(t.CommentLines); err != nil {
return err
}
g.Do("},\n", nil)
g.Do("Dependencies: []string{\n", args)
// Map order is undefined, sort them or we may get a different file generated each time.
keys := []string{}
for k := range g.refTypes {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := g.refTypes[k]
if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" {
// This is a known type, we do not need a reference to it
// Will eliminate special case of time.Time
continue
}
g.Do("\"$.$\",", k)
}
g.Do("},\n},\n", nil)
}
return nil
}
func (g openAPITypeWriter) generateExtensions(CommentLines []string) error {
tagValues := getOpenAPITagValue(CommentLines)
type NameValue struct {
Name, Value string
}
extensions := []NameValue{}
for _, val := range tagValues {
if strings.HasPrefix(val, tagExtensionPrefix) {
parts := strings.SplitN(val, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("Invalid extension value: %v", val)
}
extensions = append(extensions, NameValue{parts[0], parts[1]})
}
}
patchMergeKeyTag, err := getSingleTagsValue(CommentLines, tagPatchMergeKey)
if err != nil {
return err
}
if len(patchMergeKeyTag) > 0 {
extensions = append(extensions, NameValue{tagExtensionPrefix + patchMergeKeyExtensionName, patchMergeKeyTag})
}
patchStrategyTag, err := getSingleTagsValue(CommentLines, tagPatchStrategy)
if err != nil {
return err
}
if len(patchStrategyTag) > 0 {
extensions = append(extensions, NameValue{tagExtensionPrefix + patchStrategyExtensionName, patchStrategyTag})
}
if len(extensions) == 0 {
return nil
}
g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
for _, extension := range extensions {
g.Do("\"$.$\": ", extension.Name)
g.Do("\"$.$\",\n", extension.Value)
}
g.Do("},\n},\n", nil)
return nil
}
// TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
patchMergeKeyStructTag, patchStrategyStructTag := getPatchTags(m)
patchMergeKeyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchMergeKey)
if err != nil {
return err
}
patchStrategyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchStrategy)
if err != nil {
return err
}
if patchMergeKeyStructTag != patchMergeKeyCommentTag {
return fmt.Errorf("patchMergeKey in comment and struct tags should match for member (%s) of (%s)",
m.Name, parent.Name.String())
}
if patchStrategyStructTag != patchStrategyCommentTag {
return fmt.Errorf("patchStrategy in comment and struct tags should match for member (%s) of (%s)",
m.Name, parent.Name.String())
}
return nil
}
func (g openAPITypeWriter) generateDescription(CommentLines []string) {
var buffer bytes.Buffer
delPrevChar := func() {
if buffer.Len() > 0 {
buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
}
}
for _, line := range CommentLines {
// Ignore all lines after ---
if line == "---" {
break
}
line = strings.TrimRight(line, " ")
leading := strings.TrimLeft(line, " ")
switch {
case len(line) == 0: // Keep paragraphs
delPrevChar()
buffer.WriteString("\n\n")
case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
default:
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
delPrevChar()
line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
} else {
line += " "
}
buffer.WriteString(line)
}
}
postDoc := strings.TrimRight(buffer.String(), "\n")
postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
postDoc = strings.Trim(postDoc, " ")
if postDoc != "" {
g.Do("Description: \"$.$\",\n", postDoc)
}
}
func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
name := getReferableName(m)
if name == "" {
return nil
}
if err := g.validatePatchTags(m, parent); err != nil {
return err
}
g.Do("\"$.$\": {\n", name)
if err := g.generateExtensions(m.CommentLines); err != nil {
return err
}
g.Do("SchemaProps: spec.SchemaProps{\n", nil)
g.generateDescription(m.CommentLines)
jsonTags := getJsonTags(m)
if len(jsonTags) > 1 && jsonTags[1] == "string" {
g.generateSimpleProperty("string", "")
g.Do("},\n},\n", nil)
return nil
}
t := resolveAliasAndPtrType(m.Type)
// If we can get a openAPI type and format for this type, we consider it to be simple property
typeString, format := openapi.GetOpenAPITypeFormat(t.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
g.Do("},\n},\n", nil)
return nil
}
switch t.Kind {
case types.Builtin:
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
case types.Map:
if err := g.generateMapProperty(t); err != nil {
return err
}
case types.Slice, types.Array:
if err := g.generateSliceProperty(t); err != nil {
return err
}
case types.Struct, types.Interface:
g.generateReferenceProperty(t)
default:
return fmt.Errorf("cannot generate spec for type %v", t)
}
g.Do("},\n},\n", nil)
return g.Error()
}
func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
g.Do("Type: []string{\"$.$\"},\n", typeString)
g.Do("Format: \"$.$\",\n", format)
}
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
g.refTypes[t.Name.String()] = t
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
}
func resolveAliasAndPtrType(t *types.Type) *types.Type {
var prev *types.Type
for prev != t {
prev = t
if t.Kind == types.Alias {
t = t.Underlying
}
if t.Kind == types.Pointer {
t = t.Elem
}
}
return t
}
func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
keyType := resolveAliasAndPtrType(t.Key)
elemType := resolveAliasAndPtrType(t.Elem)
// According to OpenAPI examples, only map from string is supported
if keyType.Name.Name != "string" {
return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
}
g.Do("Type: []string{\"object\"},\n", nil)
g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
g.Do("},\n},\n},\n", nil)
return nil
}
switch elemType.Kind {
case types.Builtin:
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function.", elemType)
case types.Struct:
g.generateReferenceProperty(elemType)
case types.Slice, types.Array:
g.generateSliceProperty(elemType)
default:
return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
}
g.Do("},\n},\n},\n", nil)
return nil
}
func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
elemType := resolveAliasAndPtrType(t.Elem)
g.Do("Type: []string{\"array\"},\n", nil)
g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)
g.Do("},\n},\n},\n", nil)
return nil
}
switch elemType.Kind {
case types.Builtin:
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function.", elemType)
case types.Struct:
g.generateReferenceProperty(elemType)
default:
return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
}
g.Do("},\n},\n},\n", nil)
return nil
}

View File

@@ -1,375 +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 generators
import (
"bytes"
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/parser"
"k8s.io/gengo/types"
)
func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
b := parser.New()
for name, src := range files {
if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil {
t.Fatal(err)
}
}
u, err := b.FindTypes()
if err != nil {
t.Fatal(err)
}
orderer := namer.Orderer{Namer: testNamer}
o := orderer.OrderUniverse(u)
return b, u, o
}
func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertions, *bytes.Buffer) {
assert := assert.New(t)
var testFiles = map[string]string{
"base/foo/bar.go": code,
}
rawNamer := namer.NewRawNamer("o", nil)
namers := namer.NameSystems{
"raw": namer.NewRawNamer("", nil),
}
builder, universe, _ := construct(t, testFiles, rawNamer)
context, err := generator.NewContext(builder, namers, "raw")
if err != nil {
t.Fatal(err)
}
buffer := &bytes.Buffer{}
sw := generator.NewSnippetWriter(buffer, context, "$", "$")
blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
return newOpenAPITypeWriter(sw).generate(blahT), assert, buffer
}
func TestSimple(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
package foo
// Blah is a test.
// +k8s:openapi-gen=true
// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
type Blah struct {
// A simple string
String string
// A simple int
Int int `+"`"+`json:",omitempty"`+"`"+`
// An int considered string simple int
IntString int `+"`"+`json:",string"`+"`"+`
// A simple int64
Int64 int64
// A simple int32
Int32 int32
// A simple int16
Int16 int16
// A simple int8
Int8 int8
// A simple int
Uint uint
// A simple int64
Uint64 uint64
// A simple int32
Uint32 uint32
// A simple int16
Uint16 uint16
// A simple int8
Uint8 uint8
// A simple byte
Byte byte
// A simple boolean
Bool bool
// A simple float64
Float64 float64
// A simple float32
Float32 float32
// a base64 encoded characters
ByteArray []byte
// a member with an extension
// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
WithExtension string
// a member with struct tag as extension
// +patchStrategy=ps
// +patchMergeKey=pmk
WithStructTagExtension string `+"`"+`patchStrategy:"ps" patchMergeKey:"pmk"`+"`"+`
}
`)
if err != nil {
t.Fatal(err)
}
assert.Equal(`"base/foo.Blah": {
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Blah is a test.",
Properties: map[string]spec.Schema{
"String": {
SchemaProps: spec.SchemaProps{
Description: "A simple string",
Type: []string{"string"},
Format: "",
},
},
"Int64": {
SchemaProps: spec.SchemaProps{
Description: "A simple int64",
Type: []string{"integer"},
Format: "int64",
},
},
"Int32": {
SchemaProps: spec.SchemaProps{
Description: "A simple int32",
Type: []string{"integer"},
Format: "int32",
},
},
"Int16": {
SchemaProps: spec.SchemaProps{
Description: "A simple int16",
Type: []string{"integer"},
Format: "int32",
},
},
"Int8": {
SchemaProps: spec.SchemaProps{
Description: "A simple int8",
Type: []string{"integer"},
Format: "byte",
},
},
"Uint": {
SchemaProps: spec.SchemaProps{
Description: "A simple int",
Type: []string{"integer"},
Format: "int32",
},
},
"Uint64": {
SchemaProps: spec.SchemaProps{
Description: "A simple int64",
Type: []string{"integer"},
Format: "int64",
},
},
"Uint32": {
SchemaProps: spec.SchemaProps{
Description: "A simple int32",
Type: []string{"integer"},
Format: "int64",
},
},
"Uint16": {
SchemaProps: spec.SchemaProps{
Description: "A simple int16",
Type: []string{"integer"},
Format: "int32",
},
},
"Uint8": {
SchemaProps: spec.SchemaProps{
Description: "A simple int8",
Type: []string{"integer"},
Format: "byte",
},
},
"Byte": {
SchemaProps: spec.SchemaProps{
Description: "A simple byte",
Type: []string{"integer"},
Format: "byte",
},
},
"Bool": {
SchemaProps: spec.SchemaProps{
Description: "A simple boolean",
Type: []string{"boolean"},
Format: "",
},
},
"Float64": {
SchemaProps: spec.SchemaProps{
Description: "A simple float64",
Type: []string{"number"},
Format: "double",
},
},
"Float32": {
SchemaProps: spec.SchemaProps{
Description: "A simple float32",
Type: []string{"number"},
Format: "float",
},
},
"ByteArray": {
SchemaProps: spec.SchemaProps{
Description: "a base64 encoded characters",
Type: []string{"string"},
Format: "byte",
},
},
"WithExtension": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-member-tag": "member_test",
},
},
SchemaProps: spec.SchemaProps{
Description: "a member with an extension",
Type: []string{"string"},
Format: "",
},
},
"WithStructTagExtension": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-patch-merge-key": "pmk",
"x-kubernetes-patch-strategy": "ps",
},
},
SchemaProps: spec.SchemaProps{
Description: "a member with struct tag as extension",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension"},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-type-tag": "type_test",
},
},
},
Dependencies: []string{
},
},
`, buffer.String())
}
func TestFailingSample1(t *testing.T) {
err, assert, _ := testOpenAPITypeWritter(t, `
package foo
// Map sample tests openAPIGen.generateMapProperty method.
type Blah struct {
// A sample String to String map
StringToArray map[string]map[string]string
}
`)
if assert.Error(err, "An error was expected") {
assert.Equal(err, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
}
}
func TestFailingSample2(t *testing.T) {
err, assert, _ := testOpenAPITypeWritter(t, `
package foo
// Map sample tests openAPIGen.generateMapProperty method.
type Blah struct {
// A sample String to String map
StringToArray map[int]string
} `)
if assert.Error(err, "An error was expected") {
assert.Equal(err, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
}
}
func TestPointer(t *testing.T) {
err, assert, buffer := testOpenAPITypeWritter(t, `
package foo
// PointerSample demonstrate pointer's properties
type Blah struct {
// A string pointer
StringPointer *string
// A struct pointer
StructPointer *Blah
// A slice pointer
SlicePointer *[]string
// A map pointer
MapPointer *map[string]string
}
`)
if err != nil {
t.Fatal(err)
}
assert.Equal(`"base/foo.Blah": {
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "PointerSample demonstrate pointer's properties",
Properties: map[string]spec.Schema{
"StringPointer": {
SchemaProps: spec.SchemaProps{
Description: "A string pointer",
Type: []string{"string"},
Format: "",
},
},
"StructPointer": {
SchemaProps: spec.SchemaProps{
Description: "A struct pointer",
Ref: ref("base/foo.Blah"),
},
},
"SlicePointer": {
SchemaProps: spec.SchemaProps{
Description: "A slice pointer",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
"MapPointer": {
SchemaProps: spec.SchemaProps{
Description: "A map pointer",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
},
},
Dependencies: []string{
"base/foo.Blah",},
},
`, buffer.String())
}

View File

@@ -23,7 +23,7 @@ import (
"path/filepath"
"k8s.io/gengo/args"
"k8s.io/kube-gen/cmd/openapi-gen/generators"
"k8s.io/kube-openapi/pkg/generators"
"github.com/golang/glog"
)