Merge pull request #71223 from sttts/sttts-openapi-aggreation-without-clone

openapi-aggregation: speed up merging from 1 sec to 50-100 ms
pull/564/head
Kubernetes Prow Robot 2019-02-11 10:30:56 -08:00 committed by GitHub
commit 6912bbb153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 96435 additions and 95692 deletions

28
Godeps/Godeps.json generated
View File

@ -4099,59 +4099,59 @@
},
{
"ImportPath": "k8s.io/kube-openapi/cmd/openapi-gen",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/cmd/openapi-gen/args",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/aggregator",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/common",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/generators",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/generators/rules",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/schemaconv",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto/testing",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto/validation",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/sets",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/repo-infra/kazel",

File diff suppressed because it is too large Load Diff

View File

@ -339,10 +339,9 @@ kube::util::create-fake-git-tree() {
kube::util::godep_restored() {
local -r godeps_json=${1:-Godeps/Godeps.json}
local -r gopath=${2:-${GOPATH%:*}}
if ! which jq &>/dev/null; then
echo "jq not found. Please install." 1>&2
return 1
fi
kube::util::require-jq
local root
local old_rev=""
while read path rev; do
@ -461,7 +460,7 @@ kube::util::base_ref() {
fi
full_branch="$(kube::util::git_upstream_remote_name)/${git_branch}"
# make sure the branch is valid, otherwise the check will pass erroneously.
if ! git describe "${full_branch}" >/dev/null; then
# abort!
@ -479,7 +478,7 @@ kube::util::has_changes() {
local -r git_branch=$1
local -r pattern=$2
local -r not_pattern=${3:-totallyimpossiblepattern}
local base_ref=$(kube::util::base_ref "${git_branch}")
echo "Checking for '${pattern}' changes against '${base_ref}'"
@ -792,6 +791,15 @@ function kube::util::check-file-in-alphabetical-order {
fi
}
# kube::util::require-jq
# Checks whether jq is installed.
function kube::util::require-jq {
if ! which jq &>/dev/null; then
echo "jq not found. Please install." 1>&2
return 1
fi
}
# Some useful colors.
if [[ -z "${color_start-}" ]]; then
declare -r color_start="\033["

View File

@ -25,6 +25,7 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
OPENAPI_ROOT_DIR="${KUBE_ROOT}/api/openapi-spec"
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::util::require-jq
kube::golang::setup_env
make -C "${KUBE_ROOT}" WHAT=cmd/kube-apiserver
@ -83,7 +84,7 @@ fi
kube::log::status "Updating " ${OPENAPI_ROOT_DIR}
curl -w "\n" -fs "${API_HOST}:${API_PORT}/openapi/v2" > "${OPENAPI_ROOT_DIR}/swagger.json"
curl -w "\n" -fs "${API_HOST}:${API_PORT}/openapi/v2" | jq -S . > "${OPENAPI_ROOT_DIR}/swagger.json"
kube::log::status "SUCCESS"

View File

@ -1,7 +1,7 @@
package(default_visibility = ["//visibility:public"])
load("//build:code_generation.bzl", "gen_openapi", "openapi_deps")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
gen_openapi(
outs = ["zz_generated.openapi.go"],
@ -30,3 +30,13 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["openapi_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
],
)

View File

@ -0,0 +1,51 @@
/*
Copyright 2019 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"
"reflect"
"testing"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/util/diff"
)
func TestOpenAPIRoundtrip(t *testing.T) {
dummyRef := func(name string) spec.Ref { return spec.MustCreateRef("#/definitions/dummy") }
for name, value := range GetOpenAPIDefinitions(dummyRef) {
t.Run(name, func(t *testing.T) {
data, err := json.Marshal(value.Schema)
if err != nil {
t.Error(err)
return
}
roundTripped := spec.Schema{}
if err := json.Unmarshal(data, &roundTripped); err != nil {
t.Error(err)
return
}
if !reflect.DeepEqual(value.Schema, roundTripped) {
t.Errorf("unexpected diff (a=expected,b=roundtripped):\n%s", diff.ObjectReflectDiff(value.Schema, roundTripped))
return
}
})
}
}

View File

@ -2252,27 +2252,27 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/common",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/schemaconv",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer",

View File

@ -68,7 +68,7 @@ type JSON struct {
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSON) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return []string{}
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
@ -91,7 +91,7 @@ type JSONSchemaPropsOrArray struct {
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSONSchemaPropsOrArray) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return []string{}
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
@ -111,7 +111,7 @@ type JSONSchemaPropsOrBool struct {
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSONSchemaPropsOrBool) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return []string{}
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
@ -133,7 +133,7 @@ type JSONSchemaPropsOrStringArray struct {
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaType() []string {
// TODO: return actual types when anyOf is supported
return []string{}
return nil
}
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing

View File

@ -176,7 +176,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",

View File

@ -1944,31 +1944,31 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/common",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/schemaconv",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto/testing",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/client-go/discovery",

View File

@ -11,7 +11,6 @@ go_test(
srcs = ["openapi_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/openapi/testing:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",

View File

@ -108,6 +108,18 @@ func (s groupVersionKinds) Less(i, j int) bool {
return s[i].Group < s[j].Group
}
func (s groupVersionKinds) JSON() []interface{} {
j := []interface{}{}
for _, gvk := range s {
j = append(j, map[string]interface{}{
"group": gvk.Group,
"version": gvk.Version,
"kind": gvk.Kind,
})
}
return j
}
// DefinitionNamer is the type to customize OpenAPI definition name.
type DefinitionNamer struct {
typeGroupVersionKinds map[string]groupVersionKinds
@ -172,7 +184,7 @@ func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer {
func (d *DefinitionNamer) GetDefinitionName(name string) (string, spec.Extensions) {
if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok {
return friendlyName(name), spec.Extensions{
extensionGVK: []v1.GroupVersionKind(groupVersionKinds),
extensionGVK: groupVersionKinds.JSON(),
}
}
return friendlyName(name), nil

View File

@ -23,7 +23,6 @@ import (
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
openapitesting "k8s.io/apiserver/pkg/endpoints/openapi/testing"
)
@ -58,13 +57,13 @@ func TestGetDefinitionName(t *testing.T) {
namer := NewDefinitionNamer(s)
n, e := namer.GetDefinitionName(typePkgName)
assertEqual(t, typeFriendlyName, n)
assertEqual(t, e["x-kubernetes-group-version-kind"], []v1.GroupVersionKind{
{
Group: "test",
Version: "v1",
Kind: "TestType",
assertEqual(t, []interface{}{
map[string]interface{}{
"group": "test",
"version": "v1",
"kind": "TestType",
},
})
}, e["x-kubernetes-group-version-kind"])
n, e2 := namer.GetDefinitionName("test.com/another.Type")
assertEqual(t, "com.test.another.Type", n)
assertEqual(t, e2, spec.Extensions(nil))

View File

@ -89,18 +89,18 @@ func buildTestOpenAPIDefinition() kubeopenapi.OpenAPIDefinition {
},
VendorExtensible: openapi.VendorExtensible{
Extensions: openapi.Extensions{
"x-kubernetes-group-version-kind": []map[string]string{
{
"x-kubernetes-group-version-kind": []interface{}{
map[string]interface{}{
"group": "",
"version": "v1",
"kind": "Getter",
},
{
map[string]interface{}{
"group": "batch",
"version": "v1",
"kind": "Getter",
},
{
map[string]interface{}{
"group": "extensions",
"version": "v1",
"kind": "Getter",

View File

@ -44,8 +44,8 @@ func TestOpenAPIDefinitionsToProtoModels(t *testing.T) {
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-group-version-kind": []map[string]string{
{
"x-kubernetes-group-version-kind": []interface{}{
map[string]interface{}{
"group": "testgroup.k8s.io",
"version": "v1",
"kind": "Foo",

View File

@ -612,7 +612,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -976,7 +976,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -544,7 +544,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -1816,31 +1816,31 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/aggregator",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/common",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/schemaconv",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -196,13 +196,12 @@ func (s *specAggregator) buildOpenAPISpec() (specToReturn *spec.Swagger, err err
}
sortByPriority(specs)
for _, specInfo := range specs {
// TODO: Make kube-openapi.MergeSpec(s) accept nil or empty spec as destination and just clone the spec in that case.
if specToReturn == nil {
specToReturn, err = aggregator.CloneSpec(specInfo.spec)
if err != nil {
return nil, err
}
continue
specToReturn = &spec.Swagger{}
*specToReturn = *specInfo.spec
// Paths and Definitions are set by MergeSpecsIgnorePathConflict
specToReturn.Paths = nil
specToReturn.Definitions = nil
}
if err := aggregator.MergeSpecsIgnorePathConflict(specToReturn, specInfo.spec); err != nil {
return nil, err
@ -267,7 +266,7 @@ func (s *specAggregator) UpdateAPIServiceSpec(apiServiceName string, spec *spec.
// For APIServices (non-local) specs, only merge their /apis/ prefixed endpoint as it is the only paths
// proxy handler delegates.
if specInfo.apiService.Spec.Service != nil {
aggregator.FilterSpecByPaths(spec, []string{"/apis/"})
spec = aggregator.FilterSpecByPathsWithoutSideEffects(spec, []string{"/apis/"})
}
return s.tryUpdatingServiceSpecs(&openAPISpecInfo{

View File

@ -520,7 +520,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/integer",

View File

@ -544,7 +544,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -1772,27 +1772,27 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/builder",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/common",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/handler",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/schemaconv",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -1152,7 +1152,7 @@
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "ced9eb3070a5f1c548ef46e8dfe2a97c208d9f03"
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "k8s.io/utils/buffer",

View File

@ -2,7 +2,11 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["aggregator.go"],
srcs = [
"aggregator.go",
"mutating_walker.go",
"walker.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/kube-openapi/pkg/aggregator",
importpath = "k8s.io/kube-openapi/pkg/aggregator",
visibility = ["//visibility:public"],

View File

@ -17,7 +17,6 @@ limitations under the License.
package aggregator
import (
"encoding/json"
"fmt"
"reflect"
"strings"
@ -27,154 +26,14 @@ import (
"k8s.io/kube-openapi/pkg/util"
)
const (
definitionPrefix = "#/definitions/"
)
// Run a walkRefCallback method on all references of an OpenAPI spec
type referenceWalker 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
// Keep track of visited references
alreadyVisited map[string]bool
}
func walkOnAllReferences(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) {
walker := &referenceWalker{walkRefCallback: walkRef, root: sp, alreadyVisited: map[string]bool{}}
walker.Start()
}
func (s *referenceWalker) walkRef(ref spec.Ref) spec.Ref {
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 !s.alreadyVisited[refStr] && strings.HasPrefix(refStr, definitionPrefix) {
s.alreadyVisited[refStr] = true
k := refStr[len(definitionPrefix):]
def := s.root.Definitions[k]
s.walkSchema(&def)
// Make sure we don't assign to nil map
if s.root.Definitions == nil {
s.root.Definitions = spec.Definitions{}
}
s.root.Definitions[k] = def
}
return s.walkRefCallback(ref)
}
func (s *referenceWalker) walkSchema(schema *spec.Schema) {
if schema == nil {
return
}
schema.Ref = s.walkRef(schema.Ref)
for k, v := range schema.Definitions {
s.walkSchema(&v)
schema.Definitions[k] = v
}
for k, v := range schema.Properties {
s.walkSchema(&v)
schema.Properties[k] = v
}
for k, v := range schema.PatternProperties {
s.walkSchema(&v)
schema.PatternProperties[k] = v
}
for i := range schema.AllOf {
s.walkSchema(&schema.AllOf[i])
}
for i := range schema.AnyOf {
s.walkSchema(&schema.AnyOf[i])
}
for i := range schema.OneOf {
s.walkSchema(&schema.OneOf[i])
}
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 i := range schema.Items.Schemas {
s.walkSchema(&schema.Items.Schemas[i])
}
}
}
func (s *referenceWalker) 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 *referenceWalker) walkResponse(resp *spec.Response) {
if resp == nil {
return
}
resp.Ref = s.walkRef(resp.Ref)
s.walkSchema(resp.Schema)
}
func (s *referenceWalker) 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 *referenceWalker) Start() {
if s.root.Paths == nil {
return
}
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)
}
}
// usedDefinitionForSpec returns a map with all used definitions in the provided spec as keys and true as values.
func usedDefinitionForSpec(sp *spec.Swagger) map[string]bool {
func usedDefinitionForSpec(root *spec.Swagger) map[string]bool {
usedDefinitions := map[string]bool{}
walkOnAllReferences(func(ref spec.Ref) spec.Ref {
walkOnAllReferences(func(ref *spec.Ref) {
if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) {
usedDefinitions[refStr[len(definitionPrefix):]] = true
}
return ref
}, sp)
}, root)
return usedDefinitions
}
@ -182,6 +41,18 @@ func usedDefinitionForSpec(sp *spec.Swagger) map[string]bool {
// i.e. if a Path removed by this function, all definitions used by it and not used
// anywhere else will also be removed.
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
*sp = *FilterSpecByPathsWithoutSideEffects(sp, keepPathPrefixes)
}
// FilterSpecByPathsWithoutSideEffects removes unnecessary paths and definitions used by those paths.
// i.e. if a Path removed by this function, all definitions used by it and not used
// anywhere else will also be removed.
// It does not modify the input, but the output shares data structures with the input.
func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []string) *spec.Swagger {
if sp.Paths == nil {
return sp
}
// Walk all references to find all used definitions. This function
// want to only deal with unused definitions resulted from filtering paths.
// Thus a definition will be removed only if it has been used before but
@ -190,71 +61,100 @@ func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
// First remove unwanted paths
prefixes := util.NewTrie(keepPathPrefixes)
orgPaths := sp.Paths
if orgPaths == nil {
return
}
sp.Paths = &spec.Paths{
VendorExtensible: orgPaths.VendorExtensible,
ret := *sp
ret.Paths = &spec.Paths{
VendorExtensible: sp.Paths.VendorExtensible,
Paths: map[string]spec.PathItem{},
}
for path, pathItem := range orgPaths.Paths {
for path, pathItem := range sp.Paths.Paths {
if !prefixes.HasPrefix(path) {
continue
}
sp.Paths.Paths[path] = pathItem
ret.Paths.Paths[path] = pathItem
}
// Walk all references to find all definition references.
usedDefinitions := usedDefinitionForSpec(sp)
usedDefinitions := usedDefinitionForSpec(&ret)
// Remove unused definitions
orgDefinitions := sp.Definitions
sp.Definitions = spec.Definitions{}
for k, v := range orgDefinitions {
ret.Definitions = spec.Definitions{}
for k, v := range sp.Definitions {
if usedDefinitions[k] || !initialUsedDefinitions[k] {
sp.Definitions[k] = v
ret.Definitions[k] = v
}
}
return &ret
}
func renameDefinition(s *spec.Swagger, old, new string) {
oldRef := definitionPrefix + old
newRef := definitionPrefix + new
walkOnAllReferences(func(ref spec.Ref) spec.Ref {
if ref.String() == oldRef {
return spec.MustCreateRef(newRef)
type rename struct {
from, to string
}
// renameDefinition renames references, without mutating the input.
// The output might share data structures with the input.
func renameDefinition(s *spec.Swagger, renames map[string]string) *spec.Swagger {
refRenames := make(map[string]string, len(renames))
foundOne := false
for k, v := range renames {
refRenames[definitionPrefix+k] = definitionPrefix + v
if _, ok := s.Definitions[k]; ok {
foundOne = true
}
}
if !foundOne {
return s
}
ret := &spec.Swagger{}
*ret = *s
ret = replaceReferences(func(ref *spec.Ref) *spec.Ref {
refName := ref.String()
if newRef, found := refRenames[refName]; found {
ret := spec.MustCreateRef(newRef)
return &ret
}
return ref
}, s)
// Make sure we don't assign to nil map
if s.Definitions == nil {
s.Definitions = spec.Definitions{}
}, ret)
renamedDefinitions := make(spec.Definitions, len(ret.Definitions))
for k, v := range ret.Definitions {
if newRef, found := renames[k]; found {
k = newRef
}
renamedDefinitions[k] = v
}
s.Definitions[new] = s.Definitions[old]
delete(s.Definitions, old)
ret.Definitions = renamedDefinitions
return ret
}
// MergeSpecsIgnorePathConflict is the same as MergeSpecs except it will ignore any path
// conflicts by keeping the paths of destination. It will rename definition conflicts.
// The source is not mutated.
func MergeSpecsIgnorePathConflict(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, true)
}
// MergeSpecsFailOnDefinitionConflict is differ from MergeSpecs as it fails if there is
// a definition conflict.
// The source is not mutated.
func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, false, false)
}
// MergeSpecs copies paths and definitions from source to dest, rename definitions if needed.
// dest will be mutated, and source will not be changed. It will fail on path conflicts.
// The source is not mutated.
func MergeSpecs(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, false)
}
// mergeSpecs merged source into dest while resolving conflicts.
// The source is not mutated.
func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConflicts bool) (err error) {
specCloned := false
// Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
if source.Paths == nil {
// When a source spec does not have any path, that means none of the definitions
@ -279,12 +179,7 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
return nil
}
if hasConflictingPath {
source, err = CloneSpec(source)
if err != nil {
return err
}
specCloned = true
FilterSpecByPaths(source, keepPaths)
source = FilterSpecByPathsWithoutSideEffects(source, keepPaths)
}
}
// Check for model conflicts
@ -301,21 +196,11 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
}
if conflicts {
if !specCloned {
source, err = CloneSpec(source)
if err != nil {
return err
}
}
specCloned = true
usedNames := map[string]bool{}
for k := range dest.Definitions {
usedNames[k] = true
}
type Rename struct {
from, to string
}
renames := []Rename{}
renames := map[string]string{}
OUTERLOOP:
for k, v := range source.Definitions {
@ -334,7 +219,7 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
newName = fmt.Sprintf("%s_v%d", k, i)
v2, found = dest.Definitions[newName]
if found && reflect.DeepEqual(v, v2) {
renames = append(renames, Rename{from: k, to: newName})
renames[k] = newName
continue OUTERLOOP
}
}
@ -345,13 +230,11 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
newName = fmt.Sprintf("%s_v%d", k, i)
_, foundInSource = source.Definitions[newName]
}
renames = append(renames, Rename{from: k, to: newName})
renames[k] = newName
usedNames[newName] = true
}
}
for _, r := range renames {
renameDefinition(source, r.from, r.to)
}
source = renameDefinition(source, renames)
}
for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found {
@ -374,18 +257,3 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
}
return nil
}
// CloneSpec clones OpenAPI spec
func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) {
// TODO(mehdy): Find a faster way to clone an spec
bytes, err := json.Marshal(source)
if err != nil {
return nil, err
}
var ret spec.Swagger
err = json.Unmarshal(bytes, &ret)
if err != nil {
return nil, err
}
return &ret, nil
}

View File

@ -0,0 +1,498 @@
/*
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 aggregator
import (
_ "net/http/pprof"
"github.com/go-openapi/spec"
)
// Run a walkRefCallback method on all references of an OpenAPI spec, replacing the values.
type mutatingReferenceWalker struct {
// walkRefCallback will be called on each reference. Do not mutate the input, always create a copy first and return that.
walkRefCallback func(ref *spec.Ref) *spec.Ref
}
// replaceReferences rewrites the references without mutating the input.
// The output might share data with the input.
func replaceReferences(walkRef func(ref *spec.Ref) *spec.Ref, sp *spec.Swagger) *spec.Swagger {
walker := &mutatingReferenceWalker{walkRefCallback: walkRef}
return walker.Start(sp)
}
func (w *mutatingReferenceWalker) walkSchema(schema *spec.Schema) *spec.Schema {
if schema == nil {
return nil
}
orig := schema
clone := func() {
if orig == schema {
schema = &spec.Schema{}
*schema = *orig
}
}
if r := w.walkRefCallback(&schema.Ref); r != &schema.Ref {
clone()
schema.Ref = *r
}
definitionsCloned := false
for k, v := range schema.Definitions {
if s := w.walkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
schema.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
schema.Definitions[k2] = v2
}
}
schema.Definitions[k] = *s
}
}
propertiesCloned := false
for k, v := range schema.Properties {
if s := w.walkSchema(&v); s != &v {
if !propertiesCloned {
propertiesCloned = true
clone()
schema.Properties = make(map[string]spec.Schema, len(orig.Properties))
for k2, v2 := range orig.Properties {
schema.Properties[k2] = v2
}
}
schema.Properties[k] = *s
}
}
patternPropertiesCloned := false
for k, v := range schema.PatternProperties {
if s := w.walkSchema(&v); s != &v {
if !patternPropertiesCloned {
patternPropertiesCloned = true
clone()
schema.PatternProperties = make(map[string]spec.Schema, len(orig.PatternProperties))
for k2, v2 := range orig.PatternProperties {
schema.PatternProperties[k2] = v2
}
}
schema.PatternProperties[k] = *s
}
}
allOfCloned := false
for i := range schema.AllOf {
if s := w.walkSchema(&schema.AllOf[i]); s != &schema.AllOf[i] {
if !allOfCloned {
allOfCloned = true
clone()
schema.AllOf = make([]spec.Schema, len(orig.AllOf))
copy(schema.AllOf, orig.AllOf)
}
schema.AllOf[i] = *s
}
}
anyOfCloned := false
for i := range schema.AnyOf {
if s := w.walkSchema(&schema.AnyOf[i]); s != &schema.AnyOf[i] {
if !anyOfCloned {
anyOfCloned = true
clone()
schema.AnyOf = make([]spec.Schema, len(orig.AnyOf))
copy(schema.AnyOf, orig.AnyOf)
}
schema.AnyOf[i] = *s
}
}
oneOfCloned := false
for i := range schema.OneOf {
if s := w.walkSchema(&schema.OneOf[i]); s != &schema.OneOf[i] {
if !oneOfCloned {
oneOfCloned = true
clone()
schema.OneOf = make([]spec.Schema, len(orig.OneOf))
copy(schema.OneOf, orig.OneOf)
}
schema.OneOf[i] = *s
}
}
if schema.Not != nil {
if s := w.walkSchema(schema.Not); s != schema.Not {
clone()
schema.Not = s
}
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
if s := w.walkSchema(schema.AdditionalProperties.Schema); s != schema.AdditionalProperties.Schema {
clone()
schema.AdditionalProperties = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalProperties.Allows}
}
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
if s := w.walkSchema(schema.AdditionalItems.Schema); s != schema.AdditionalItems.Schema {
clone()
schema.AdditionalItems = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalItems.Allows}
}
}
if schema.Items != nil {
if schema.Items.Schema != nil {
if s := w.walkSchema(schema.Items.Schema); s != schema.Items.Schema {
clone()
schema.Items = &spec.SchemaOrArray{Schema: s}
}
} else {
itemsCloned := false
for i := range schema.Items.Schemas {
if s := w.walkSchema(&schema.Items.Schemas[i]); s != &schema.Items.Schemas[i] {
if !itemsCloned {
clone()
schema.Items = &spec.SchemaOrArray{
Schemas: make([]spec.Schema, len(orig.Items.Schemas)),
}
itemsCloned = true
copy(schema.Items.Schemas, orig.Items.Schemas)
}
schema.Items.Schemas[i] = *s
}
}
}
}
return schema
}
func (w *mutatingReferenceWalker) walkParameter(param *spec.Parameter) *spec.Parameter {
if param == nil {
return nil
}
orig := param
cloned := false
clone := func() {
if !cloned {
cloned = true
param = &spec.Parameter{}
*param = *orig
}
}
if r := w.walkRefCallback(&param.Ref); r != &param.Ref {
clone()
param.Ref = *r
}
if s := w.walkSchema(param.Schema); s != param.Schema {
clone()
param.Schema = s
}
if param.Items != nil {
if r := w.walkRefCallback(&param.Items.Ref); r != &param.Items.Ref {
param.Items.Ref = *r
}
}
return param
}
func (w *mutatingReferenceWalker) walkParameters(params []spec.Parameter) ([]spec.Parameter, bool) {
if params == nil {
return nil, false
}
orig := params
cloned := false
clone := func() {
if !cloned {
cloned = true
params = make([]spec.Parameter, len(params))
copy(params, orig)
}
}
for i := range params {
if s := w.walkParameter(&params[i]); s != &params[i] {
clone()
params[i] = *s
}
}
return params, cloned
}
func (w *mutatingReferenceWalker) walkResponse(resp *spec.Response) *spec.Response {
if resp == nil {
return nil
}
orig := resp
cloned := false
clone := func() {
if !cloned {
cloned = true
resp = &spec.Response{}
*resp = *orig
}
}
if r := w.walkRefCallback(&resp.Ref); r != &resp.Ref {
clone()
resp.Ref = *r
}
if s := w.walkSchema(resp.Schema); s != resp.Schema {
clone()
resp.Schema = s
}
return resp
}
func (w *mutatingReferenceWalker) walkResponses(resps *spec.Responses) *spec.Responses {
if resps == nil {
return nil
}
orig := resps
cloned := false
clone := func() {
if !cloned {
cloned = true
resps = &spec.Responses{}
*resps = *orig
}
}
if r := w.walkResponse(resps.ResponsesProps.Default); r != resps.ResponsesProps.Default {
clone()
resps.Default = r
}
responsesCloned := false
for k, v := range resps.ResponsesProps.StatusCodeResponses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
resps.ResponsesProps.StatusCodeResponses = make(map[int]spec.Response, len(orig.StatusCodeResponses))
for k2, v2 := range orig.StatusCodeResponses {
resps.ResponsesProps.StatusCodeResponses[k2] = v2
}
}
resps.ResponsesProps.StatusCodeResponses[k] = *r
}
}
return resps
}
func (w *mutatingReferenceWalker) walkOperation(op *spec.Operation) *spec.Operation {
if op == nil {
return nil
}
orig := op
cloned := false
clone := func() {
if !cloned {
cloned = true
op = &spec.Operation{}
*op = *orig
}
}
parametersCloned := false
for i := range op.Parameters {
if s := w.walkParameter(&op.Parameters[i]); s != &op.Parameters[i] {
if !parametersCloned {
parametersCloned = true
clone()
op.Parameters = make([]spec.Parameter, len(orig.Parameters))
copy(op.Parameters, orig.Parameters)
}
op.Parameters[i] = *s
}
}
if r := w.walkResponses(op.Responses); r != op.Responses {
clone()
op.Responses = r
}
return op
}
func (w *mutatingReferenceWalker) walkPathItem(pathItem *spec.PathItem) *spec.PathItem {
if pathItem == nil {
return nil
}
orig := pathItem
cloned := false
clone := func() {
if !cloned {
cloned = true
pathItem = &spec.PathItem{}
*pathItem = *orig
}
}
if p, changed := w.walkParameters(pathItem.Parameters); changed {
clone()
pathItem.Parameters = p
}
if op := w.walkOperation(pathItem.Get); op != pathItem.Get {
clone()
pathItem.Get = op
}
if op := w.walkOperation(pathItem.Head); op != pathItem.Head {
clone()
pathItem.Head = op
}
if op := w.walkOperation(pathItem.Delete); op != pathItem.Delete {
clone()
pathItem.Delete = op
}
if op := w.walkOperation(pathItem.Options); op != pathItem.Options {
clone()
pathItem.Options = op
}
if op := w.walkOperation(pathItem.Patch); op != pathItem.Patch {
clone()
pathItem.Patch = op
}
if op := w.walkOperation(pathItem.Post); op != pathItem.Post {
clone()
pathItem.Post = op
}
if op := w.walkOperation(pathItem.Put); op != pathItem.Put {
clone()
pathItem.Put = op
}
return pathItem
}
func (w *mutatingReferenceWalker) walkPaths(paths *spec.Paths) *spec.Paths {
if paths == nil {
return nil
}
orig := paths
cloned := false
clone := func() {
if !cloned {
cloned = true
paths = &spec.Paths{}
*paths = *orig
}
}
pathsCloned := false
for k, v := range paths.Paths {
if p := w.walkPathItem(&v); p != &v {
if !pathsCloned {
pathsCloned = true
clone()
paths.Paths = make(map[string]spec.PathItem, len(orig.Paths))
for k2, v2 := range orig.Paths {
paths.Paths[k2] = v2
}
}
paths.Paths[k] = *p
}
}
return paths
}
func (w *mutatingReferenceWalker) Start(swagger *spec.Swagger) *spec.Swagger {
if swagger == nil {
return nil
}
orig := swagger
cloned := false
clone := func() {
if !cloned {
cloned = true
swagger = &spec.Swagger{}
*swagger = *orig
}
}
parametersCloned := false
for k, v := range swagger.Parameters {
if p := w.walkParameter(&v); p != &v {
if !parametersCloned {
parametersCloned = true
clone()
swagger.Parameters = make(map[string]spec.Parameter, len(orig.Parameters))
for k2, v2 := range orig.Parameters {
swagger.Parameters[k2] = v2
}
}
swagger.Parameters[k] = *p
}
}
responsesCloned := false
for k, v := range swagger.Responses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
swagger.Responses = make(map[string]spec.Response, len(orig.Responses))
for k2, v2 := range orig.Responses {
swagger.Responses[k2] = v2
}
}
swagger.Responses[k] = *r
}
}
definitionsCloned := false
for k, v := range swagger.Definitions {
if s := w.walkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
swagger.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
swagger.Definitions[k2] = v2
}
}
swagger.Definitions[k] = *s
}
}
if swagger.Paths != nil {
if p := w.walkPaths(swagger.Paths); p != swagger.Paths {
clone()
swagger.Paths = p
}
}
return swagger
}

162
vendor/k8s.io/kube-openapi/pkg/aggregator/walker.go generated vendored Normal file
View File

@ -0,0 +1,162 @@
/*
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 aggregator
import (
"strings"
"github.com/go-openapi/spec"
)
const (
definitionPrefix = "#/definitions/"
)
// Run a readonlyReferenceWalker method on all references of an OpenAPI spec
type readonlyReferenceWalker struct {
// walkRefCallback will be called on each reference. The input will never be nil.
walkRefCallback func(ref *spec.Ref)
// The spec to walk through.
root *spec.Swagger
}
// walkOnAllReferences recursively walks on all references, while following references into definitions.
// it calls walkRef on each found reference.
func walkOnAllReferences(walkRef func(ref *spec.Ref), root *spec.Swagger) {
alreadyVisited := map[string]bool{}
walker := &readonlyReferenceWalker{
root: root,
}
walker.walkRefCallback = func(ref *spec.Ref) {
walkRef(ref)
refStr := ref.String()
if refStr == "" || !strings.HasPrefix(refStr, definitionPrefix) {
return
}
defName := refStr[len(definitionPrefix):]
if _, found := root.Definitions[defName]; found && !alreadyVisited[refStr] {
alreadyVisited[refStr] = true
def := root.Definitions[defName]
walker.walkSchema(&def)
}
}
walker.Start()
}
func (s *readonlyReferenceWalker) walkSchema(schema *spec.Schema) {
if schema == nil {
return
}
s.walkRefCallback(&schema.Ref)
var v *spec.Schema
if len(schema.Definitions)+len(schema.Properties)+len(schema.PatternProperties) > 0 {
v = &spec.Schema{}
}
for k := range schema.Definitions {
*v = schema.Definitions[k]
s.walkSchema(v)
}
for k := range schema.Properties {
*v = schema.Properties[k]
s.walkSchema(v)
}
for k := range schema.PatternProperties {
*v = schema.PatternProperties[k]
s.walkSchema(v)
}
for i := range schema.AllOf {
s.walkSchema(&schema.AllOf[i])
}
for i := range schema.AnyOf {
s.walkSchema(&schema.AnyOf[i])
}
for i := range schema.OneOf {
s.walkSchema(&schema.OneOf[i])
}
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 i := range schema.Items.Schemas {
s.walkSchema(&schema.Items.Schemas[i])
}
}
}
func (s *readonlyReferenceWalker) walkParams(params []spec.Parameter) {
if params == nil {
return
}
for _, param := range params {
s.walkRefCallback(&param.Ref)
s.walkSchema(param.Schema)
if param.Items != nil {
s.walkRefCallback(&param.Items.Ref)
}
}
}
func (s *readonlyReferenceWalker) walkResponse(resp *spec.Response) {
if resp == nil {
return
}
s.walkRefCallback(&resp.Ref)
s.walkSchema(resp.Schema)
}
func (s *readonlyReferenceWalker) 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 *readonlyReferenceWalker) Start() {
if s.root.Paths == nil {
return
}
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)
}
}

View File

@ -171,7 +171,7 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
for _, t := range c.Order {
err := newOpenAPITypeWriter(sw).generateCall(t)
err := newOpenAPITypeWriter(sw, c).generateCall(t)
if err != nil {
return err
}
@ -186,7 +186,7 @@ func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
klog.V(5).Infof("generating for type %v", t)
sw := generator.NewSnippetWriter(w, c, "$", "$")
err := newOpenAPITypeWriter(sw).generate(t)
err := newOpenAPITypeWriter(sw, c).generate(t)
if err != nil {
return err
}
@ -221,13 +221,15 @@ func shouldInlineMembers(m *types.Member) bool {
type openAPITypeWriter struct {
*generator.SnippetWriter
context *generator.Context
refTypes map[string]*types.Type
GetDefinitionInterface *types.Type
}
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter {
func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter {
return openAPITypeWriter{
SnippetWriter: sw,
context: c,
refTypes: map[string]*types.Type{},
}
}
@ -337,12 +339,22 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
g.generateDescription(t.CommentLines)
g.Do("Type: []string{\"object\"},\n", nil)
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
required, err := g.generateMembers(t, []string{})
// write members into a temporary buffer, in order to postpone writing out the Properties field. We only do
// that if it is not empty.
propertiesBuf := bytes.Buffer{}
bsw := g
bsw.SnippetWriter = generator.NewSnippetWriter(&propertiesBuf, g.context, "$", "$")
required, err := bsw.generateMembers(t, []string{})
if err != nil {
return err
}
g.Do("},\n", nil)
if propertiesBuf.Len() > 0 {
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
g.Do(strings.Replace(propertiesBuf.String(), "$", "$\"$\"$", -1), nil) // escape $ (used as delimiter of the templates)
g.Do("},\n", nil)
}
if len(required) > 0 {
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
}
@ -351,13 +363,14 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
return err
}
g.Do("},\n", nil)
g.Do("Dependencies: []string{\n", args)
// Map order is undefined, sort them or we may get a different file generated each time.
keys := []string{}
for k := range g.refTypes {
keys = append(keys, k)
}
sort.Strings(keys)
deps := []string{}
for _, k := range keys {
v := g.refTypes[k]
if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" {
@ -365,9 +378,16 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
// Will eliminate special case of time.Time
continue
}
g.Do("\"$.$\",", k)
deps = append(deps, k)
}
g.Do("},\n}\n}\n\n", nil)
if len(deps) > 0 {
g.Do("Dependencies: []string{\n", args)
for _, k := range deps {
g.Do("\"$.$\",", k)
}
g.Do("},\n", nil)
}
g.Do("}\n}\n\n", nil)
}
return nil
}
@ -409,7 +429,7 @@ func (g openAPITypeWriter) emitExtensions(extensions []extension) {
for _, extension := range extensions {
g.Do("\"$.$\": ", extension.xName)
if extension.hasMultipleValues() {
g.Do("[]string{\n", nil)
g.Do("[]interface{}{\n", nil)
}
for _, value := range extension.values {
g.Do("\"$.$\",\n", value)
@ -562,7 +582,7 @@ func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
}
g.Do("Type: []string{\"object\"},\n", nil)
g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
g.Do("AdditionalProperties: &spec.SchemaOrBool{\nAllows: true,\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
if typeString != "" {
g.generateSimpleProperty(typeString, format)

View File

@ -13,6 +13,7 @@ go_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/github.com/json-iterator/go:go_default_library",
"//vendor/github.com/munnerz/goautoneg:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/builder:go_default_library",

View File

@ -20,7 +20,6 @@ import (
"bytes"
"compress/gzip"
"crypto/sha512"
"encoding/json"
"fmt"
"mime"
"net/http"
@ -28,15 +27,15 @@ import (
"sync"
"time"
yaml "gopkg.in/yaml.v2"
"github.com/NYTimes/gziphandler"
restful "github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"github.com/golang/protobuf/proto"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
"github.com/json-iterator/go"
"github.com/munnerz/goautoneg"
yaml "gopkg.in/yaml.v2"
"k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/common"
@ -78,6 +77,15 @@ func computeETag(data []byte) string {
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
}
// NewOpenAPIService builds an OpenAPIService starting with the given spec.
func NewOpenAPIService(spec *spec.Swagger) (*OpenAPIService, error) {
o := &OpenAPIService{}
if err := o.UpdateSpec(spec); err != nil {
return nil, err
}
return o, nil
}
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process
// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU.
@ -89,7 +97,11 @@ func BuildAndRegisterOpenAPIService(servePath string, webServices []*restful.Web
if err != nil {
return nil, err
}
return RegisterOpenAPIService(spec, servePath, handler)
o, err := NewOpenAPIService(spec)
if err != nil {
return nil, err
}
return o, o.RegisterOpenAPIService(servePath, handler)
}
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
@ -99,18 +111,30 @@ func BuildAndRegisterOpenAPIService(servePath string, webServices []*restful.Web
// RegisterOpenAPIService registers a handler to provide access to provided swagger spec.
// Note: servePath should end with ".json" as the RegisterOpenAPIService assume it is serving a
// json file and will also serve .pb and .gz files.
func RegisterOpenAPIService(openapiSpec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
//
// Deprecated: use OpenAPIService.RegisterOpenAPIService instead.
func RegisterOpenAPIService(spec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
o, err := NewOpenAPIService(spec)
if err != nil {
return nil, err
}
return o, o.RegisterOpenAPIService(servePath, handler)
}
// NOTE: [DEPRECATION] We will announce deprecation for format-separated endpoints for OpenAPI spec,
// and switch to a single /openapi/v2 endpoint in Kubernetes 1.10. The design doc and deprecation process
// are tracked at: https://docs.google.com/document/d/19lEqE9lc4yHJ3WJAJxS_G7TcORIJXGHyq3wpwcH28nU.
//
// RegisterOpenAPIService registers a handler to provide access to provided swagger spec.
// Note: servePath should end with ".json" as the RegisterOpenAPIService assume it is serving a
// json file and will also serve .pb and .gz files.
func (o *OpenAPIService) RegisterOpenAPIService(servePath string, handler common.PathHandler) error {
if !strings.HasSuffix(servePath, jsonExt) {
return nil, fmt.Errorf("serving path must end with \"%s\"", jsonExt)
return fmt.Errorf("serving path must end with \"%s\"", jsonExt)
}
servePathBase := strings.TrimSuffix(servePath, jsonExt)
o := OpenAPIService{}
if err := o.UpdateSpec(openapiSpec); err != nil {
return nil, err
}
type fileInfo struct {
ext string
getDataAndETag func() ([]byte, string, time.Time)
@ -137,7 +161,7 @@ func RegisterOpenAPIService(openapiSpec *spec.Swagger, servePath string, handler
))
}
return &o, nil
return nil
}
func (o *OpenAPIService) getSwaggerBytes() ([]byte, string, time.Time) {
@ -159,11 +183,15 @@ func (o *OpenAPIService) getSwaggerPbGzBytes() ([]byte, string, time.Time) {
}
func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
specBytes, err := json.MarshalIndent(openapiSpec, " ", " ")
specBytes, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(openapiSpec)
if err != nil {
return err
}
specPb, err := toProtoBinary(specBytes)
var json map[string]interface{}
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(specBytes, &json); err != nil {
return err
}
specPb, err := ToProtoBinary(json)
if err != nil {
return err
}
@ -189,13 +217,33 @@ func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
return nil
}
func toProtoBinary(spec []byte) ([]byte, error) {
var info yaml.MapSlice
err := yaml.Unmarshal(spec, &info)
if err != nil {
return nil, err
func jsonToYAML(j map[string]interface{}) yaml.MapSlice {
if j == nil {
return nil
}
document, err := openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
ret := make(yaml.MapSlice, 0, len(j))
for k, v := range j {
ret = append(ret, yaml.MapItem{k, jsonToYAMLValue(v)})
}
return ret
}
func jsonToYAMLValue(j interface{}) interface{} {
switch j := j.(type) {
case map[string]interface{}:
return jsonToYAML(j)
case []interface{}:
ret := make([]interface{}, len(j))
for i := range j {
ret[i] = jsonToYAMLValue(j[i])
}
return ret
}
return j
}
func ToProtoBinary(json map[string]interface{}) ([]byte, error) {
document, err := openapi_v2.NewDocument(jsonToYAML(json), compiler.NewContext("$root", nil))
if err != nil {
return nil, err
}
@ -211,12 +259,18 @@ func toGzip(data []byte) []byte {
}
// RegisterOpenAPIVersionedService registers a handler to provide access to provided swagger spec.
func RegisterOpenAPIVersionedService(openapiSpec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
o := OpenAPIService{}
if err := o.UpdateSpec(openapiSpec); err != nil {
//
// Deprecated: use OpenAPIService.RegisterOpenAPIVersionedService instead.
func RegisterOpenAPIVersionedService(spec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
o, err := NewOpenAPIService(spec)
if err != nil {
return nil, err
}
return o, o.RegisterOpenAPIVersionedService(servePath, handler)
}
// RegisterOpenAPIVersionedService registers a handler to provide access to provided swagger spec.
func (o *OpenAPIService) RegisterOpenAPIVersionedService(servePath string, handler common.PathHandler) error {
accepted := []struct {
Type string
SubType string
@ -257,7 +311,7 @@ func RegisterOpenAPIVersionedService(openapiSpec *spec.Swagger, servePath string
}),
))
return &o, nil
return nil
}
// BuildAndRegisterOpenAPIVersionedService builds the spec and registers a handler to provide access to it.
@ -267,5 +321,9 @@ func BuildAndRegisterOpenAPIVersionedService(servePath string, webServices []*re
if err != nil {
return nil, err
}
return RegisterOpenAPIVersionedService(spec, servePath, handler)
o, err := NewOpenAPIService(spec)
if err != nil {
return nil, err
}
return o, o.RegisterOpenAPIVersionedService(servePath, handler)
}

View File

@ -98,6 +98,13 @@ func (c *convert) insertTypeDef(name string, model proto.Schema) {
func (c *convert) makeRef(model proto.Schema) schema.TypeRef {
var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok {
if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
return schema.TypeRef{
Inlined: schema.Atom{
Untyped: &schema.Untyped{},
},
}
}
// reference a named type
_, n := path.Split(r.Reference())
tr.NamedType = &n

View File

@ -21,14 +21,39 @@ import (
"strings"
)
// ToCanonicalName converts Golang package/type name into canonical OpenAPI name.
// Examples:
// [DEPRECATED] ToCanonicalName converts Golang package/type canonical name into REST friendly OpenAPI name.
// This method is deprecated because it has a misleading name. Please use ToRESTFriendlyName
// instead
//
// NOTE: actually the "canonical name" in this method should be named "REST friendly OpenAPI name",
// which is different from "canonical name" defined in GetCanonicalTypeName. The "canonical name" defined
// in GetCanonicalTypeName means Go type names with full package path.
//
// Examples of REST friendly OpenAPI name:
// Input: k8s.io/api/core/v1.Pod
// Output: io.k8s.api.core.v1.Pod
//
// Input: k8s.io/api/core/v1
// Output: io.k8s.api.core.v1
//
// Input: csi.storage.k8s.io/v1alpha1.CSINodeInfo
// Output: io.k8s.storage.csi.v1alpha1.CSINodeInfo
func ToCanonicalName(name string) string {
return ToRESTFriendlyName(name)
}
// ToRESTFriendlyName converts Golang package/type canonical name into REST friendly OpenAPI name.
//
// Examples of REST friendly OpenAPI name:
// Input: k8s.io/api/core/v1.Pod
// Output: io.k8s.api.core.v1.Pod
//
// Input: k8s.io/api/core/v1
// Output: io.k8s.api.core.v1
//
// Input: csi.storage.k8s.io/v1alpha1.CSINodeInfo
// Output: io.k8s.storage.csi.v1alpha1.CSINodeInfo
func ToRESTFriendlyName(name string) string {
nameParts := strings.Split(name, "/")
// Reverse first part. e.g., io.k8s... instead of k8s.io...
if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
@ -41,9 +66,30 @@ func ToCanonicalName(name string) string {
return strings.Join(nameParts, ".")
}
// OpenAPICanonicalTypeNamer is an interface for models without Go type to seed model name.
//
// OpenAPI canonical names are Go type names with full package path, for uniquely indentifying
// a model / Go type. If a Go type is vendored from another package, only the path after "/vendor/"
// should be used. For custom resource definition (CRD), the canonical name is expected to be
// group/version.kind
//
// Examples of canonical name:
// Go type: k8s.io/kubernetes/pkg/apis/core.Pod
// CRD: csi.storage.k8s.io/v1alpha1.CSINodeInfo
//
// Example for vendored Go type:
// Original full path: k8s.io/kubernetes/vendor/k8s.io/api/core/v1.Pod
// Canonical name: k8s.io/api/core/v1.Pod
type OpenAPICanonicalTypeNamer interface {
OpenAPICanonicalTypeName() string
}
// GetCanonicalTypeName will find the canonical type name of a sample object, removing
// the "vendor" part of the path
func GetCanonicalTypeName(model interface{}) string {
if namer, ok := model.(OpenAPICanonicalTypeNamer); ok {
return namer.OpenAPICanonicalTypeName()
}
t := reflect.TypeOf(model)
if t.Kind() == reflect.Ptr {
t = t.Elem()