Revert "Aggregate OpenAPI specs"

This reverts commit 1a1d9a0394.
This commit is contained in:
mbohlool 2017-07-07 14:51:42 -07:00
parent a571ea43f9
commit 88868402b8
5 changed files with 6 additions and 1210 deletions

View File

@ -51,6 +51,7 @@ func createAggregatorConfig(kubeAPIServerConfig genericapiserver.Config, command
// the aggregator doesn't wire these up. It just delegates them to the kubeapiserver
genericConfig.EnableSwaggerUI = false
genericConfig.OpenAPIConfig = nil
genericConfig.SwaggerConfig = nil
// copy the etcd options so we don't mutate originals.

View File

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

View File

@ -1,482 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openapi
import (
"fmt"
"strings"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apiserver/pkg/util/trie"
)
const (
DEFINITION_PREFIX = "#/definitions/"
)
var cloner = conversion.NewCloner()
// Run a walkRefCallback method on all references of an OpenAPI spec
type walkAllRefs struct {
// walkRefCallback will be called on each reference and the return value
// will replace that reference. This will allow the callers to change
// all/some references of an spec (e.g. useful in renaming definitions).
walkRefCallback func(ref spec.Ref) spec.Ref
// The spec to walk through.
root *spec.Swagger
}
func newWalkAllRefs(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) *walkAllRefs {
return &walkAllRefs{
walkRefCallback: walkRef,
root: sp,
}
}
func (s *walkAllRefs) walkRef(ref spec.Ref) spec.Ref {
if ref.String() != "" {
refStr := ref.String()
// References that start with #/definitions/ has a definition
// inside the same spec file. If that is the case, walk through
// those definitions too.
// We do not support external references yet.
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
def := s.root.Definitions[refStr[len(DEFINITION_PREFIX):]]
s.walkSchema(&def)
}
}
return s.walkRefCallback(ref)
}
func (s *walkAllRefs) walkSchema(schema *spec.Schema) {
if schema == nil {
return
}
schema.Ref = s.walkRef(schema.Ref)
for _, v := range schema.Definitions {
s.walkSchema(&v)
}
for _, v := range schema.Properties {
s.walkSchema(&v)
}
for _, v := range schema.PatternProperties {
s.walkSchema(&v)
}
for _, v := range schema.AllOf {
s.walkSchema(&v)
}
for _, v := range schema.AnyOf {
s.walkSchema(&v)
}
for _, v := range schema.OneOf {
s.walkSchema(&v)
}
if schema.Not != nil {
s.walkSchema(schema.Not)
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
s.walkSchema(schema.AdditionalProperties.Schema)
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
s.walkSchema(schema.AdditionalItems.Schema)
}
if schema.Items != nil {
if schema.Items.Schema != nil {
s.walkSchema(schema.Items.Schema)
}
for _, v := range schema.Items.Schemas {
s.walkSchema(&v)
}
}
}
func (s *walkAllRefs) walkParams(params []spec.Parameter) {
if params == nil {
return
}
for _, param := range params {
param.Ref = s.walkRef(param.Ref)
s.walkSchema(param.Schema)
if param.Items != nil {
param.Items.Ref = s.walkRef(param.Items.Ref)
}
}
}
func (s *walkAllRefs) walkResponse(resp *spec.Response) {
if resp == nil {
return
}
resp.Ref = s.walkRef(resp.Ref)
s.walkSchema(resp.Schema)
}
func (s *walkAllRefs) walkOperation(op *spec.Operation) {
if op == nil {
return
}
s.walkParams(op.Parameters)
if op.Responses == nil {
return
}
s.walkResponse(op.Responses.Default)
for _, r := range op.Responses.StatusCodeResponses {
s.walkResponse(&r)
}
}
func (s *walkAllRefs) Start() {
for _, pathItem := range s.root.Paths.Paths {
s.walkParams(pathItem.Parameters)
s.walkOperation(pathItem.Delete)
s.walkOperation(pathItem.Get)
s.walkOperation(pathItem.Head)
s.walkOperation(pathItem.Options)
s.walkOperation(pathItem.Patch)
s.walkOperation(pathItem.Post)
s.walkOperation(pathItem.Put)
}
}
// FilterSpecByPaths remove unnecessary paths and unused definitions.
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
// First remove unwanted paths
prefixes := trie.New(keepPathPrefixes)
orgPaths := sp.Paths
if orgPaths == nil {
return
}
sp.Paths = &spec.Paths{
VendorExtensible: orgPaths.VendorExtensible,
Paths: map[string]spec.PathItem{},
}
for path, pathItem := range orgPaths.Paths {
if !prefixes.HasPrefix(path) {
continue
}
sp.Paths.Paths[path] = pathItem
}
// Walk all references to find all definition references.
usedDefinitions := map[string]bool{}
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
if ref.String() != "" {
refStr := ref.String()
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
usedDefinitions[refStr[len(DEFINITION_PREFIX):]] = true
}
}
return ref
}, sp).Start()
// Remove unused definitions
orgDefinitions := sp.Definitions
sp.Definitions = spec.Definitions{}
for k, v := range orgDefinitions {
if usedDefinitions[k] {
sp.Definitions[k] = v
}
}
}
func equalSchemaMap(s1, s2 map[string]spec.Schema) bool {
if len(s1) != len(s2) {
return false
}
for k, v := range s1 {
v2, found := s2[k]
if !found {
return false
}
if !EqualSchema(&v, &v2) {
return false
}
}
return true
}
func equalSchemaArray(s1, s2 []spec.Schema) bool {
if s1 == nil || s2 == nil {
return s1 == nil && s2 == nil
}
if len(s1) != len(s2) {
return false
}
for _, v1 := range s1 {
found := false
for _, v2 := range s2 {
if EqualSchema(&v1, &v2) {
found = true
break
}
}
if !found {
return false
}
}
for _, v2 := range s2 {
found := false
for _, v1 := range s1 {
if EqualSchema(&v1, &v2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func equalSchemaOrBool(s1, s2 *spec.SchemaOrBool) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if s1.Allows != s2.Allows {
return false
}
if !EqualSchema(s1.Schema, s2.Schema) {
return false
}
return true
}
func equalSchemaOrArray(s1, s2 *spec.SchemaOrArray) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if !EqualSchema(s1.Schema, s2.Schema) {
return false
}
if !equalSchemaArray(s1.Schemas, s2.Schemas) {
return false
}
return true
}
func equalStringArray(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for _, v1 := range s1 {
found := false
for _, v2 := range s2 {
if v1 == v2 {
found = true
break
}
}
if !found {
return false
}
}
for _, v2 := range s2 {
found := false
for _, v1 := range s1 {
if v1 == v2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func equalFloatPointer(s1, s2 *float64) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
return *s1 == *s2
}
func equalIntPointer(s1, s2 *int64) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
return *s1 == *s2
}
// EqualSchema returns true if models have the same properties and references
// even if they have different documentation.
func EqualSchema(s1, s2 *spec.Schema) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if s1.Ref.String() != s2.Ref.String() {
return false
}
if !equalSchemaMap(s1.Definitions, s2.Definitions) {
return false
}
if !equalSchemaMap(s1.Properties, s2.Properties) {
fmt.Println("Not equal props")
return false
}
if !equalSchemaMap(s1.PatternProperties, s2.PatternProperties) {
return false
}
if !equalSchemaArray(s1.AllOf, s2.AllOf) {
return false
}
if !equalSchemaArray(s1.AnyOf, s2.AnyOf) {
return false
}
if !equalSchemaArray(s1.OneOf, s2.OneOf) {
return false
}
if !EqualSchema(s1.Not, s2.Not) {
return false
}
if !equalSchemaOrBool(s1.AdditionalProperties, s2.AdditionalProperties) {
return false
}
if !equalSchemaOrBool(s1.AdditionalItems, s2.AdditionalItems) {
return false
}
if !equalSchemaOrArray(s1.Items, s2.Items) {
return false
}
if !equalStringArray(s1.Type, s2.Type) {
return false
}
if s1.Format != s2.Format {
return false
}
if !equalFloatPointer(s1.Minimum, s2.Minimum) {
return false
}
if !equalFloatPointer(s1.Maximum, s2.Maximum) {
return false
}
if s1.ExclusiveMaximum != s2.ExclusiveMaximum {
return false
}
if s1.ExclusiveMinimum != s2.ExclusiveMinimum {
return false
}
if !equalFloatPointer(s1.MultipleOf, s2.MultipleOf) {
return false
}
if !equalIntPointer(s1.MaxLength, s2.MaxLength) {
return false
}
if !equalIntPointer(s1.MinLength, s2.MinLength) {
return false
}
if !equalIntPointer(s1.MaxItems, s2.MaxItems) {
return false
}
if !equalIntPointer(s1.MinItems, s2.MinItems) {
return false
}
if s1.Pattern != s2.Pattern {
return false
}
if s1.UniqueItems != s2.UniqueItems {
return false
}
if !equalIntPointer(s1.MaxProperties, s2.MaxProperties) {
return false
}
if !equalIntPointer(s1.MinProperties, s2.MinProperties) {
return false
}
if !equalStringArray(s1.Required, s2.Required) {
return false
}
return len(s1.Enum) == 0 && len(s2.Enum) == 0 && len(s1.Dependencies) == 0 && len(s2.Dependencies) == 0
}
func renameDefinition(s *spec.Swagger, old, new string) {
old_ref := DEFINITION_PREFIX + old
new_ref := DEFINITION_PREFIX + new
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
if ref.String() == old_ref {
return spec.MustCreateRef(new_ref)
}
return ref
}, s).Start()
s.Definitions[new] = s.Definitions[old]
delete(s.Definitions, old)
}
// Copy paths and definitions from source to dest, rename definitions if needed.
// dest will be mutated, and source will not be changed.
func MergeSpecs(dest, source *spec.Swagger) error {
source, err := CloneSpec(source)
if err != nil {
return err
}
for k, v := range source.Paths.Paths {
if _, found := dest.Paths.Paths[k]; found {
return fmt.Errorf("Unable to merge: Duplicated path %s", k)
}
dest.Paths.Paths[k] = v
}
usedNames := map[string]bool{}
for k := range dest.Definitions {
usedNames[k] = true
}
type Rename struct {
from, to string
}
renames := []Rename{}
for k, v := range source.Definitions {
v2, found := dest.Definitions[k]
if found || usedNames[k] {
if found && EqualSchema(&v, &v2) {
continue
}
i := 2
newName := fmt.Sprintf("%s_v%d", k, i)
for usedNames[newName] {
i += 1
newName = fmt.Sprintf("%s_v%d", k, i)
}
renames = append(renames, Rename{from: k, to: newName})
usedNames[newName] = true
} else {
usedNames[k] = true
}
}
for _, r := range renames {
renameDefinition(source, r.from, r.to)
}
for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found {
dest.Definitions[k] = v
}
}
return nil
}
// Clone OpenAPI spec
func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) {
if ret, err := cloner.DeepCopy(source); err != nil {
return nil, err
} else {
return ret.(*spec.Swagger), nil
}
}

View File

@ -1,520 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openapi
import (
"testing"
"github.com/ghodss/yaml"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestFilterSpecs(t *testing.T) {
var spec1, spec1_filtered *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1_filtered)
assert := assert.New(t)
FilterSpecByPaths(spec1, []string{"/test"})
assert.Equal(spec1_filtered, spec1)
}
func TestMergeSpecsSimple(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(expected, spec1)
}
func TestMergeSpecsReuseModel(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "This status has another description"
InvalidInput:
type: "string"
format: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(expected, spec1)
}
func TestMergeSpecsRenameModel(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
InvalidInput:
type: "string"
format: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test_v2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
Test_v2:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
InvalidInput:
type: "string"
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
expected_yaml, _ := yaml.Marshal(expected)
spec1_yaml, _ := yaml.Marshal(spec1)
assert.Equal(string(expected_yaml), string(spec1_yaml))
}

View File

@ -17,10 +17,7 @@ limitations under the License.
package apiserver
import (
"context"
"encoding/json"
"net/http"
"sync"
"time"
"k8s.io/apimachinery/pkg/apimachinery/announced"
@ -36,15 +33,6 @@ import (
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/pkg/version"
"bytes"
"fmt"
"io"
"github.com/go-openapi/spec"
"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/apiserver/pkg/server/openapi"
"k8s.io/client-go/transport"
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
"k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
@ -62,10 +50,6 @@ var (
Codecs = serializer.NewCodecFactory(Scheme)
)
const (
LOAD_OPENAPI_SPEC_MAX_RETRIES = 10
)
func init() {
install.Install(groupFactoryRegistry, registry, Scheme)
@ -123,24 +107,6 @@ type APIAggregator struct {
// handledGroups are the groups that already have routes
handledGroups sets.String
// Swagger spec for each api service
apiServiceSpecs map[string]*spec.Swagger
// List of the specs that needs to be loaded. When a spec is successfully loaded
// it will be removed from this list and added to apiServiceSpecs.
// Map values are retry counts. After a preset retries, it will stop
// trying.
toLoadAPISpec map[string]int
// protecting toLoadAPISpec and apiServiceSpecs
specMutex sync.Mutex
// rootSpec is the OpenAPI spec of the Aggregator server.
rootSpec *spec.Swagger
// delegationSpec is the delegation API Server's spec (most of API groups are in this spec).
delegationSpec *spec.Swagger
// lister is used to add group handling for /apis/<group> aggregator lookups based on
// controller state
lister listers.APIServiceLister
@ -193,14 +159,11 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
s := &APIAggregator{
GenericAPIServer: genericServer,
delegateHandler: delegationTarget.UnprotectedHandler(),
delegationSpec: delegationTarget.OpenAPISpec(),
contextMapper: c.GenericConfig.RequestContextMapper,
proxyClientCert: c.ProxyClientCert,
proxyClientKey: c.ProxyClientKey,
proxyTransport: c.ProxyTransport,
proxyHandlers: map[string]*proxyHandler{},
apiServiceSpecs: map[string]*spec.Swagger{},
toLoadAPISpec: map[string]int{},
handledGroups: sets.String{},
lister: informerFactory.Apiregistration().InternalVersion().APIServices().Lister(),
APIRegistrationInformers: informerFactory,
@ -249,20 +212,6 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
return nil
})
s.GenericAPIServer.PrepareOpenAPIService()
if s.GenericAPIServer.OpenAPIService != nil {
s.rootSpec = s.GenericAPIServer.OpenAPIService.GetSpec()
if err := s.updateOpenAPISpec(); err != nil {
return nil, err
}
s.GenericAPIServer.OpenAPIService.AddUpdateHook(func(r *http.Request) {
if s.tryLoadingOpenAPISpecs(r) {
s.updateOpenAPISpec()
}
})
}
return s, nil
}
@ -293,9 +242,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) {
}
proxyHandler.updateAPIService(apiService)
s.proxyHandlers[apiService.Name] = proxyHandler
s.deferLoadAPISpec(apiService.Name)
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)
@ -324,19 +270,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) {
s.handledGroups.Insert(apiService.Spec.Group)
}
func (s *APIAggregator) deferLoadAPISpec(name string) {
s.specMutex.Lock()
defer s.specMutex.Unlock()
s.toLoadAPISpec[name] = 0
}
func (s *APIAggregator) deleteApiSpec(name string) {
s.specMutex.Lock()
defer s.specMutex.Unlock()
delete(s.apiServiceSpecs, name)
delete(s.toLoadAPISpec, name)
}
// RemoveAPIService removes the APIService from being handled. It is not thread-safe, so only call it on one thread at a time please.
// It's a slow moving API, so its ok to run the controller on a single thread.
func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
@ -350,124 +283,7 @@ func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath)
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
delete(s.proxyHandlers, apiServiceName)
s.deleteApiSpec(apiServiceName)
s.updateOpenAPISpec()
// TODO unregister group level discovery when there are no more versions for the group
// We don't need this right away because the handler properly delegates when no versions are present
}
func (_ *APIAggregator) loadOpenAPISpec(p *proxyHandler, r *http.Request) (*spec.Swagger, error) {
value := p.handlingInfo.Load()
if value == nil {
return nil, nil
}
handlingInfo := value.(proxyHandlingInfo)
if handlingInfo.local {
return nil, nil
}
loc, err := p.serviceResolver.ResolveEndpoint(handlingInfo.serviceNamespace, handlingInfo.serviceName)
if err != nil {
return nil, fmt.Errorf("missing route")
}
host := loc.Host
var w io.Reader
req, err := http.NewRequest("GET", "/swagger.json", w)
if err != nil {
return nil, err
}
req.URL.Scheme = "https"
req.URL.Host = host
req = req.WithContext(context.Background())
// Get user from the original request
ctx, ok := p.contextMapper.Get(r)
if !ok {
return nil, fmt.Errorf("missing context")
}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return nil, fmt.Errorf("missing user")
}
proxyRoundTripper := transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), handlingInfo.proxyRoundTripper)
res, err := proxyRoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, errors.New(res.Status)
}
buf := new(bytes.Buffer)
buf.ReadFrom(res.Body)
bytes := buf.Bytes()
var s spec.Swagger
if err := json.Unmarshal(bytes, &s); err != nil {
return nil, err
}
return &s, nil
}
// Returns true if any Spec is loaded
func (s *APIAggregator) tryLoadingOpenAPISpecs(r *http.Request) bool {
s.specMutex.Lock()
defer s.specMutex.Unlock()
if len(s.toLoadAPISpec) == 0 {
return false
}
loaded := false
newList := map[string]int{}
for name, retries := range s.toLoadAPISpec {
if retries >= LOAD_OPENAPI_SPEC_MAX_RETRIES {
continue
}
proxyHandler := s.proxyHandlers[name]
if spec, err := s.loadOpenAPISpec(proxyHandler, r); err != nil {
glog.Warningf("Failed to Load OpenAPI spec (try %d of %d) for %s, err=%s", retries+1, LOAD_OPENAPI_SPEC_MAX_RETRIES, name, err)
newList[name] = retries + 1
} else if spec != nil {
s.apiServiceSpecs[name] = spec
loaded = true
}
s.toLoadAPISpec = newList
}
return loaded
}
func (s *APIAggregator) updateOpenAPISpec() error {
s.specMutex.Lock()
defer s.specMutex.Unlock()
if s.GenericAPIServer.OpenAPIService == nil {
return nil
}
sp, err := openapi.CloneSpec(s.rootSpec)
if err != nil {
return err
}
openapi.FilterSpecByPaths(sp, []string{"/apis/apiregistration.k8s.io/"})
if _, found := sp.Paths.Paths["/version/"]; found {
return fmt.Errorf("Cleanup didn't work")
}
if err := openapi.MergeSpecs(sp, s.delegationSpec); err != nil {
return err
}
for k, v := range s.apiServiceSpecs {
version := apiregistration.APIServiceNameToGroupVersion(k)
proxyPath := "/apis/" + version.Group + "/"
// v1. is a special case for the legacy API. It proxies to a wider set of endpoints.
if k == legacyAPIServiceName {
proxyPath = "/api/"
}
spc, err := openapi.CloneSpec(v)
if err != nil {
return err
}
openapi.FilterSpecByPaths(spc, []string{proxyPath})
if err := openapi.MergeSpecs(sp, spc); err != nil {
return err
}
}
return s.GenericAPIServer.OpenAPIService.UpdateSpec(sp)
}