mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
parent
a571ea43f9
commit
88868402b8
@ -51,6 +51,7 @@ func createAggregatorConfig(kubeAPIServerConfig genericapiserver.Config, command
|
|||||||
|
|
||||||
// the aggregator doesn't wire these up. It just delegates them to the kubeapiserver
|
// the aggregator doesn't wire these up. It just delegates them to the kubeapiserver
|
||||||
genericConfig.EnableSwaggerUI = false
|
genericConfig.EnableSwaggerUI = false
|
||||||
|
genericConfig.OpenAPIConfig = nil
|
||||||
genericConfig.SwaggerConfig = nil
|
genericConfig.SwaggerConfig = nil
|
||||||
|
|
||||||
// copy the etcd options so we don't mutate originals.
|
// copy the etcd options so we don't mutate originals.
|
||||||
|
@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/emicklei/go-restful-swagger12"
|
"github.com/emicklei/go-restful-swagger12"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"k8s.io/apimachinery/pkg/apimachinery"
|
"k8s.io/apimachinery/pkg/apimachinery"
|
||||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -43,7 +42,6 @@ import (
|
|||||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
"k8s.io/apiserver/pkg/server/openapi"
|
|
||||||
"k8s.io/apiserver/pkg/server/routes"
|
"k8s.io/apiserver/pkg/server/routes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
@ -131,9 +129,6 @@ type GenericAPIServer struct {
|
|||||||
swaggerConfig *swagger.Config
|
swaggerConfig *swagger.Config
|
||||||
openAPIConfig *openapicommon.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
|
// 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.
|
// 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.
|
// 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 returns the paths for supporting an index
|
||||||
ListedPaths() []string
|
ListedPaths() []string
|
||||||
|
|
||||||
// OpenAPISpec returns the OpenAPI spec of the delegation target if exists, nil otherwise.
|
|
||||||
OpenAPISpec() *spec.Swagger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GenericAPIServer) UnprotectedHandler() http.Handler {
|
func (s *GenericAPIServer) UnprotectedHandler() http.Handler {
|
||||||
@ -191,9 +183,6 @@ func (s *GenericAPIServer) HealthzChecks() []healthz.HealthzChecker {
|
|||||||
func (s *GenericAPIServer) ListedPaths() []string {
|
func (s *GenericAPIServer) ListedPaths() []string {
|
||||||
return s.listedPathProvider.ListedPaths()
|
return s.listedPathProvider.ListedPaths()
|
||||||
}
|
}
|
||||||
func (s *GenericAPIServer) OpenAPISpec() *spec.Swagger {
|
|
||||||
return s.OpenAPIService.GetSpec()
|
|
||||||
}
|
|
||||||
|
|
||||||
var EmptyDelegate = emptyDelegate{
|
var EmptyDelegate = emptyDelegate{
|
||||||
requestContextMapper: apirequest.NewRequestContextMapper(),
|
requestContextMapper: apirequest.NewRequestContextMapper(),
|
||||||
@ -218,9 +207,6 @@ func (s emptyDelegate) ListedPaths() []string {
|
|||||||
func (s emptyDelegate) RequestContextMapper() apirequest.RequestContextMapper {
|
func (s emptyDelegate) RequestContextMapper() apirequest.RequestContextMapper {
|
||||||
return s.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.
|
// RequestContextMapper is exposed so that third party resource storage can be build in a different location.
|
||||||
// TODO refactor third party resource storage
|
// TODO refactor third party resource storage
|
||||||
@ -243,22 +229,17 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
|
|||||||
if s.swaggerConfig != nil {
|
if s.swaggerConfig != nil {
|
||||||
routes.Swagger{Config: s.swaggerConfig}.Install(s.Handler.GoRestfulContainer)
|
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()
|
s.installHealthz()
|
||||||
|
|
||||||
return preparedGenericAPIServer{s}
|
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
|
// Run spawns the secure http server. It only returns if stopCh is closed
|
||||||
// or the secure port cannot be listened on initially.
|
// or the secure port cannot be listened on initially.
|
||||||
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
|
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
|
||||||
|
@ -1,482 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/conversion"
|
|
||||||
"k8s.io/apiserver/pkg/util/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DEFINITION_PREFIX = "#/definitions/"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cloner = conversion.NewCloner()
|
|
||||||
|
|
||||||
// Run a walkRefCallback method on all references of an OpenAPI spec
|
|
||||||
type walkAllRefs struct {
|
|
||||||
// walkRefCallback will be called on each reference and the return value
|
|
||||||
// will replace that reference. This will allow the callers to change
|
|
||||||
// all/some references of an spec (e.g. useful in renaming definitions).
|
|
||||||
walkRefCallback func(ref spec.Ref) spec.Ref
|
|
||||||
|
|
||||||
// The spec to walk through.
|
|
||||||
root *spec.Swagger
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWalkAllRefs(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) *walkAllRefs {
|
|
||||||
return &walkAllRefs{
|
|
||||||
walkRefCallback: walkRef,
|
|
||||||
root: sp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *walkAllRefs) walkRef(ref spec.Ref) spec.Ref {
|
|
||||||
if ref.String() != "" {
|
|
||||||
refStr := ref.String()
|
|
||||||
// References that start with #/definitions/ has a definition
|
|
||||||
// inside the same spec file. If that is the case, walk through
|
|
||||||
// those definitions too.
|
|
||||||
// We do not support external references yet.
|
|
||||||
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
|
|
||||||
def := s.root.Definitions[refStr[len(DEFINITION_PREFIX):]]
|
|
||||||
s.walkSchema(&def)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.walkRefCallback(ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *walkAllRefs) walkSchema(schema *spec.Schema) {
|
|
||||||
if schema == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
schema.Ref = s.walkRef(schema.Ref)
|
|
||||||
for _, v := range schema.Definitions {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
for _, v := range schema.Properties {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
for _, v := range schema.PatternProperties {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
for _, v := range schema.AllOf {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
for _, v := range schema.AnyOf {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
for _, v := range schema.OneOf {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
if schema.Not != nil {
|
|
||||||
s.walkSchema(schema.Not)
|
|
||||||
}
|
|
||||||
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
|
|
||||||
s.walkSchema(schema.AdditionalProperties.Schema)
|
|
||||||
}
|
|
||||||
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
|
|
||||||
s.walkSchema(schema.AdditionalItems.Schema)
|
|
||||||
}
|
|
||||||
if schema.Items != nil {
|
|
||||||
if schema.Items.Schema != nil {
|
|
||||||
s.walkSchema(schema.Items.Schema)
|
|
||||||
}
|
|
||||||
for _, v := range schema.Items.Schemas {
|
|
||||||
s.walkSchema(&v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *walkAllRefs) walkParams(params []spec.Parameter) {
|
|
||||||
if params == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, param := range params {
|
|
||||||
param.Ref = s.walkRef(param.Ref)
|
|
||||||
s.walkSchema(param.Schema)
|
|
||||||
if param.Items != nil {
|
|
||||||
param.Items.Ref = s.walkRef(param.Items.Ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *walkAllRefs) walkResponse(resp *spec.Response) {
|
|
||||||
if resp == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Ref = s.walkRef(resp.Ref)
|
|
||||||
s.walkSchema(resp.Schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *walkAllRefs) walkOperation(op *spec.Operation) {
|
|
||||||
if op == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.walkParams(op.Parameters)
|
|
||||||
if op.Responses == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.walkResponse(op.Responses.Default)
|
|
||||||
for _, r := range op.Responses.StatusCodeResponses {
|
|
||||||
s.walkResponse(&r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *walkAllRefs) Start() {
|
|
||||||
for _, pathItem := range s.root.Paths.Paths {
|
|
||||||
s.walkParams(pathItem.Parameters)
|
|
||||||
s.walkOperation(pathItem.Delete)
|
|
||||||
s.walkOperation(pathItem.Get)
|
|
||||||
s.walkOperation(pathItem.Head)
|
|
||||||
s.walkOperation(pathItem.Options)
|
|
||||||
s.walkOperation(pathItem.Patch)
|
|
||||||
s.walkOperation(pathItem.Post)
|
|
||||||
s.walkOperation(pathItem.Put)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterSpecByPaths remove unnecessary paths and unused definitions.
|
|
||||||
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
|
|
||||||
// First remove unwanted paths
|
|
||||||
prefixes := trie.New(keepPathPrefixes)
|
|
||||||
orgPaths := sp.Paths
|
|
||||||
if orgPaths == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sp.Paths = &spec.Paths{
|
|
||||||
VendorExtensible: orgPaths.VendorExtensible,
|
|
||||||
Paths: map[string]spec.PathItem{},
|
|
||||||
}
|
|
||||||
for path, pathItem := range orgPaths.Paths {
|
|
||||||
if !prefixes.HasPrefix(path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sp.Paths.Paths[path] = pathItem
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk all references to find all definition references.
|
|
||||||
usedDefinitions := map[string]bool{}
|
|
||||||
|
|
||||||
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
|
|
||||||
if ref.String() != "" {
|
|
||||||
refStr := ref.String()
|
|
||||||
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
|
|
||||||
usedDefinitions[refStr[len(DEFINITION_PREFIX):]] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ref
|
|
||||||
}, sp).Start()
|
|
||||||
|
|
||||||
// Remove unused definitions
|
|
||||||
orgDefinitions := sp.Definitions
|
|
||||||
sp.Definitions = spec.Definitions{}
|
|
||||||
for k, v := range orgDefinitions {
|
|
||||||
if usedDefinitions[k] {
|
|
||||||
sp.Definitions[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalSchemaMap(s1, s2 map[string]spec.Schema) bool {
|
|
||||||
if len(s1) != len(s2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for k, v := range s1 {
|
|
||||||
v2, found := s2[k]
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !EqualSchema(&v, &v2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalSchemaArray(s1, s2 []spec.Schema) bool {
|
|
||||||
if s1 == nil || s2 == nil {
|
|
||||||
return s1 == nil && s2 == nil
|
|
||||||
}
|
|
||||||
if len(s1) != len(s2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, v1 := range s1 {
|
|
||||||
found := false
|
|
||||||
for _, v2 := range s2 {
|
|
||||||
if EqualSchema(&v1, &v2) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, v2 := range s2 {
|
|
||||||
found := false
|
|
||||||
for _, v1 := range s1 {
|
|
||||||
if EqualSchema(&v1, &v2) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalSchemaOrBool(s1, s2 *spec.SchemaOrBool) bool {
|
|
||||||
if s1 == nil || s2 == nil {
|
|
||||||
return s1 == s2
|
|
||||||
}
|
|
||||||
if s1.Allows != s2.Allows {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !EqualSchema(s1.Schema, s2.Schema) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalSchemaOrArray(s1, s2 *spec.SchemaOrArray) bool {
|
|
||||||
if s1 == nil || s2 == nil {
|
|
||||||
return s1 == s2
|
|
||||||
}
|
|
||||||
if !EqualSchema(s1.Schema, s2.Schema) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaArray(s1.Schemas, s2.Schemas) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalStringArray(s1, s2 []string) bool {
|
|
||||||
if len(s1) != len(s2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, v1 := range s1 {
|
|
||||||
found := false
|
|
||||||
for _, v2 := range s2 {
|
|
||||||
if v1 == v2 {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, v2 := range s2 {
|
|
||||||
found := false
|
|
||||||
for _, v1 := range s1 {
|
|
||||||
if v1 == v2 {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalFloatPointer(s1, s2 *float64) bool {
|
|
||||||
if s1 == nil || s2 == nil {
|
|
||||||
return s1 == s2
|
|
||||||
}
|
|
||||||
return *s1 == *s2
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalIntPointer(s1, s2 *int64) bool {
|
|
||||||
if s1 == nil || s2 == nil {
|
|
||||||
return s1 == s2
|
|
||||||
}
|
|
||||||
return *s1 == *s2
|
|
||||||
}
|
|
||||||
|
|
||||||
// EqualSchema returns true if models have the same properties and references
|
|
||||||
// even if they have different documentation.
|
|
||||||
func EqualSchema(s1, s2 *spec.Schema) bool {
|
|
||||||
if s1 == nil || s2 == nil {
|
|
||||||
return s1 == s2
|
|
||||||
}
|
|
||||||
if s1.Ref.String() != s2.Ref.String() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaMap(s1.Definitions, s2.Definitions) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaMap(s1.Properties, s2.Properties) {
|
|
||||||
fmt.Println("Not equal props")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaMap(s1.PatternProperties, s2.PatternProperties) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaArray(s1.AllOf, s2.AllOf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaArray(s1.AnyOf, s2.AnyOf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaArray(s1.OneOf, s2.OneOf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !EqualSchema(s1.Not, s2.Not) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaOrBool(s1.AdditionalProperties, s2.AdditionalProperties) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaOrBool(s1.AdditionalItems, s2.AdditionalItems) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalSchemaOrArray(s1.Items, s2.Items) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalStringArray(s1.Type, s2.Type) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s1.Format != s2.Format {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalFloatPointer(s1.Minimum, s2.Minimum) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalFloatPointer(s1.Maximum, s2.Maximum) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s1.ExclusiveMaximum != s2.ExclusiveMaximum {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s1.ExclusiveMinimum != s2.ExclusiveMinimum {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalFloatPointer(s1.MultipleOf, s2.MultipleOf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalIntPointer(s1.MaxLength, s2.MaxLength) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalIntPointer(s1.MinLength, s2.MinLength) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalIntPointer(s1.MaxItems, s2.MaxItems) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalIntPointer(s1.MinItems, s2.MinItems) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s1.Pattern != s2.Pattern {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s1.UniqueItems != s2.UniqueItems {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalIntPointer(s1.MaxProperties, s2.MaxProperties) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalIntPointer(s1.MinProperties, s2.MinProperties) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !equalStringArray(s1.Required, s2.Required) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return len(s1.Enum) == 0 && len(s2.Enum) == 0 && len(s1.Dependencies) == 0 && len(s2.Dependencies) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func renameDefinition(s *spec.Swagger, old, new string) {
|
|
||||||
old_ref := DEFINITION_PREFIX + old
|
|
||||||
new_ref := DEFINITION_PREFIX + new
|
|
||||||
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
|
|
||||||
if ref.String() == old_ref {
|
|
||||||
return spec.MustCreateRef(new_ref)
|
|
||||||
}
|
|
||||||
return ref
|
|
||||||
}, s).Start()
|
|
||||||
s.Definitions[new] = s.Definitions[old]
|
|
||||||
delete(s.Definitions, old)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy paths and definitions from source to dest, rename definitions if needed.
|
|
||||||
// dest will be mutated, and source will not be changed.
|
|
||||||
func MergeSpecs(dest, source *spec.Swagger) error {
|
|
||||||
source, err := CloneSpec(source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for k, v := range source.Paths.Paths {
|
|
||||||
if _, found := dest.Paths.Paths[k]; found {
|
|
||||||
return fmt.Errorf("Unable to merge: Duplicated path %s", k)
|
|
||||||
}
|
|
||||||
dest.Paths.Paths[k] = v
|
|
||||||
}
|
|
||||||
usedNames := map[string]bool{}
|
|
||||||
for k := range dest.Definitions {
|
|
||||||
usedNames[k] = true
|
|
||||||
}
|
|
||||||
type Rename struct {
|
|
||||||
from, to string
|
|
||||||
}
|
|
||||||
renames := []Rename{}
|
|
||||||
for k, v := range source.Definitions {
|
|
||||||
v2, found := dest.Definitions[k]
|
|
||||||
if found || usedNames[k] {
|
|
||||||
if found && EqualSchema(&v, &v2) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i := 2
|
|
||||||
newName := fmt.Sprintf("%s_v%d", k, i)
|
|
||||||
for usedNames[newName] {
|
|
||||||
i += 1
|
|
||||||
newName = fmt.Sprintf("%s_v%d", k, i)
|
|
||||||
}
|
|
||||||
renames = append(renames, Rename{from: k, to: newName})
|
|
||||||
usedNames[newName] = true
|
|
||||||
} else {
|
|
||||||
usedNames[k] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, r := range renames {
|
|
||||||
renameDefinition(source, r.from, r.to)
|
|
||||||
}
|
|
||||||
for k, v := range source.Definitions {
|
|
||||||
if _, found := dest.Definitions[k]; !found {
|
|
||||||
dest.Definitions[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone OpenAPI spec
|
|
||||||
func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) {
|
|
||||||
if ret, err := cloner.DeepCopy(source); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return ret.(*spec.Swagger), nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,520 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFilterSpecs(t *testing.T) {
|
|
||||||
var spec1, spec1_filtered *spec.Swagger
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test2"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
Test2:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
other:
|
|
||||||
$ref: "#/definitions/Other"
|
|
||||||
Other:
|
|
||||||
type: "string"
|
|
||||||
`), &spec1)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &spec1_filtered)
|
|
||||||
assert := assert.New(t)
|
|
||||||
FilterSpecByPaths(spec1, []string{"/test"})
|
|
||||||
assert.Equal(spec1_filtered, spec1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeSpecsSimple(t *testing.T) {
|
|
||||||
var spec1, spec2, expected *spec.Swagger
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &spec1)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test2"
|
|
||||||
definitions:
|
|
||||||
Test2:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
other:
|
|
||||||
$ref: "#/definitions/Other"
|
|
||||||
Other:
|
|
||||||
type: "string"
|
|
||||||
`), &spec2)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test2"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
Test2:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
other:
|
|
||||||
$ref: "#/definitions/Other"
|
|
||||||
Other:
|
|
||||||
type: "string"
|
|
||||||
`), &expected)
|
|
||||||
assert := assert.New(t)
|
|
||||||
if !assert.NoError(MergeSpecs(spec1, spec2)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(expected, spec1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeSpecsReuseModel(t *testing.T) {
|
|
||||||
var spec1, spec2, expected *spec.Swagger
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &spec1)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
description: "This Test has a description"
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "This status has another description"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &spec2)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &expected)
|
|
||||||
assert := assert.New(t)
|
|
||||||
if !assert.NoError(MergeSpecs(spec1, spec2)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(expected, spec1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeSpecsRenameModel(t *testing.T) {
|
|
||||||
var spec1, spec2, expected *spec.Swagger
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &spec1)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
description: "This Test has a description"
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &spec2)
|
|
||||||
yaml.Unmarshal([]byte(`
|
|
||||||
swagger: "2.0"
|
|
||||||
paths:
|
|
||||||
/test:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test"
|
|
||||||
summary: "Test API"
|
|
||||||
operationId: "addTest"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test"
|
|
||||||
responses:
|
|
||||||
405:
|
|
||||||
description: "Invalid input"
|
|
||||||
$ref: "#/definitions/InvalidInput"
|
|
||||||
/othertest:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- "test2"
|
|
||||||
summary: "Test2 API"
|
|
||||||
operationId: "addTest2"
|
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
|
||||||
- "application/xml"
|
|
||||||
parameters:
|
|
||||||
- in: "body"
|
|
||||||
name: "body"
|
|
||||||
description: "test2 object"
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Test_v2"
|
|
||||||
definitions:
|
|
||||||
Test:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
status:
|
|
||||||
type: "string"
|
|
||||||
description: "Status"
|
|
||||||
Test_v2:
|
|
||||||
description: "This Test has a description"
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
InvalidInput:
|
|
||||||
type: "string"
|
|
||||||
format: "string"
|
|
||||||
`), &expected)
|
|
||||||
assert := assert.New(t)
|
|
||||||
if !assert.NoError(MergeSpecs(spec1, spec2)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expected_yaml, _ := yaml.Marshal(expected)
|
|
||||||
spec1_yaml, _ := yaml.Marshal(spec1)
|
|
||||||
|
|
||||||
assert.Equal(string(expected_yaml), string(spec1_yaml))
|
|
||||||
}
|
|
@ -17,10 +17,7 @@ limitations under the License.
|
|||||||
package apiserver
|
package apiserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||||
@ -36,15 +33,6 @@ import (
|
|||||||
kubeinformers "k8s.io/client-go/informers"
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/pkg/version"
|
"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"
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
|
"k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
||||||
@ -62,10 +50,6 @@ var (
|
|||||||
Codecs = serializer.NewCodecFactory(Scheme)
|
Codecs = serializer.NewCodecFactory(Scheme)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
LOAD_OPENAPI_SPEC_MAX_RETRIES = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
install.Install(groupFactoryRegistry, registry, Scheme)
|
install.Install(groupFactoryRegistry, registry, Scheme)
|
||||||
|
|
||||||
@ -123,24 +107,6 @@ type APIAggregator struct {
|
|||||||
// handledGroups are the groups that already have routes
|
// handledGroups are the groups that already have routes
|
||||||
handledGroups sets.String
|
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
|
// lister is used to add group handling for /apis/<group> aggregator lookups based on
|
||||||
// controller state
|
// controller state
|
||||||
lister listers.APIServiceLister
|
lister listers.APIServiceLister
|
||||||
@ -193,14 +159,11 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
|
|||||||
s := &APIAggregator{
|
s := &APIAggregator{
|
||||||
GenericAPIServer: genericServer,
|
GenericAPIServer: genericServer,
|
||||||
delegateHandler: delegationTarget.UnprotectedHandler(),
|
delegateHandler: delegationTarget.UnprotectedHandler(),
|
||||||
delegationSpec: delegationTarget.OpenAPISpec(),
|
|
||||||
contextMapper: c.GenericConfig.RequestContextMapper,
|
contextMapper: c.GenericConfig.RequestContextMapper,
|
||||||
proxyClientCert: c.ProxyClientCert,
|
proxyClientCert: c.ProxyClientCert,
|
||||||
proxyClientKey: c.ProxyClientKey,
|
proxyClientKey: c.ProxyClientKey,
|
||||||
proxyTransport: c.ProxyTransport,
|
proxyTransport: c.ProxyTransport,
|
||||||
proxyHandlers: map[string]*proxyHandler{},
|
proxyHandlers: map[string]*proxyHandler{},
|
||||||
apiServiceSpecs: map[string]*spec.Swagger{},
|
|
||||||
toLoadAPISpec: map[string]int{},
|
|
||||||
handledGroups: sets.String{},
|
handledGroups: sets.String{},
|
||||||
lister: informerFactory.Apiregistration().InternalVersion().APIServices().Lister(),
|
lister: informerFactory.Apiregistration().InternalVersion().APIServices().Lister(),
|
||||||
APIRegistrationInformers: informerFactory,
|
APIRegistrationInformers: informerFactory,
|
||||||
@ -249,20 +212,6 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
|
|||||||
return nil
|
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
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,9 +242,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) {
|
|||||||
}
|
}
|
||||||
proxyHandler.updateAPIService(apiService)
|
proxyHandler.updateAPIService(apiService)
|
||||||
s.proxyHandlers[apiService.Name] = proxyHandler
|
s.proxyHandlers[apiService.Name] = proxyHandler
|
||||||
|
|
||||||
s.deferLoadAPISpec(apiService.Name)
|
|
||||||
|
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
|
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(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)
|
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.
|
// 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.
|
// It's a slow moving API, so its ok to run the controller on a single thread.
|
||||||
func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
|
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)
|
||||||
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
|
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
|
||||||
delete(s.proxyHandlers, apiServiceName)
|
delete(s.proxyHandlers, apiServiceName)
|
||||||
s.deleteApiSpec(apiServiceName)
|
|
||||||
s.updateOpenAPISpec()
|
|
||||||
|
|
||||||
// TODO unregister group level discovery when there are no more versions for the group
|
// 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
|
// 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)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user