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:
commit
713755562e
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
reviewers:
|
||||
- yujuhong
|
||||
- gmarek
|
||||
- mbohlool
|
||||
- philips
|
||||
approvers:
|
||||
- mbohlool
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package openapi contains code to generate OpenAPI discovery spec (which
|
||||
// initial version of it also known as Swagger 2.0).
|
||||
// For more details: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
|
||||
package openapi
|
|
@ -1,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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,465 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/openapi"
|
||||
)
|
||||
|
||||
// setUp is a convenience function for setting up for (most) tests.
|
||||
func setUp(t *testing.T, fullMethods bool) (openAPI, *restful.Container, *assert.Assertions) {
|
||||
assert := assert.New(t)
|
||||
config, container := getConfig(fullMethods)
|
||||
return openAPI{
|
||||
config: config,
|
||||
swagger: &spec.Swagger{
|
||||
SwaggerProps: spec.SwaggerProps{
|
||||
Swagger: OpenAPIVersion,
|
||||
Definitions: spec.Definitions{},
|
||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
||||
Info: config.Info,
|
||||
},
|
||||
},
|
||||
}, container, assert
|
||||
}
|
||||
|
||||
func noOp(request *restful.Request, response *restful.Response) {}
|
||||
|
||||
// Test input
|
||||
type TestInput struct {
|
||||
// Name of the input
|
||||
Name string `json:"name,omitempty"`
|
||||
// ID of the input
|
||||
ID int `json:"id,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// Test output
|
||||
type TestOutput struct {
|
||||
// Name of the output
|
||||
Name string `json:"name,omitempty"`
|
||||
// Number of outputs
|
||||
Count int `json:"count,omitempty"`
|
||||
}
|
||||
|
||||
func (_ TestInput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
||||
schema := spec.Schema{}
|
||||
schema.Description = "Test input"
|
||||
schema.Properties = map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the input",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"id": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ID of the input",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"tags": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
schema.Extensions = spec.Extensions{"x-test": "test"}
|
||||
return &openapi.OpenAPIDefinition{
|
||||
Schema: schema,
|
||||
Dependencies: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (_ TestOutput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
||||
schema := spec.Schema{}
|
||||
schema.Description = "Test output"
|
||||
schema.Properties = map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the output",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"count": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Number of outputs",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
}
|
||||
return &openapi.OpenAPIDefinition{
|
||||
Schema: schema,
|
||||
Dependencies: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
var _ openapi.OpenAPIDefinitionGetter = TestInput{}
|
||||
var _ openapi.OpenAPIDefinitionGetter = TestOutput{}
|
||||
|
||||
func getTestRoute(ws *restful.WebService, method string, additionalParams bool, opPrefix string) *restful.RouteBuilder {
|
||||
ret := ws.Method(method).
|
||||
Path("/test/{path:*}").
|
||||
Doc(fmt.Sprintf("%s test input", method)).
|
||||
Operation(fmt.Sprintf("%s%sTestInput", method, opPrefix)).
|
||||
Produces(restful.MIME_JSON).
|
||||
Consumes(restful.MIME_JSON).
|
||||
Param(ws.PathParameter("path", "path to the resource").DataType("string")).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Reads(TestInput{}).
|
||||
Returns(200, "OK", TestOutput{}).
|
||||
Writes(TestOutput{}).
|
||||
To(noOp)
|
||||
if additionalParams {
|
||||
ret.Param(ws.HeaderParameter("hparam", "a test head parameter").DataType("integer"))
|
||||
ret.Param(ws.FormParameter("fparam", "a test form parameter").DataType("number"))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getConfig(fullMethods bool) (*openapi.Config, *restful.Container) {
|
||||
mux := http.NewServeMux()
|
||||
container := restful.NewContainer()
|
||||
container.ServeMux = mux
|
||||
ws := new(restful.WebService)
|
||||
ws.Path("/foo")
|
||||
ws.Route(getTestRoute(ws, "get", true, "foo"))
|
||||
if fullMethods {
|
||||
ws.Route(getTestRoute(ws, "post", false, "foo")).
|
||||
Route(getTestRoute(ws, "put", false, "foo")).
|
||||
Route(getTestRoute(ws, "head", false, "foo")).
|
||||
Route(getTestRoute(ws, "patch", false, "foo")).
|
||||
Route(getTestRoute(ws, "options", false, "foo")).
|
||||
Route(getTestRoute(ws, "delete", false, "foo"))
|
||||
|
||||
}
|
||||
ws.Path("/bar")
|
||||
ws.Route(getTestRoute(ws, "get", true, "bar"))
|
||||
if fullMethods {
|
||||
ws.Route(getTestRoute(ws, "post", false, "bar")).
|
||||
Route(getTestRoute(ws, "put", false, "bar")).
|
||||
Route(getTestRoute(ws, "head", false, "bar")).
|
||||
Route(getTestRoute(ws, "patch", false, "bar")).
|
||||
Route(getTestRoute(ws, "options", false, "bar")).
|
||||
Route(getTestRoute(ws, "delete", false, "bar"))
|
||||
|
||||
}
|
||||
container.Add(ws)
|
||||
return &openapi.Config{
|
||||
ProtocolList: []string{"https"},
|
||||
Info: &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "TestAPI",
|
||||
Description: "Test API",
|
||||
Version: "unversioned",
|
||||
},
|
||||
},
|
||||
GetDefinitions: func(_ openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
|
||||
return map[string]openapi.OpenAPIDefinition{
|
||||
"k8s.io/apiserver/pkg/server/openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
|
||||
"k8s.io/apiserver/pkg/server/openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
||||
// Bazel changes the package name, this is ok for testing, but we need to fix it if it happened
|
||||
// in the main code.
|
||||
"k8s.io/apiserver/pkg/server/openapi/go_default_test.TestInput": *TestInput{}.OpenAPIDefinition(),
|
||||
"k8s.io/apiserver/pkg/server/openapi/go_default_test.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
||||
}
|
||||
},
|
||||
GetDefinitionName: func(name string) (string, spec.Extensions) {
|
||||
friendlyName := name[strings.LastIndex(name, "/")+1:]
|
||||
if strings.HasPrefix(friendlyName, "go_default_test") {
|
||||
friendlyName = "openapi" + friendlyName[len("go_default_test"):]
|
||||
}
|
||||
return friendlyName, spec.Extensions{"x-test2": "test2"}
|
||||
},
|
||||
}, container
|
||||
}
|
||||
|
||||
func getTestOperation(method string, opPrefix string) *spec.Operation {
|
||||
return &spec.Operation{
|
||||
OperationProps: spec.OperationProps{
|
||||
Description: fmt.Sprintf("%s test input", method),
|
||||
Consumes: []string{"application/json"},
|
||||
Produces: []string{"application/json"},
|
||||
Schemes: []string{"https"},
|
||||
Parameters: []spec.Parameter{},
|
||||
Responses: getTestResponses(),
|
||||
ID: fmt.Sprintf("%s%sTestInput", method, opPrefix),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem {
|
||||
ret := spec.PathItem{
|
||||
PathItemProps: spec.PathItemProps{
|
||||
Get: getTestOperation("get", opPrefix),
|
||||
Parameters: getTestCommonParameters(),
|
||||
},
|
||||
}
|
||||
ret.Get.Parameters = getAdditionalTestParameters()
|
||||
if allMethods {
|
||||
ret.Put = getTestOperation("put", opPrefix)
|
||||
ret.Put.Parameters = getTestParameters()
|
||||
ret.Post = getTestOperation("post", opPrefix)
|
||||
ret.Post.Parameters = getTestParameters()
|
||||
ret.Head = getTestOperation("head", opPrefix)
|
||||
ret.Head.Parameters = getTestParameters()
|
||||
ret.Patch = getTestOperation("patch", opPrefix)
|
||||
ret.Patch.Parameters = getTestParameters()
|
||||
ret.Delete = getTestOperation("delete", opPrefix)
|
||||
ret.Delete.Parameters = getTestParameters()
|
||||
ret.Options = getTestOperation("options", opPrefix)
|
||||
ret.Options.Parameters = getTestParameters()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getRefSchema(ref string) *spec.Schema {
|
||||
return &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: spec.MustCreateRef(ref),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestResponses() *spec.Responses {
|
||||
ret := spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: map[int]spec.Response{},
|
||||
},
|
||||
}
|
||||
ret.StatusCodeResponses[200] = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: "OK",
|
||||
Schema: getRefSchema("#/definitions/openapi.TestOutput"),
|
||||
},
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
func getTestCommonParameters() []spec.Parameter {
|
||||
ret := make([]spec.Parameter, 2)
|
||||
ret[0] = spec.Parameter{
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "string",
|
||||
},
|
||||
ParamProps: spec.ParamProps{
|
||||
Description: "path to the resource",
|
||||
Name: "path",
|
||||
In: "path",
|
||||
Required: true,
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
ret[1] = spec.Parameter{
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "string",
|
||||
},
|
||||
ParamProps: spec.ParamProps{
|
||||
Description: "If 'true', then the output is pretty printed.",
|
||||
Name: "pretty",
|
||||
In: "query",
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getTestParameters() []spec.Parameter {
|
||||
ret := make([]spec.Parameter, 1)
|
||||
ret[0] = spec.Parameter{
|
||||
ParamProps: spec.ParamProps{
|
||||
Name: "body",
|
||||
In: "body",
|
||||
Required: true,
|
||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
||||
},
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getAdditionalTestParameters() []spec.Parameter {
|
||||
ret := make([]spec.Parameter, 3)
|
||||
ret[0] = spec.Parameter{
|
||||
ParamProps: spec.ParamProps{
|
||||
Name: "body",
|
||||
In: "body",
|
||||
Required: true,
|
||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
||||
},
|
||||
}
|
||||
ret[1] = spec.Parameter{
|
||||
ParamProps: spec.ParamProps{
|
||||
Name: "fparam",
|
||||
Description: "a test form parameter",
|
||||
In: "formData",
|
||||
},
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "number",
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
ret[2] = spec.Parameter{
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "integer",
|
||||
},
|
||||
ParamProps: spec.ParamProps{
|
||||
Description: "a test head parameter",
|
||||
Name: "hparam",
|
||||
In: "header",
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getTestInputDefinition() spec.Schema {
|
||||
return spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Test input",
|
||||
Properties: map[string]spec.Schema{
|
||||
"id": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ID of the input",
|
||||
Type: spec.StringOrArray{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the input",
|
||||
Type: spec.StringOrArray{"string"},
|
||||
},
|
||||
},
|
||||
"tags": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: spec.StringOrArray{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: spec.StringOrArray{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-test": "test",
|
||||
"x-test2": "test2",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestOutputDefinition() spec.Schema {
|
||||
return spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Test output",
|
||||
Properties: map[string]spec.Schema{
|
||||
"count": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Number of outputs",
|
||||
Type: spec.StringOrArray{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the output",
|
||||
Type: spec.StringOrArray{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-test2": "test2",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSwaggerSpec(t *testing.T) {
|
||||
o, container, assert := setUp(t, true)
|
||||
expected := &spec.Swagger{
|
||||
SwaggerProps: spec.SwaggerProps{
|
||||
Info: &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "TestAPI",
|
||||
Description: "Test API",
|
||||
Version: "unversioned",
|
||||
},
|
||||
},
|
||||
Swagger: "2.0",
|
||||
Paths: &spec.Paths{
|
||||
Paths: map[string]spec.PathItem{
|
||||
"/foo/test/{path}": getTestPathItem(true, "foo"),
|
||||
"/bar/test/{path}": getTestPathItem(true, "bar"),
|
||||
},
|
||||
},
|
||||
Definitions: spec.Definitions{
|
||||
"openapi.TestInput": getTestInputDefinition(),
|
||||
"openapi.TestOutput": getTestOutputDefinition(),
|
||||
},
|
||||
},
|
||||
}
|
||||
err := o.init(container.RegisteredWebServices())
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
expected_json, err := json.Marshal(expected)
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
actual_json, err := json.Marshal(o.swagger)
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(string(expected_json), string(actual_json))
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
type parameters []spec.Parameter
|
||||
|
||||
func (s parameters) Len() int { return len(s) }
|
||||
func (s parameters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// byNameIn used in sorting parameters by Name and In fields.
|
||||
type byNameIn struct {
|
||||
parameters
|
||||
}
|
||||
|
||||
func (s byNameIn) Less(i, j int) bool {
|
||||
return s.parameters[i].Name < s.parameters[j].Name || (s.parameters[i].Name == s.parameters[j].Name && s.parameters[i].In < s.parameters[j].In)
|
||||
}
|
||||
|
||||
// SortParameters sorts parameters by Name and In fields.
|
||||
func sortParameters(p []spec.Parameter) {
|
||||
sort.Sort(byNameIn{p})
|
||||
}
|
||||
|
||||
func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route {
|
||||
pathToRoutes := make(map[string][]restful.Route)
|
||||
for _, r := range routes {
|
||||
pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r)
|
||||
}
|
||||
return pathToRoutes
|
||||
}
|
||||
|
||||
func mapKeyFromParam(param *restful.Parameter) interface{} {
|
||||
return struct {
|
||||
Name string
|
||||
Kind int
|
||||
}{
|
||||
Name: param.Data().Name,
|
||||
Kind: param.Data().Kind,
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["trie.go"],
|
||||
tags = ["automanaged"],
|
||||
)
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package trie
|
||||
|
||||
// A simple trie implementation with Add an HasPrefix methods only.
|
||||
type Trie struct {
|
||||
children map[byte]*Trie
|
||||
wordTail bool
|
||||
word string
|
||||
}
|
||||
|
||||
// New creates a Trie and add all strings in the provided list to it.
|
||||
func New(list []string) Trie {
|
||||
ret := Trie{
|
||||
children: make(map[byte]*Trie),
|
||||
wordTail: false,
|
||||
}
|
||||
for _, v := range list {
|
||||
ret.Add(v)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Add adds a word to this trie
|
||||
func (t *Trie) Add(v string) {
|
||||
root := t
|
||||
for _, b := range []byte(v) {
|
||||
child, exists := root.children[b]
|
||||
if !exists {
|
||||
child = &Trie{
|
||||
children: make(map[byte]*Trie),
|
||||
wordTail: false,
|
||||
}
|
||||
root.children[b] = child
|
||||
}
|
||||
root = child
|
||||
}
|
||||
root.wordTail = true
|
||||
root.word = v
|
||||
}
|
||||
|
||||
// HasPrefix returns true of v has any of the prefixes stored in this trie.
|
||||
func (t *Trie) HasPrefix(v string) bool {
|
||||
_, has := t.GetPrefix(v)
|
||||
return has
|
||||
}
|
||||
|
||||
// GetPrefix is like HasPrefix but return the prefix in case of match or empty string otherwise.
|
||||
func (t *Trie) GetPrefix(v string) (string, bool) {
|
||||
root := t
|
||||
if root.wordTail {
|
||||
return root.word, true
|
||||
}
|
||||
for _, b := range []byte(v) {
|
||||
child, exists := root.children[b]
|
||||
if !exists {
|
||||
return "", false
|
||||
}
|
||||
if child.wordTail {
|
||||
return child.word, true
|
||||
}
|
||||
root = child
|
||||
}
|
||||
return "", false
|
||||
}
|
Loading…
Reference in New Issue