Merge pull request #48861 from mbohlool/openapi_aggr

Automatic merge from submit-queue (batch tested with PRs 49992, 48861, 49267, 49356, 49886)

Reintegrate aggregation support for OpenAPI

Reintegrating changes of #46734

Changes summary:

- Extracted all OpenAPI specs to new repo `kube-openapi`
- Make OpenAPI spec aggregator to copy and rename any non-requal model (even with documentation change only).
- Load specs when adding APIServices and retry on failure until successful spec retrieval or a 404.
- Assumes all Specs except aggregator's Spec are static
- A re-register of any APIService will result in updating the spec for that service (Suggestion for TPR: they should be registered to aggregator API Server, Open for discussion if any more changes needed for another PR.)

fixes #48548

Kubernetes-commit: 9067d359511890b893794c2e0a93bff88ed7d697
This commit is contained in:
Kubernetes Publisher 2017-08-02 05:15:57 -07:00
commit 713755562e
20 changed files with 81 additions and 2405 deletions

118
Godeps/Godeps.json generated
View File

@ -804,239 +804,239 @@
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/testing",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/testing/roundtrip",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/validation",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/validation/path",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery/announced",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/validation",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/labels",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/openapi",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/selection",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/types",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/proxy",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/rand",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/uuid",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/version",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/watch",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "806e2e07933f50f9edcf738b14a53ae8d2011ab3"
"Rev": "94389d3dacac832c3e0e0b6324e05abc3756ea67"
},
{
"ImportPath": "k8s.io/client-go/discovery",

View File

@ -31,6 +31,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/trie:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util:go_default_library",
],
)

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

@ -22,7 +22,6 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
@ -40,6 +39,7 @@ go_test(
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
],
)
@ -69,7 +69,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apimachinery:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
@ -101,11 +100,11 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/openapi:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/routes:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
],
)

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

@ -27,11 +27,9 @@ import (
"github.com/emicklei/go-restful-swagger12"
"github.com/golang/glog"
"github.com/go-openapi/spec"
"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"
@ -43,9 +41,9 @@ import (
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/openapi"
"k8s.io/apiserver/pkg/server/routes"
restclient "k8s.io/client-go/rest"
openapicommon "k8s.io/kube-openapi/pkg/common"
)
// Info about an API group.
@ -131,9 +129,6 @@ type GenericAPIServer struct {
swaggerConfig *swagger.Config
openAPIConfig *openapicommon.Config
// Enables updating OpenAPI spec using update method.
OpenAPIService *openapi.OpenAPIService
// PostStartHooks are each called after the server has started listening, in a separate go func for each
// with no guarantee of ordering between them. The map key is a name used for error reporting.
// It may kill the process with a panic if it wishes to by returning an error.
@ -173,9 +168,6 @@ type DelegationTarget interface {
// ListedPaths returns the paths for supporting an index
ListedPaths() []string
// OpenAPISpec returns the OpenAPI spec of the delegation target if exists, nil otherwise.
OpenAPISpec() *spec.Swagger
}
func (s *GenericAPIServer) UnprotectedHandler() http.Handler {
@ -191,9 +183,6 @@ func (s *GenericAPIServer) HealthzChecks() []healthz.HealthzChecker {
func (s *GenericAPIServer) ListedPaths() []string {
return s.listedPathProvider.ListedPaths()
}
func (s *GenericAPIServer) OpenAPISpec() *spec.Swagger {
return s.OpenAPIService.GetSpec()
}
var EmptyDelegate = emptyDelegate{
requestContextMapper: apirequest.NewRequestContextMapper(),
@ -218,9 +207,6 @@ func (s emptyDelegate) ListedPaths() []string {
func (s emptyDelegate) RequestContextMapper() apirequest.RequestContextMapper {
return s.requestContextMapper
}
func (s emptyDelegate) OpenAPISpec() *spec.Swagger {
return nil
}
// RequestContextMapper is exposed so that third party resource storage can be build in a different location.
// TODO refactor third party resource storage
@ -243,22 +229,17 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
if s.swaggerConfig != nil {
routes.Swagger{Config: s.swaggerConfig}.Install(s.Handler.GoRestfulContainer)
}
s.PrepareOpenAPIService()
if s.openAPIConfig != nil {
routes.OpenAPI{
Config: s.openAPIConfig,
}.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
}
s.installHealthz()
return preparedGenericAPIServer{s}
}
// PrepareOpenAPIService installs OpenAPI handler if it does not exists.
func (s *GenericAPIServer) PrepareOpenAPIService() {
if s.openAPIConfig != nil && s.OpenAPIService == nil {
s.OpenAPIService = routes.OpenAPI{
Config: s.openAPIConfig,
}.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
}
}
// Run spawns the secure http server. It only returns if stopCh is closed
// or the secure port cannot be listened on initially.
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {

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,51 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"openapi_aggregator_test.go",
"openapi_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor/github.com/emicklei/go-restful:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"openapi.go",
"openapi_aggregator.go",
"openapi_handler.go",
"util.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/NYTimes/gziphandler:go_default_library",
"//vendor/github.com/emicklei/go-restful:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/golang/protobuf/proto:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/googleapis/gnostic/compiler:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/trie:go_default_library",
],
)

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,434 +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 (
"crypto/sha512"
"encoding/json"
"fmt"
"net/http"
"reflect"
"strings"
restful "github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/openapi"
"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
protocolList []string
definitions map[string]openapi.OpenAPIDefinition
}
func computeEtag(data []byte) string {
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
}
func BuildSwaggerSpec(webServices []*restful.WebService, config *openapi.Config) (*spec.Swagger, error) {
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 nil, err
}
return o.swagger, 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(DEFINITION_PREFIX + 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
}
}
return nil
}
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 DEFINITION_PREFIX + 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,482 +0,0 @@
/*
Copyright 2017 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 (
"fmt"
"strings"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apiserver/pkg/util/trie"
)
const (
DEFINITION_PREFIX = "#/definitions/"
)
var cloner = conversion.NewCloner()
// Run a walkRefCallback method on all references of an OpenAPI spec
type walkAllRefs struct {
// walkRefCallback will be called on each reference and the return value
// will replace that reference. This will allow the callers to change
// all/some references of an spec (e.g. useful in renaming definitions).
walkRefCallback func(ref spec.Ref) spec.Ref
// The spec to walk through.
root *spec.Swagger
}
func newWalkAllRefs(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) *walkAllRefs {
return &walkAllRefs{
walkRefCallback: walkRef,
root: sp,
}
}
func (s *walkAllRefs) walkRef(ref spec.Ref) spec.Ref {
if ref.String() != "" {
refStr := ref.String()
// References that start with #/definitions/ has a definition
// inside the same spec file. If that is the case, walk through
// those definitions too.
// We do not support external references yet.
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
def := s.root.Definitions[refStr[len(DEFINITION_PREFIX):]]
s.walkSchema(&def)
}
}
return s.walkRefCallback(ref)
}
func (s *walkAllRefs) walkSchema(schema *spec.Schema) {
if schema == nil {
return
}
schema.Ref = s.walkRef(schema.Ref)
for _, v := range schema.Definitions {
s.walkSchema(&v)
}
for _, v := range schema.Properties {
s.walkSchema(&v)
}
for _, v := range schema.PatternProperties {
s.walkSchema(&v)
}
for _, v := range schema.AllOf {
s.walkSchema(&v)
}
for _, v := range schema.AnyOf {
s.walkSchema(&v)
}
for _, v := range schema.OneOf {
s.walkSchema(&v)
}
if schema.Not != nil {
s.walkSchema(schema.Not)
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
s.walkSchema(schema.AdditionalProperties.Schema)
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
s.walkSchema(schema.AdditionalItems.Schema)
}
if schema.Items != nil {
if schema.Items.Schema != nil {
s.walkSchema(schema.Items.Schema)
}
for _, v := range schema.Items.Schemas {
s.walkSchema(&v)
}
}
}
func (s *walkAllRefs) walkParams(params []spec.Parameter) {
if params == nil {
return
}
for _, param := range params {
param.Ref = s.walkRef(param.Ref)
s.walkSchema(param.Schema)
if param.Items != nil {
param.Items.Ref = s.walkRef(param.Items.Ref)
}
}
}
func (s *walkAllRefs) walkResponse(resp *spec.Response) {
if resp == nil {
return
}
resp.Ref = s.walkRef(resp.Ref)
s.walkSchema(resp.Schema)
}
func (s *walkAllRefs) walkOperation(op *spec.Operation) {
if op == nil {
return
}
s.walkParams(op.Parameters)
if op.Responses == nil {
return
}
s.walkResponse(op.Responses.Default)
for _, r := range op.Responses.StatusCodeResponses {
s.walkResponse(&r)
}
}
func (s *walkAllRefs) Start() {
for _, pathItem := range s.root.Paths.Paths {
s.walkParams(pathItem.Parameters)
s.walkOperation(pathItem.Delete)
s.walkOperation(pathItem.Get)
s.walkOperation(pathItem.Head)
s.walkOperation(pathItem.Options)
s.walkOperation(pathItem.Patch)
s.walkOperation(pathItem.Post)
s.walkOperation(pathItem.Put)
}
}
// FilterSpecByPaths remove unnecessary paths and unused definitions.
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
// First remove unwanted paths
prefixes := trie.New(keepPathPrefixes)
orgPaths := sp.Paths
if orgPaths == nil {
return
}
sp.Paths = &spec.Paths{
VendorExtensible: orgPaths.VendorExtensible,
Paths: map[string]spec.PathItem{},
}
for path, pathItem := range orgPaths.Paths {
if !prefixes.HasPrefix(path) {
continue
}
sp.Paths.Paths[path] = pathItem
}
// Walk all references to find all definition references.
usedDefinitions := map[string]bool{}
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
if ref.String() != "" {
refStr := ref.String()
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
usedDefinitions[refStr[len(DEFINITION_PREFIX):]] = true
}
}
return ref
}, sp).Start()
// Remove unused definitions
orgDefinitions := sp.Definitions
sp.Definitions = spec.Definitions{}
for k, v := range orgDefinitions {
if usedDefinitions[k] {
sp.Definitions[k] = v
}
}
}
func equalSchemaMap(s1, s2 map[string]spec.Schema) bool {
if len(s1) != len(s2) {
return false
}
for k, v := range s1 {
v2, found := s2[k]
if !found {
return false
}
if !EqualSchema(&v, &v2) {
return false
}
}
return true
}
func equalSchemaArray(s1, s2 []spec.Schema) bool {
if s1 == nil || s2 == nil {
return s1 == nil && s2 == nil
}
if len(s1) != len(s2) {
return false
}
for _, v1 := range s1 {
found := false
for _, v2 := range s2 {
if EqualSchema(&v1, &v2) {
found = true
break
}
}
if !found {
return false
}
}
for _, v2 := range s2 {
found := false
for _, v1 := range s1 {
if EqualSchema(&v1, &v2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func equalSchemaOrBool(s1, s2 *spec.SchemaOrBool) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if s1.Allows != s2.Allows {
return false
}
if !EqualSchema(s1.Schema, s2.Schema) {
return false
}
return true
}
func equalSchemaOrArray(s1, s2 *spec.SchemaOrArray) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if !EqualSchema(s1.Schema, s2.Schema) {
return false
}
if !equalSchemaArray(s1.Schemas, s2.Schemas) {
return false
}
return true
}
func equalStringArray(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for _, v1 := range s1 {
found := false
for _, v2 := range s2 {
if v1 == v2 {
found = true
break
}
}
if !found {
return false
}
}
for _, v2 := range s2 {
found := false
for _, v1 := range s1 {
if v1 == v2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func equalFloatPointer(s1, s2 *float64) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
return *s1 == *s2
}
func equalIntPointer(s1, s2 *int64) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
return *s1 == *s2
}
// EqualSchema returns true if models have the same properties and references
// even if they have different documentation.
func EqualSchema(s1, s2 *spec.Schema) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if s1.Ref.String() != s2.Ref.String() {
return false
}
if !equalSchemaMap(s1.Definitions, s2.Definitions) {
return false
}
if !equalSchemaMap(s1.Properties, s2.Properties) {
fmt.Println("Not equal props")
return false
}
if !equalSchemaMap(s1.PatternProperties, s2.PatternProperties) {
return false
}
if !equalSchemaArray(s1.AllOf, s2.AllOf) {
return false
}
if !equalSchemaArray(s1.AnyOf, s2.AnyOf) {
return false
}
if !equalSchemaArray(s1.OneOf, s2.OneOf) {
return false
}
if !EqualSchema(s1.Not, s2.Not) {
return false
}
if !equalSchemaOrBool(s1.AdditionalProperties, s2.AdditionalProperties) {
return false
}
if !equalSchemaOrBool(s1.AdditionalItems, s2.AdditionalItems) {
return false
}
if !equalSchemaOrArray(s1.Items, s2.Items) {
return false
}
if !equalStringArray(s1.Type, s2.Type) {
return false
}
if s1.Format != s2.Format {
return false
}
if !equalFloatPointer(s1.Minimum, s2.Minimum) {
return false
}
if !equalFloatPointer(s1.Maximum, s2.Maximum) {
return false
}
if s1.ExclusiveMaximum != s2.ExclusiveMaximum {
return false
}
if s1.ExclusiveMinimum != s2.ExclusiveMinimum {
return false
}
if !equalFloatPointer(s1.MultipleOf, s2.MultipleOf) {
return false
}
if !equalIntPointer(s1.MaxLength, s2.MaxLength) {
return false
}
if !equalIntPointer(s1.MinLength, s2.MinLength) {
return false
}
if !equalIntPointer(s1.MaxItems, s2.MaxItems) {
return false
}
if !equalIntPointer(s1.MinItems, s2.MinItems) {
return false
}
if s1.Pattern != s2.Pattern {
return false
}
if s1.UniqueItems != s2.UniqueItems {
return false
}
if !equalIntPointer(s1.MaxProperties, s2.MaxProperties) {
return false
}
if !equalIntPointer(s1.MinProperties, s2.MinProperties) {
return false
}
if !equalStringArray(s1.Required, s2.Required) {
return false
}
return len(s1.Enum) == 0 && len(s2.Enum) == 0 && len(s1.Dependencies) == 0 && len(s2.Dependencies) == 0
}
func renameDefinition(s *spec.Swagger, old, new string) {
old_ref := DEFINITION_PREFIX + old
new_ref := DEFINITION_PREFIX + new
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
if ref.String() == old_ref {
return spec.MustCreateRef(new_ref)
}
return ref
}, s).Start()
s.Definitions[new] = s.Definitions[old]
delete(s.Definitions, old)
}
// Copy paths and definitions from source to dest, rename definitions if needed.
// dest will be mutated, and source will not be changed.
func MergeSpecs(dest, source *spec.Swagger) error {
source, err := CloneSpec(source)
if err != nil {
return err
}
for k, v := range source.Paths.Paths {
if _, found := dest.Paths.Paths[k]; found {
return fmt.Errorf("Unable to merge: Duplicated path %s", k)
}
dest.Paths.Paths[k] = v
}
usedNames := map[string]bool{}
for k := range dest.Definitions {
usedNames[k] = true
}
type Rename struct {
from, to string
}
renames := []Rename{}
for k, v := range source.Definitions {
v2, found := dest.Definitions[k]
if found || usedNames[k] {
if found && EqualSchema(&v, &v2) {
continue
}
i := 2
newName := fmt.Sprintf("%s_v%d", k, i)
for usedNames[newName] {
i += 1
newName = fmt.Sprintf("%s_v%d", k, i)
}
renames = append(renames, Rename{from: k, to: newName})
usedNames[newName] = true
} else {
usedNames[k] = true
}
}
for _, r := range renames {
renameDefinition(source, r.from, r.to)
}
for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found {
dest.Definitions[k] = v
}
}
return nil
}
// Clone OpenAPI spec
func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) {
if ret, err := cloner.DeepCopy(source); err != nil {
return nil, err
} else {
return ret.(*spec.Swagger), nil
}
}

View File

@ -1,520 +0,0 @@
/*
Copyright 2017 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 (
"testing"
"github.com/ghodss/yaml"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestFilterSpecs(t *testing.T) {
var spec1, spec1_filtered *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1_filtered)
assert := assert.New(t)
FilterSpecByPaths(spec1, []string{"/test"})
assert.Equal(spec1_filtered, spec1)
}
func TestMergeSpecsSimple(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(expected, spec1)
}
func TestMergeSpecsReuseModel(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "This status has another description"
InvalidInput:
type: "string"
format: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(expected, spec1)
}
func TestMergeSpecsRenameModel(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
InvalidInput:
type: "string"
format: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test_v2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
Test_v2:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
InvalidInput:
type: "string"
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
expected_yaml, _ := yaml.Marshal(expected)
spec1_yaml, _ := yaml.Marshal(spec1)
assert.Equal(string(expected_yaml), string(spec1_yaml))
}

View File

@ -1,163 +0,0 @@
/*
Copyright 2017 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"
"encoding/json"
"fmt"
"mime"
"net/http"
"strings"
"time"
"github.com/NYTimes/gziphandler"
"github.com/go-openapi/spec"
"github.com/golang/protobuf/proto"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
"gopkg.in/yaml.v2"
genericmux "k8s.io/apiserver/pkg/server/mux"
)
type OpenAPIService struct {
orgSpec *spec.Swagger
specBytes []byte
specPb []byte
specPbGz []byte
lastModified time.Time
updateHooks []func(*http.Request)
}
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
func RegisterOpenAPIService(openapiSpec *spec.Swagger, servePath string, mux *genericmux.PathRecorderMux) (*OpenAPIService, error) {
if !strings.HasSuffix(servePath, JSON_EXT) {
return nil, fmt.Errorf("Serving path must ends with \"%s\".", JSON_EXT)
}
servePathBase := servePath[:len(servePath)-len(JSON_EXT)]
o := OpenAPIService{}
if err := o.UpdateSpec(openapiSpec); err != nil {
return nil, err
}
mime.AddExtensionType(".json", MIME_JSON)
mime.AddExtensionType(".pb-v1", MIME_PB)
mime.AddExtensionType(".gz", MIME_PB_GZ)
type fileInfo struct {
ext string
getData func() []byte
}
files := []fileInfo{
{".json", o.getSwaggerBytes},
{"-2.0.0.json", o.getSwaggerBytes},
{"-2.0.0.pb-v1", o.getSwaggerPbBytes},
{"-2.0.0.pb-v1.gz", o.getSwaggerPbGzBytes},
}
for _, file := range files {
path := servePathBase + file.ext
getData := file.getData
mux.Handle(path, gziphandler.GzipHandler(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Path not found!"))
return
}
o.update(r)
data := getData()
etag := computeEtag(data)
w.Header().Set("Etag", etag)
// ServeContent will take care of caching using eTag.
http.ServeContent(w, r, path, o.lastModified, bytes.NewReader(data))
}),
))
}
return &o, nil
}
func (o *OpenAPIService) getSwaggerBytes() []byte {
return o.specBytes
}
func (o *OpenAPIService) getSwaggerPbBytes() []byte {
return o.specPb
}
func (o *OpenAPIService) getSwaggerPbGzBytes() []byte {
return o.specPbGz
}
func (o *OpenAPIService) GetSpec() *spec.Swagger {
return o.orgSpec
}
func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
o.orgSpec = openapiSpec
o.specBytes, err = json.MarshalIndent(openapiSpec, " ", " ")
if err != nil {
return err
}
o.specPb, err = toProtoBinary(o.specBytes)
if err != nil {
return err
}
o.specPbGz = toGzip(o.specPb)
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()
}
// Adds an update hook to be called on each spec request. The hook is responsible
// to call UpdateSpec method.
func (o *OpenAPIService) AddUpdateHook(hook func(*http.Request)) {
o.updateHooks = append(o.updateHooks, hook)
}
func (o *OpenAPIService) update(r *http.Request) {
for _, h := range o.updateHooks {
h(r)
}
}

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

@ -27,14 +27,14 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/openapi:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/routes/data/swagger:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/etcd/metrics:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/handler:go_default_library",
],
)

View File

@ -18,30 +18,22 @@ 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) *apiserveropenapi.OpenAPIService {
openapiSpec, err := apiserveropenapi.BuildSwaggerSpec(c.RegisteredWebServices(), oa.Config)
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
_, 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)
return nil
}
service, err := apiserveropenapi.RegisterOpenAPIService(openapiSpec, "/swagger.json", mux)
if err != nil {
glog.Fatalf("Failed to register open api spec for root: %v", err)
return nil
}
return service
}

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
}