bump(k8s.io/kube-openapi): c12348ce28de40eed0136aa2b644d0ee0650e56c

pull/564/head
Dr. Stefan Schimanski 2019-02-01 13:46:01 +01:00
parent 2fe54a144b
commit 84f0629e95
10 changed files with 924 additions and 260 deletions

28
Godeps/Godeps.json generated
View File

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

View File

@ -2,7 +2,11 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library( go_library(
name = "go_default_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", importmap = "k8s.io/kubernetes/vendor/k8s.io/kube-openapi/pkg/aggregator",
importpath = "k8s.io/kube-openapi/pkg/aggregator", importpath = "k8s.io/kube-openapi/pkg/aggregator",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],

View File

@ -17,7 +17,6 @@ limitations under the License.
package aggregator package aggregator
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
@ -27,154 +26,14 @@ import (
"k8s.io/kube-openapi/pkg/util" "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. // 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{} 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) { if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) {
usedDefinitions[refStr[len(definitionPrefix):]] = true usedDefinitions[refStr[len(definitionPrefix):]] = true
} }
return ref }, root)
}, sp)
return usedDefinitions 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 // i.e. if a Path removed by this function, all definitions used by it and not used
// anywhere else will also be removed. // anywhere else will also be removed.
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) { 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 // Walk all references to find all used definitions. This function
// want to only deal with unused definitions resulted from filtering paths. // 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 // 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 // First remove unwanted paths
prefixes := util.NewTrie(keepPathPrefixes) prefixes := util.NewTrie(keepPathPrefixes)
orgPaths := sp.Paths ret := *sp
if orgPaths == nil { ret.Paths = &spec.Paths{
return VendorExtensible: sp.Paths.VendorExtensible,
}
sp.Paths = &spec.Paths{
VendorExtensible: orgPaths.VendorExtensible,
Paths: map[string]spec.PathItem{}, Paths: map[string]spec.PathItem{},
} }
for path, pathItem := range orgPaths.Paths { for path, pathItem := range sp.Paths.Paths {
if !prefixes.HasPrefix(path) { if !prefixes.HasPrefix(path) {
continue continue
} }
sp.Paths.Paths[path] = pathItem ret.Paths.Paths[path] = pathItem
} }
// Walk all references to find all definition references. // Walk all references to find all definition references.
usedDefinitions := usedDefinitionForSpec(sp) usedDefinitions := usedDefinitionForSpec(&ret)
// Remove unused definitions // Remove unused definitions
orgDefinitions := sp.Definitions ret.Definitions = spec.Definitions{}
sp.Definitions = spec.Definitions{} for k, v := range sp.Definitions {
for k, v := range orgDefinitions {
if usedDefinitions[k] || !initialUsedDefinitions[k] { if usedDefinitions[k] || !initialUsedDefinitions[k] {
sp.Definitions[k] = v ret.Definitions[k] = v
} }
} }
return &ret
} }
func renameDefinition(s *spec.Swagger, old, new string) { type rename struct {
oldRef := definitionPrefix + old from, to string
newRef := definitionPrefix + new }
walkOnAllReferences(func(ref spec.Ref) spec.Ref {
if ref.String() == oldRef { // renameDefinition renames references, without mutating the input.
return spec.MustCreateRef(newRef) // 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 return ref
}, s) }, ret)
// Make sure we don't assign to nil map
if s.Definitions == nil { renamedDefinitions := make(spec.Definitions, len(ret.Definitions))
s.Definitions = spec.Definitions{} for k, v := range ret.Definitions {
if newRef, found := renames[k]; found {
k = newRef
}
renamedDefinitions[k] = v
} }
s.Definitions[new] = s.Definitions[old] ret.Definitions = renamedDefinitions
delete(s.Definitions, old)
return ret
} }
// MergeSpecsIgnorePathConflict is the same as MergeSpecs except it will ignore any path // MergeSpecsIgnorePathConflict is the same as MergeSpecs except it will ignore any path
// conflicts by keeping the paths of destination. It will rename definition conflicts. // conflicts by keeping the paths of destination. It will rename definition conflicts.
// The source is not mutated.
func MergeSpecsIgnorePathConflict(dest, source *spec.Swagger) error { func MergeSpecsIgnorePathConflict(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, true) return mergeSpecs(dest, source, true, true)
} }
// MergeSpecsFailOnDefinitionConflict is differ from MergeSpecs as it fails if there is // MergeSpecsFailOnDefinitionConflict is differ from MergeSpecs as it fails if there is
// a definition conflict. // a definition conflict.
// The source is not mutated.
func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error { func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, false, false) return mergeSpecs(dest, source, false, false)
} }
// MergeSpecs copies paths and definitions from source to dest, rename definitions if needed. // 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. // 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 { func MergeSpecs(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, false) 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) { 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). // Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
if source.Paths == nil { if source.Paths == nil {
// When a source spec does not have any path, that means none of the definitions // 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 return nil
} }
if hasConflictingPath { if hasConflictingPath {
source, err = CloneSpec(source) source = FilterSpecByPathsWithoutSideEffects(source, keepPaths)
if err != nil {
return err
}
specCloned = true
FilterSpecByPaths(source, keepPaths)
} }
} }
// Check for model conflicts // Check for model conflicts
@ -301,21 +196,11 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
} }
if conflicts { if conflicts {
if !specCloned {
source, err = CloneSpec(source)
if err != nil {
return err
}
}
specCloned = true
usedNames := map[string]bool{} usedNames := map[string]bool{}
for k := range dest.Definitions { for k := range dest.Definitions {
usedNames[k] = true usedNames[k] = true
} }
type Rename struct { renames := map[string]string{}
from, to string
}
renames := []Rename{}
OUTERLOOP: OUTERLOOP:
for k, v := range source.Definitions { 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) newName = fmt.Sprintf("%s_v%d", k, i)
v2, found = dest.Definitions[newName] v2, found = dest.Definitions[newName]
if found && reflect.DeepEqual(v, v2) { if found && reflect.DeepEqual(v, v2) {
renames = append(renames, Rename{from: k, to: newName}) renames[k] = newName
continue OUTERLOOP continue OUTERLOOP
} }
} }
@ -345,13 +230,11 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
newName = fmt.Sprintf("%s_v%d", k, i) newName = fmt.Sprintf("%s_v%d", k, i)
_, foundInSource = source.Definitions[newName] _, foundInSource = source.Definitions[newName]
} }
renames = append(renames, Rename{from: k, to: newName}) renames[k] = newName
usedNames[newName] = true usedNames[newName] = true
} }
} }
for _, r := range renames { source = renameDefinition(source, renames)
renameDefinition(source, r.from, r.to)
}
} }
for k, v := range source.Definitions { for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found { if _, found := dest.Definitions[k]; !found {
@ -374,18 +257,3 @@ func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConf
} }
return nil 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)) sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
for _, t := range c.Order { for _, t := range c.Order {
err := newOpenAPITypeWriter(sw).generateCall(t) err := newOpenAPITypeWriter(sw, c).generateCall(t)
if err != nil { if err != nil {
return err 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 { func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
klog.V(5).Infof("generating for type %v", t) klog.V(5).Infof("generating for type %v", t)
sw := generator.NewSnippetWriter(w, c, "$", "$") sw := generator.NewSnippetWriter(w, c, "$", "$")
err := newOpenAPITypeWriter(sw).generate(t) err := newOpenAPITypeWriter(sw, c).generate(t)
if err != nil { if err != nil {
return err return err
} }
@ -221,13 +221,15 @@ func shouldInlineMembers(m *types.Member) bool {
type openAPITypeWriter struct { type openAPITypeWriter struct {
*generator.SnippetWriter *generator.SnippetWriter
context *generator.Context
refTypes map[string]*types.Type refTypes map[string]*types.Type
GetDefinitionInterface *types.Type GetDefinitionInterface *types.Type
} }
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter { func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter {
return openAPITypeWriter{ return openAPITypeWriter{
SnippetWriter: sw, SnippetWriter: sw,
context: c,
refTypes: map[string]*types.Type{}, 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.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
g.generateDescription(t.CommentLines) g.generateDescription(t.CommentLines)
g.Do("Type: []string{\"object\"},\n", nil) 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 { if err != nil {
return err 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 { if len(required) > 0 {
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\"")) g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
} }
@ -351,13 +363,14 @@ func (g openAPITypeWriter) generate(t *types.Type) error {
return err return err
} }
g.Do("},\n", nil) 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. // Map order is undefined, sort them or we may get a different file generated each time.
keys := []string{} keys := []string{}
for k := range g.refTypes { for k := range g.refTypes {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
deps := []string{}
for _, k := range keys { for _, k := range keys {
v := g.refTypes[k] v := g.refTypes[k]
if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" { 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 // Will eliminate special case of time.Time
continue 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 return nil
} }
@ -409,7 +429,7 @@ func (g openAPITypeWriter) emitExtensions(extensions []extension) {
for _, extension := range extensions { for _, extension := range extensions {
g.Do("\"$.$\": ", extension.xName) g.Do("\"$.$\": ", extension.xName)
if extension.hasMultipleValues() { if extension.hasMultipleValues() {
g.Do("[]string{\n", nil) g.Do("[]interface{}{\n", nil)
} }
for _, value := range extension.values { for _, value := range extension.values {
g.Do("\"$.$\",\n", value) 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) 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("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()) typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
if typeString != "" { if typeString != "" {
g.generateSimpleProperty(typeString, format) g.generateSimpleProperty(typeString, format)

View File

@ -13,6 +13,7 @@ go_library(
"//vendor/github.com/golang/protobuf/proto: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/OpenAPIv2:go_default_library",
"//vendor/github.com/googleapis/gnostic/compiler: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/github.com/munnerz/goautoneg:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/builder:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/builder:go_default_library",

View File

@ -20,7 +20,6 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/sha512" "crypto/sha512"
"encoding/json"
"fmt" "fmt"
"mime" "mime"
"net/http" "net/http"
@ -28,15 +27,15 @@ import (
"sync" "sync"
"time" "time"
yaml "gopkg.in/yaml.v2"
"github.com/NYTimes/gziphandler" "github.com/NYTimes/gziphandler"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2" openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler" "github.com/googleapis/gnostic/compiler"
"github.com/json-iterator/go"
"github.com/munnerz/goautoneg" "github.com/munnerz/goautoneg"
yaml "gopkg.in/yaml.v2"
"k8s.io/kube-openapi/pkg/builder" "k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/common"
@ -78,6 +77,15 @@ func computeETag(data []byte) string {
return fmt.Sprintf("\"%X\"", sha512.Sum512(data)) 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, // 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 // 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. // 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 { if err != nil {
return nil, err 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, // 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. // 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 // Note: servePath should end with ".json" as the RegisterOpenAPIService assume it is serving a
// json file and will also serve .pb and .gz files. // 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) { 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) servePathBase := strings.TrimSuffix(servePath, jsonExt)
o := OpenAPIService{}
if err := o.UpdateSpec(openapiSpec); err != nil {
return nil, err
}
type fileInfo struct { type fileInfo struct {
ext string ext string
getDataAndETag func() ([]byte, string, time.Time) 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) { 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) { func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
specBytes, err := json.MarshalIndent(openapiSpec, " ", " ") specBytes, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(openapiSpec)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -189,13 +217,33 @@ func (o *OpenAPIService) UpdateSpec(openapiSpec *spec.Swagger) (err error) {
return nil return nil
} }
func toProtoBinary(spec []byte) ([]byte, error) { func jsonToYAML(j map[string]interface{}) yaml.MapSlice {
var info yaml.MapSlice if j == nil {
err := yaml.Unmarshal(spec, &info) return nil
if err != nil {
return nil, err
} }
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 { if err != nil {
return nil, err return nil, err
} }
@ -211,12 +259,18 @@ func toGzip(data []byte) []byte {
} }
// RegisterOpenAPIVersionedService registers a handler to provide access to provided swagger spec. // 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{} // Deprecated: use OpenAPIService.RegisterOpenAPIVersionedService instead.
if err := o.UpdateSpec(openapiSpec); err != nil { func RegisterOpenAPIVersionedService(spec *spec.Swagger, servePath string, handler common.PathHandler) (*OpenAPIService, error) {
o, err := NewOpenAPIService(spec)
if err != nil {
return nil, err 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 { accepted := []struct {
Type string Type string
SubType 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. // 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 { if err != nil {
return nil, err 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 { func (c *convert) makeRef(model proto.Schema) schema.TypeRef {
var tr schema.TypeRef var tr schema.TypeRef
if r, ok := model.(*proto.Ref); ok { 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 // reference a named type
_, n := path.Split(r.Reference()) _, n := path.Split(r.Reference())
tr.NamedType = &n tr.NamedType = &n

View File

@ -21,14 +21,39 @@ import (
"strings" "strings"
) )
// ToCanonicalName converts Golang package/type name into canonical OpenAPI name. // [DEPRECATED] ToCanonicalName converts Golang package/type canonical name into REST friendly OpenAPI name.
// Examples: // 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 // Input: k8s.io/api/core/v1.Pod
// Output: io.k8s.api.core.v1.Pod // Output: io.k8s.api.core.v1.Pod
// //
// Input: k8s.io/api/core/v1 // Input: k8s.io/api/core/v1
// Output: io.k8s.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 { 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, "/") nameParts := strings.Split(name, "/")
// Reverse first part. e.g., io.k8s... instead of k8s.io... // Reverse first part. e.g., io.k8s... instead of k8s.io...
if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
@ -41,9 +66,30 @@ func ToCanonicalName(name string) string {
return strings.Join(nameParts, ".") 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 // GetCanonicalTypeName will find the canonical type name of a sample object, removing
// the "vendor" part of the path // the "vendor" part of the path
func GetCanonicalTypeName(model interface{}) string { func GetCanonicalTypeName(model interface{}) string {
if namer, ok := model.(OpenAPICanonicalTypeNamer); ok {
return namer.OpenAPICanonicalTypeName()
}
t := reflect.TypeOf(model) t := reflect.TypeOf(model)
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
t = t.Elem() t = t.Elem()