Update main repo references to new kube-openapi repo
This commit is contained in:
@@ -38,7 +38,6 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
@@ -53,6 +52,7 @@ import (
|
|||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
|
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
|
||||||
//aggregatorinformers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
|
//aggregatorinformers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
|
||||||
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
clientgoinformers "k8s.io/client-go/informers"
|
clientgoinformers "k8s.io/client-go/informers"
|
||||||
clientgoclientset "k8s.io/client-go/kubernetes"
|
clientgoclientset "k8s.io/client-go/kubernetes"
|
||||||
|
@@ -32,7 +32,6 @@ import (
|
|||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1"
|
extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
apimachineryopenapi "k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
@@ -40,6 +39,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/server/filters"
|
"k8s.io/apiserver/pkg/server/filters"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||||
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options"
|
"k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
@@ -452,7 +452,7 @@ func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swag
|
|||||||
}
|
}
|
||||||
s.Definitions[k] = spec.Schema{
|
s.Definitions[k] = spec.Schema{
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Ref: spec.MustCreateRef("#/definitions/" + apimachineryopenapi.EscapeJsonPointer(v)),
|
Ref: spec.MustCreateRef("#/definitions/" + openapicommon.EscapeJsonPointer(v)),
|
||||||
Description: fmt.Sprintf("Deprecated. Please use %s instead.", v),
|
Description: fmt.Sprintf("Deprecated. Please use %s instead.", v),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -582,7 +582,6 @@ staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1
|
|||||||
staging/src/k8s.io/apimachinery/pkg/conversion
|
staging/src/k8s.io/apimachinery/pkg/conversion
|
||||||
staging/src/k8s.io/apimachinery/pkg/conversion/unstructured
|
staging/src/k8s.io/apimachinery/pkg/conversion/unstructured
|
||||||
staging/src/k8s.io/apimachinery/pkg/labels
|
staging/src/k8s.io/apimachinery/pkg/labels
|
||||||
staging/src/k8s.io/apimachinery/pkg/openapi
|
|
||||||
staging/src/k8s.io/apimachinery/pkg/runtime/schema
|
staging/src/k8s.io/apimachinery/pkg/runtime/schema
|
||||||
staging/src/k8s.io/apimachinery/pkg/runtime/serializer
|
staging/src/k8s.io/apimachinery/pkg/runtime/serializer
|
||||||
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf
|
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf
|
||||||
@@ -654,7 +653,6 @@ staging/src/k8s.io/apiserver/pkg/registry/rest/resttest
|
|||||||
staging/src/k8s.io/apiserver/pkg/server
|
staging/src/k8s.io/apiserver/pkg/server
|
||||||
staging/src/k8s.io/apiserver/pkg/server/healthz
|
staging/src/k8s.io/apiserver/pkg/server/healthz
|
||||||
staging/src/k8s.io/apiserver/pkg/server/httplog
|
staging/src/k8s.io/apiserver/pkg/server/httplog
|
||||||
staging/src/k8s.io/apiserver/pkg/server/openapi
|
|
||||||
staging/src/k8s.io/apiserver/pkg/server/options
|
staging/src/k8s.io/apiserver/pkg/server/options
|
||||||
staging/src/k8s.io/apiserver/pkg/server/routes/data/swagger
|
staging/src/k8s.io/apiserver/pkg/server/routes/data/swagger
|
||||||
staging/src/k8s.io/apiserver/pkg/server/storage
|
staging/src/k8s.io/apiserver/pkg/server/storage
|
||||||
@@ -674,7 +672,6 @@ staging/src/k8s.io/apiserver/pkg/util/feature
|
|||||||
staging/src/k8s.io/apiserver/pkg/util/flag
|
staging/src/k8s.io/apiserver/pkg/util/flag
|
||||||
staging/src/k8s.io/apiserver/pkg/util/proxy
|
staging/src/k8s.io/apiserver/pkg/util/proxy
|
||||||
staging/src/k8s.io/apiserver/pkg/util/trace
|
staging/src/k8s.io/apiserver/pkg/util/trace
|
||||||
staging/src/k8s.io/apiserver/pkg/util/trie
|
|
||||||
staging/src/k8s.io/apiserver/pkg/util/webhook
|
staging/src/k8s.io/apiserver/pkg/util/webhook
|
||||||
staging/src/k8s.io/apiserver/pkg/util/wsstream
|
staging/src/k8s.io/apiserver/pkg/util/wsstream
|
||||||
staging/src/k8s.io/apiserver/plugin/pkg/audit/log
|
staging/src/k8s.io/apiserver/plugin/pkg/audit/log
|
||||||
@@ -782,7 +779,6 @@ staging/src/k8s.io/kube-gen/cmd/conversion-gen/generators
|
|||||||
staging/src/k8s.io/kube-gen/cmd/go-to-protobuf/protobuf
|
staging/src/k8s.io/kube-gen/cmd/go-to-protobuf/protobuf
|
||||||
staging/src/k8s.io/kube-gen/cmd/informer-gen/generators
|
staging/src/k8s.io/kube-gen/cmd/informer-gen/generators
|
||||||
staging/src/k8s.io/kube-gen/cmd/lister-gen/generators
|
staging/src/k8s.io/kube-gen/cmd/lister-gen/generators
|
||||||
staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators
|
|
||||||
staging/src/k8s.io/metrics/pkg/apis/custom_metrics
|
staging/src/k8s.io/metrics/pkg/apis/custom_metrics
|
||||||
staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1
|
staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1
|
||||||
staging/src/k8s.io/metrics/pkg/apis/metrics
|
staging/src/k8s.io/metrics/pkg/apis/metrics
|
||||||
|
@@ -65,15 +65,16 @@ function print_forbidden_imports () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RC=0
|
RC=0
|
||||||
print_forbidden_imports apimachinery should_be_leaf || RC=1
|
print_forbidden_imports apimachinery k8s.io/kube-openapi || RC=1
|
||||||
print_forbidden_imports api k8s.io/apimachinery || RC=1
|
print_forbidden_imports api k8s.io/apimachinery || RC=1
|
||||||
print_forbidden_imports kube-gen k8s.io/apimachinery k8s.io/client-go k8s.io/gengo || RC=1
|
print_forbidden_imports kube-gen k8s.io/apimachinery k8s.io/client-go k8s.io/gengo k8s.io/kube-openapi || RC=1
|
||||||
print_forbidden_imports client-go k8s.io/apimachinery k8s.io/api || RC=1
|
print_forbidden_imports client-go k8s.io/apimachinery k8s.io/api || RC=1
|
||||||
print_forbidden_imports apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/api || RC=1
|
print_forbidden_imports apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/api k8s.io/kube-openapi || RC=1
|
||||||
print_forbidden_imports metrics k8s.io/apimachinery k8s.io/client-go k8s.io/api || RC=1
|
print_forbidden_imports metrics k8s.io/apimachinery k8s.io/client-go k8s.io/api || RC=1
|
||||||
print_forbidden_imports kube-aggregator k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
|
print_forbidden_imports kube-aggregator k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api k8s.io/kube-openapi || RC=1
|
||||||
print_forbidden_imports sample-apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
|
print_forbidden_imports sample-apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
|
||||||
print_forbidden_imports apiextensions-apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
|
print_forbidden_imports apiextensions-apiserver k8s.io/apimachinery k8s.io/client-go k8s.io/apiserver k8s.io/api || RC=1
|
||||||
|
print_forbidden_imports kube-openapi k8s.io/gengo || RC=1
|
||||||
if [ ${RC} != 0 ]; then
|
if [ ${RC} != 0 ]; then
|
||||||
exit ${RC}
|
exit ${RC}
|
||||||
fi
|
fi
|
||||||
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/google/gofuzz"
|
"github.com/google/gofuzz"
|
||||||
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/google/gofuzz"
|
"github.com/google/gofuzz"
|
||||||
|
@@ -4,7 +4,7 @@ load("@io_kubernetes_build//defs:go.bzl", "go_genrule")
|
|||||||
def openapi_library(name, tags, srcs, openapi_targets=[], vendor_targets=[]):
|
def openapi_library(name, tags, srcs, openapi_targets=[], vendor_targets=[]):
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
|
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
||||||
] + ["//%s:go_default_library" % target for target in openapi_targets] + ["//vendor/%s:go_default_library" % target for target in vendor_targets]
|
] + ["//%s:go_default_library" % target for target in openapi_targets] + ["//vendor/%s:go_default_library" % target for target in vendor_targets]
|
||||||
go_library(
|
go_library(
|
||||||
name=name,
|
name=name,
|
||||||
|
@@ -29,7 +29,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
inf "gopkg.in/inf.v0"
|
inf "gopkg.in/inf.v0"
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Quantity is a fixed-point representation of a number.
|
// Quantity is a fixed-point representation of a number.
|
||||||
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/google/gofuzz"
|
"github.com/google/gofuzz"
|
||||||
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/google/gofuzz"
|
"github.com/google/gofuzz"
|
||||||
|
@@ -1,161 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi.
|
|
||||||
type OpenAPIDefinition struct {
|
|
||||||
Schema spec.Schema
|
|
||||||
Dependencies []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReferenceCallback func(path string) spec.Ref
|
|
||||||
|
|
||||||
// OpenAPIDefinitions is collection of all definitions.
|
|
||||||
type GetOpenAPIDefinitions func(ReferenceCallback) map[string]OpenAPIDefinition
|
|
||||||
|
|
||||||
// OpenAPIDefinitionGetter gets openAPI definitions for a given type. If a type implements this interface,
|
|
||||||
// the definition returned by it will be used, otherwise the auto-generated definitions will be used. See
|
|
||||||
// GetOpenAPITypeFormat for more information about trade-offs of using this interface or GetOpenAPITypeFormat method when
|
|
||||||
// possible.
|
|
||||||
type OpenAPIDefinitionGetter interface {
|
|
||||||
OpenAPIDefinition() *OpenAPIDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is set of configuration for openAPI spec generation.
|
|
||||||
type Config struct {
|
|
||||||
// List of supported protocols such as https, http, etc.
|
|
||||||
ProtocolList []string
|
|
||||||
|
|
||||||
// Info is general information about the API.
|
|
||||||
Info *spec.Info
|
|
||||||
|
|
||||||
// DefaultResponse will be used if an operation does not have any responses listed. It
|
|
||||||
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
|
|
||||||
DefaultResponse *spec.Response
|
|
||||||
|
|
||||||
// CommonResponses will be added as a response to all operation specs. This is a good place to add common
|
|
||||||
// responses such as authorization failed.
|
|
||||||
CommonResponses map[int]spec.Response
|
|
||||||
|
|
||||||
// List of webservice's path prefixes to ignore
|
|
||||||
IgnorePrefixes []string
|
|
||||||
|
|
||||||
// OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map
|
|
||||||
// or any of the models will result in spec generation failure.
|
|
||||||
GetDefinitions GetOpenAPIDefinitions
|
|
||||||
|
|
||||||
// GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs.
|
|
||||||
GetOperationIDAndTags func(r *restful.Route) (string, []string, error)
|
|
||||||
|
|
||||||
// GetDefinitionName returns a friendly name for a definition base on the serving path. parameter `name` is the full name of the definition.
|
|
||||||
// It is an optional function to customize model names.
|
|
||||||
GetDefinitionName func(name string) (string, spec.Extensions)
|
|
||||||
|
|
||||||
// PostProcessSpec runs after the spec is ready to serve. It allows a final modification to the spec before serving.
|
|
||||||
PostProcessSpec func(*spec.Swagger) (*spec.Swagger, error)
|
|
||||||
|
|
||||||
// SecurityDefinitions is list of all security definitions for OpenAPI service. If this is not nil, the user of config
|
|
||||||
// is responsible to provide DefaultSecurity and (maybe) add unauthorized response to CommonResponses.
|
|
||||||
SecurityDefinitions *spec.SecurityDefinitions
|
|
||||||
|
|
||||||
// DefaultSecurity for all operations. This will pass as spec.SwaggerProps.Security to OpenAPI.
|
|
||||||
// For most cases, this will be list of acceptable definitions in SecurityDefinitions.
|
|
||||||
DefaultSecurity []map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
|
|
||||||
// two ways to customize spec for a type. If you add it here, a type will be converted to a simple type and the type
|
|
||||||
// comment (the comment that is added before type definition) will be lost. The spec will still have the property
|
|
||||||
// comment. The second way is to implement OpenAPIDefinitionGetter interface. That function can customize the spec (so
|
|
||||||
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
|
|
||||||
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
|
|
||||||
// Example:
|
|
||||||
// type Sample struct {
|
|
||||||
// ...
|
|
||||||
// // port of the server
|
|
||||||
// port IntOrString
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
// // IntOrString documentation...
|
|
||||||
// type IntOrString { ... }
|
|
||||||
//
|
|
||||||
// Adding IntOrString to this function:
|
|
||||||
// "port" : {
|
|
||||||
// format: "string",
|
|
||||||
// type: "int-or-string",
|
|
||||||
// Description: "port of the server"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Implement OpenAPIDefinitionGetter for IntOrString:
|
|
||||||
//
|
|
||||||
// "port" : {
|
|
||||||
// $Ref: "#/definitions/IntOrString"
|
|
||||||
// Description: "port of the server"
|
|
||||||
// }
|
|
||||||
// ...
|
|
||||||
// definitions:
|
|
||||||
// {
|
|
||||||
// "IntOrString": {
|
|
||||||
// format: "string",
|
|
||||||
// type: "int-or-string",
|
|
||||||
// Description: "IntOrString documentation..." // new
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func GetOpenAPITypeFormat(typeName string) (string, string) {
|
|
||||||
schemaTypeFormatMap := map[string][]string{
|
|
||||||
"uint": {"integer", "int32"},
|
|
||||||
"uint8": {"integer", "byte"},
|
|
||||||
"uint16": {"integer", "int32"},
|
|
||||||
"uint32": {"integer", "int64"},
|
|
||||||
"uint64": {"integer", "int64"},
|
|
||||||
"int": {"integer", "int32"},
|
|
||||||
"int8": {"integer", "byte"},
|
|
||||||
"int16": {"integer", "int32"},
|
|
||||||
"int32": {"integer", "int32"},
|
|
||||||
"int64": {"integer", "int64"},
|
|
||||||
"byte": {"integer", "byte"},
|
|
||||||
"float64": {"number", "double"},
|
|
||||||
"float32": {"number", "float"},
|
|
||||||
"bool": {"boolean", ""},
|
|
||||||
"time.Time": {"string", "date-time"},
|
|
||||||
"string": {"string", ""},
|
|
||||||
"integer": {"integer", ""},
|
|
||||||
"number": {"number", ""},
|
|
||||||
"boolean": {"boolean", ""},
|
|
||||||
"[]byte": {"string", "byte"}, // base64 encoded characters
|
|
||||||
"interface{}": {"object", ""},
|
|
||||||
}
|
|
||||||
mapped, ok := schemaTypeFormatMap[typeName]
|
|
||||||
if !ok {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
return mapped[0], mapped[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func EscapeJsonPointer(p string) string {
|
|
||||||
// Escaping reference name using rfc6901
|
|
||||||
p = strings.Replace(p, "~", "~0", -1)
|
|
||||||
p = strings.Replace(p, "/", "~1", -1)
|
|
||||||
return p
|
|
||||||
}
|
|
@@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// package openapi holds shared codes and types between open API code generator and spec generator.
|
|
||||||
package openapi
|
|
@@ -24,7 +24,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
@@ -30,10 +30,10 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/util/trie"
|
"k8s.io/kube-openapi/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var verbs = trie.New([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
|
var verbs = util.NewTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
|
||||||
|
|
||||||
const (
|
const (
|
||||||
extensionGVK = "x-kubernetes-group-version-kind"
|
extensionGVK = "x-kubernetes-group-version-kind"
|
||||||
|
@@ -33,7 +33,6 @@ import (
|
|||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
|
|
||||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
@@ -61,6 +60,7 @@ import (
|
|||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
|
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
|
||||||
)
|
)
|
||||||
|
@@ -30,7 +30,6 @@ import (
|
|||||||
"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"
|
||||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
@@ -44,6 +43,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/server/healthz"
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
"k8s.io/apiserver/pkg/server/routes"
|
"k8s.io/apiserver/pkg/server/routes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info about an API group.
|
// Info about an API group.
|
||||||
|
@@ -34,7 +34,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/apimachinery"
|
"k8s.io/apimachinery/pkg/apimachinery"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
@@ -51,6 +50,7 @@ import (
|
|||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
reviewers:
|
|
||||||
- yujuhong
|
|
||||||
- gmarek
|
|
||||||
- mbohlool
|
|
||||||
- philips
|
|
||||||
approvers:
|
|
||||||
- mbohlool
|
|
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package openapi contains code to generate OpenAPI discovery spec (which
|
|
||||||
// initial version of it also known as Swagger 2.0).
|
|
||||||
// For more details: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
|
|
||||||
package openapi
|
|
@@ -1,519 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
"github.com/googleapis/gnostic/compiler"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
genericmux "k8s.io/apiserver/pkg/server/mux"
|
|
||||||
"k8s.io/apiserver/pkg/util/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
OpenAPIVersion = "2.0"
|
|
||||||
extensionPrefix = "x-kubernetes-"
|
|
||||||
|
|
||||||
JSON_EXT = ".json"
|
|
||||||
|
|
||||||
MIME_JSON = "application/json"
|
|
||||||
// TODO(mehdy): change @68f4ded to a version tag when gnostic add version tags.
|
|
||||||
MIME_PB = "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf"
|
|
||||||
MIME_PB_GZ = "application/x-gzip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type openAPI struct {
|
|
||||||
config *openapi.Config
|
|
||||||
swagger *spec.Swagger
|
|
||||||
swaggerBytes []byte
|
|
||||||
swaggerPb []byte
|
|
||||||
swaggerPbGz []byte
|
|
||||||
lastModified time.Time
|
|
||||||
protocolList []string
|
|
||||||
definitions map[string]openapi.OpenAPIDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
func computeEtag(data []byte) string {
|
|
||||||
return fmt.Sprintf("\"%X\"", sha512.Sum512(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
|
|
||||||
func RegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *openapi.Config, mux *genericmux.PathRecorderMux) (err error) {
|
|
||||||
|
|
||||||
if !strings.HasSuffix(servePath, JSON_EXT) {
|
|
||||||
return fmt.Errorf("Serving path must ends with \"%s\".", JSON_EXT)
|
|
||||||
}
|
|
||||||
|
|
||||||
servePathBase := servePath[:len(servePath)-len(JSON_EXT)]
|
|
||||||
|
|
||||||
o := openAPI{
|
|
||||||
config: config,
|
|
||||||
swagger: &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Swagger: OpenAPIVersion,
|
|
||||||
Definitions: spec.Definitions{},
|
|
||||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
|
||||||
Info: config.Info,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = o.init(webServices)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mime.AddExtensionType(".json", MIME_JSON)
|
|
||||||
mime.AddExtensionType(".pb-v1", MIME_PB)
|
|
||||||
mime.AddExtensionType(".gz", MIME_PB_GZ)
|
|
||||||
|
|
||||||
type fileInfo struct {
|
|
||||||
ext string
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []fileInfo{
|
|
||||||
{".json", o.swaggerBytes},
|
|
||||||
{"-2.0.0.json", o.swaggerBytes},
|
|
||||||
{"-2.0.0.pb-v1", o.swaggerPb},
|
|
||||||
{"-2.0.0.pb-v1.gz", o.swaggerPbGz},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
path := servePathBase + file.ext
|
|
||||||
data := file.data
|
|
||||||
etag := computeEtag(file.data)
|
|
||||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != path {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
w.Write([]byte("Path not found!"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Etag", etag)
|
|
||||||
// ServeContent will take care of caching using eTag.
|
|
||||||
http.ServeContent(w, r, path, o.lastModified, bytes.NewReader(data))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) init(webServices []*restful.WebService) error {
|
|
||||||
if o.config.GetOperationIDAndTags == nil {
|
|
||||||
o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) {
|
|
||||||
return r.Operation, nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if o.config.GetDefinitionName == nil {
|
|
||||||
o.config.GetDefinitionName = func(name string) (string, spec.Extensions) {
|
|
||||||
return name[strings.LastIndex(name, "/")+1:], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
|
|
||||||
defName, _ := o.config.GetDefinitionName(name)
|
|
||||||
return spec.MustCreateRef("#/definitions/" + openapi.EscapeJsonPointer(defName))
|
|
||||||
})
|
|
||||||
if o.config.CommonResponses == nil {
|
|
||||||
o.config.CommonResponses = map[int]spec.Response{}
|
|
||||||
}
|
|
||||||
err := o.buildPaths(webServices)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if o.config.SecurityDefinitions != nil {
|
|
||||||
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
|
|
||||||
o.swagger.Security = o.config.DefaultSecurity
|
|
||||||
}
|
|
||||||
if o.config.PostProcessSpec != nil {
|
|
||||||
o.swagger, err = o.config.PostProcessSpec(o.swagger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
o.swaggerBytes, err = json.MarshalIndent(o.swagger, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.swaggerPb, err = toProtoBinary(o.swaggerBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.swaggerPbGz = toGzip(o.swaggerPb)
|
|
||||||
o.lastModified = time.Now()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toProtoBinary(spec []byte) ([]byte, error) {
|
|
||||||
var info yaml.MapSlice
|
|
||||||
err := yaml.Unmarshal(spec, &info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
document, err := openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return proto.Marshal(document)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toGzip(data []byte) []byte {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
zw := gzip.NewWriter(&buf)
|
|
||||||
zw.Write(data)
|
|
||||||
zw.Close()
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCanonicalizeTypeName(t reflect.Type) string {
|
|
||||||
if t.PkgPath() == "" {
|
|
||||||
return t.Name()
|
|
||||||
}
|
|
||||||
path := t.PkgPath()
|
|
||||||
if strings.Contains(path, "/vendor/") {
|
|
||||||
path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
|
|
||||||
}
|
|
||||||
return path + "." + t.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildDefinitionRecursively(name string) error {
|
|
||||||
uniqueName, extensions := o.config.GetDefinitionName(name)
|
|
||||||
if _, ok := o.swagger.Definitions[uniqueName]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if item, ok := o.definitions[name]; ok {
|
|
||||||
schema := spec.Schema{
|
|
||||||
VendorExtensible: item.Schema.VendorExtensible,
|
|
||||||
SchemaProps: item.Schema.SchemaProps,
|
|
||||||
SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
|
|
||||||
}
|
|
||||||
if extensions != nil {
|
|
||||||
if schema.Extensions == nil {
|
|
||||||
schema.Extensions = spec.Extensions{}
|
|
||||||
}
|
|
||||||
for k, v := range extensions {
|
|
||||||
schema.Extensions[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.swagger.Definitions[uniqueName] = schema
|
|
||||||
for _, v := range item.Dependencies {
|
|
||||||
if err := o.buildDefinitionRecursively(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again.", name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefinitionForType build a definition for a given type and return a referable name to it's definition.
|
|
||||||
// This is the main function that keep track of definitions used in this spec and is depend on code generated
|
|
||||||
// by k8s.io/kube-gen/cmd/openapi-gen.
|
|
||||||
func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
|
|
||||||
t := reflect.TypeOf(sample)
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
name := getCanonicalizeTypeName(t)
|
|
||||||
if err := o.buildDefinitionRecursively(name); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defName, _ := o.config.GetDefinitionName(name)
|
|
||||||
return "#/definitions/" + openapi.EscapeJsonPointer(defName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildPaths builds OpenAPI paths using go-restful's web services.
|
|
||||||
func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
|
|
||||||
pathsToIgnore := trie.New(o.config.IgnorePrefixes)
|
|
||||||
duplicateOpId := make(map[string]string)
|
|
||||||
for _, w := range webServices {
|
|
||||||
rootPath := w.RootPath()
|
|
||||||
if pathsToIgnore.HasPrefix(rootPath) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
commonParams, err := o.buildParameters(w.PathParameters())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for path, routes := range groupRoutesByPath(w.Routes()) {
|
|
||||||
// go-swagger has special variable definition {$NAME:*} that can only be
|
|
||||||
// used at the end of the path and it is not recognized by OpenAPI.
|
|
||||||
if strings.HasSuffix(path, ":*}") {
|
|
||||||
path = path[:len(path)-3] + "}"
|
|
||||||
}
|
|
||||||
if pathsToIgnore.HasPrefix(path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Aggregating common parameters make API spec (and generated clients) simpler
|
|
||||||
inPathCommonParamsMap, err := o.findCommonParameters(routes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pathItem, exists := o.swagger.Paths.Paths[path]
|
|
||||||
if exists {
|
|
||||||
return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
|
|
||||||
}
|
|
||||||
pathItem = spec.PathItem{
|
|
||||||
PathItemProps: spec.PathItemProps{
|
|
||||||
Parameters: make([]spec.Parameter, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// add web services's parameters as well as any parameters appears in all ops, as common parameters
|
|
||||||
pathItem.Parameters = append(pathItem.Parameters, commonParams...)
|
|
||||||
for _, p := range inPathCommonParamsMap {
|
|
||||||
pathItem.Parameters = append(pathItem.Parameters, p)
|
|
||||||
}
|
|
||||||
sortParameters(pathItem.Parameters)
|
|
||||||
for _, route := range routes {
|
|
||||||
op, err := o.buildOperations(route, inPathCommonParamsMap)
|
|
||||||
sortParameters(op.Parameters)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dpath, exists := duplicateOpId[op.ID]
|
|
||||||
if exists {
|
|
||||||
return fmt.Errorf("Duplicate Operation ID %v for path %v and %v.", op.ID, dpath, path)
|
|
||||||
} else {
|
|
||||||
duplicateOpId[op.ID] = path
|
|
||||||
}
|
|
||||||
switch strings.ToUpper(route.Method) {
|
|
||||||
case "GET":
|
|
||||||
pathItem.Get = op
|
|
||||||
case "POST":
|
|
||||||
pathItem.Post = op
|
|
||||||
case "HEAD":
|
|
||||||
pathItem.Head = op
|
|
||||||
case "PUT":
|
|
||||||
pathItem.Put = op
|
|
||||||
case "DELETE":
|
|
||||||
pathItem.Delete = op
|
|
||||||
case "OPTIONS":
|
|
||||||
pathItem.Options = op
|
|
||||||
case "PATCH":
|
|
||||||
pathItem.Patch = op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.swagger.Paths.Paths[path] = pathItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildOperations builds operations for each webservice path
|
|
||||||
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
|
|
||||||
ret = &spec.Operation{
|
|
||||||
OperationProps: spec.OperationProps{
|
|
||||||
Description: route.Doc,
|
|
||||||
Consumes: route.Consumes,
|
|
||||||
Produces: route.Produces,
|
|
||||||
Schemes: o.config.ProtocolList,
|
|
||||||
Responses: &spec.Responses{
|
|
||||||
ResponsesProps: spec.ResponsesProps{
|
|
||||||
StatusCodeResponses: make(map[int]spec.Response),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for k, v := range route.Metadata {
|
|
||||||
if strings.HasPrefix(k, extensionPrefix) {
|
|
||||||
if ret.Extensions == nil {
|
|
||||||
ret.Extensions = spec.Extensions{}
|
|
||||||
}
|
|
||||||
ret.Extensions.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTags(&route); err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build responses
|
|
||||||
for _, resp := range route.ResponseErrors {
|
|
||||||
ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there is no response but a write sample, assume that write sample is an http.StatusOK response.
|
|
||||||
if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil {
|
|
||||||
ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK")
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for code, resp := range o.config.CommonResponses {
|
|
||||||
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
|
|
||||||
ret.Responses.StatusCodeResponses[code] = resp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there is still no response, use default response provided.
|
|
||||||
if len(ret.Responses.StatusCodeResponses) == 0 {
|
|
||||||
ret.Responses.Default = o.config.DefaultResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build non-common Parameters
|
|
||||||
ret.Parameters = make([]spec.Parameter, 0)
|
|
||||||
for _, param := range route.ParameterDocs {
|
|
||||||
if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
|
|
||||||
openAPIParam, err := o.buildParameter(param.Data(), route.ReadSample)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
ret.Parameters = append(ret.Parameters, openAPIParam)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
|
|
||||||
schema, err := o.toSchema(model)
|
|
||||||
if err != nil {
|
|
||||||
return spec.Response{}, err
|
|
||||||
}
|
|
||||||
return spec.Response{
|
|
||||||
ResponseProps: spec.ResponseProps{
|
|
||||||
Description: description,
|
|
||||||
Schema: schema,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
|
|
||||||
commonParamsMap := make(map[interface{}]spec.Parameter, 0)
|
|
||||||
paramOpsCountByName := make(map[interface{}]int, 0)
|
|
||||||
paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
|
|
||||||
for _, route := range routes {
|
|
||||||
routeParamDuplicateMap := make(map[interface{}]bool)
|
|
||||||
s := ""
|
|
||||||
for _, param := range route.ParameterDocs {
|
|
||||||
m, _ := json.Marshal(param.Data())
|
|
||||||
s += string(m) + "\n"
|
|
||||||
key := mapKeyFromParam(param)
|
|
||||||
if routeParamDuplicateMap[key] {
|
|
||||||
msg, _ := json.Marshal(route.ParameterDocs)
|
|
||||||
return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v.", param.Data().Name, string(msg), s)
|
|
||||||
}
|
|
||||||
routeParamDuplicateMap[key] = true
|
|
||||||
paramOpsCountByName[key]++
|
|
||||||
paramNameKindToDataMap[key] = param.Data()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for key, count := range paramOpsCountByName {
|
|
||||||
paramData := paramNameKindToDataMap[key]
|
|
||||||
if count == len(routes) && paramData.Kind != restful.BodyParameterKind {
|
|
||||||
openAPIParam, err := o.buildParameter(paramData, nil)
|
|
||||||
if err != nil {
|
|
||||||
return commonParamsMap, err
|
|
||||||
}
|
|
||||||
commonParamsMap[key] = openAPIParam
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commonParamsMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) toSchema(model interface{}) (_ *spec.Schema, err error) {
|
|
||||||
if openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(getCanonicalizeTypeName(reflect.TypeOf(model))); openAPIType != "" {
|
|
||||||
return &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{openAPIType},
|
|
||||||
Format: openAPIFormat,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
ref, err := o.buildDefinitionForType(model)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Ref: spec.MustCreateRef(ref),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample interface{}) (ret spec.Parameter, err error) {
|
|
||||||
ret = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: restParam.Name,
|
|
||||||
Description: restParam.Description,
|
|
||||||
Required: restParam.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
switch restParam.Kind {
|
|
||||||
case restful.BodyParameterKind:
|
|
||||||
if bodySample != nil {
|
|
||||||
ret.In = "body"
|
|
||||||
ret.Schema, err = o.toSchema(bodySample)
|
|
||||||
return ret, err
|
|
||||||
} else {
|
|
||||||
// There is not enough information in the body parameter to build the definition.
|
|
||||||
// Body parameter has a data type that is a short name but we need full package name
|
|
||||||
// of the type to create a definition.
|
|
||||||
return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType)
|
|
||||||
}
|
|
||||||
case restful.PathParameterKind:
|
|
||||||
ret.In = "path"
|
|
||||||
if !restParam.Required {
|
|
||||||
return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
|
|
||||||
}
|
|
||||||
case restful.QueryParameterKind:
|
|
||||||
ret.In = "query"
|
|
||||||
case restful.HeaderParameterKind:
|
|
||||||
ret.In = "header"
|
|
||||||
case restful.FormParameterKind:
|
|
||||||
ret.In = "formData"
|
|
||||||
default:
|
|
||||||
return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind)
|
|
||||||
}
|
|
||||||
openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(restParam.DataType)
|
|
||||||
if openAPIType == "" {
|
|
||||||
return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType)
|
|
||||||
}
|
|
||||||
ret.Type = openAPIType
|
|
||||||
ret.Format = openAPIFormat
|
|
||||||
ret.UniqueItems = !restParam.AllowMultiple
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
|
|
||||||
ret = make([]spec.Parameter, len(restParam))
|
|
||||||
for i, v := range restParam {
|
|
||||||
ret[i], err = o.buildParameter(v.Data(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
@@ -1,465 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// setUp is a convenience function for setting up for (most) tests.
|
|
||||||
func setUp(t *testing.T, fullMethods bool) (openAPI, *restful.Container, *assert.Assertions) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
config, container := getConfig(fullMethods)
|
|
||||||
return openAPI{
|
|
||||||
config: config,
|
|
||||||
swagger: &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Swagger: OpenAPIVersion,
|
|
||||||
Definitions: spec.Definitions{},
|
|
||||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
|
||||||
Info: config.Info,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, container, assert
|
|
||||||
}
|
|
||||||
|
|
||||||
func noOp(request *restful.Request, response *restful.Response) {}
|
|
||||||
|
|
||||||
// Test input
|
|
||||||
type TestInput struct {
|
|
||||||
// Name of the input
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
// ID of the input
|
|
||||||
ID int `json:"id,omitempty"`
|
|
||||||
Tags []string `json:"tags,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test output
|
|
||||||
type TestOutput struct {
|
|
||||||
// Name of the output
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
// Number of outputs
|
|
||||||
Count int `json:"count,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ TestInput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
|
||||||
schema := spec.Schema{}
|
|
||||||
schema.Description = "Test input"
|
|
||||||
schema.Properties = map[string]spec.Schema{
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the input",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "ID of the input",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "",
|
|
||||||
Type: []string{"array"},
|
|
||||||
Items: &spec.SchemaOrArray{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
schema.Extensions = spec.Extensions{"x-test": "test"}
|
|
||||||
return &openapi.OpenAPIDefinition{
|
|
||||||
Schema: schema,
|
|
||||||
Dependencies: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ TestOutput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
|
||||||
schema := spec.Schema{}
|
|
||||||
schema.Description = "Test output"
|
|
||||||
schema.Properties = map[string]spec.Schema{
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the output",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"count": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Number of outputs",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &openapi.OpenAPIDefinition{
|
|
||||||
Schema: schema,
|
|
||||||
Dependencies: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ openapi.OpenAPIDefinitionGetter = TestInput{}
|
|
||||||
var _ openapi.OpenAPIDefinitionGetter = TestOutput{}
|
|
||||||
|
|
||||||
func getTestRoute(ws *restful.WebService, method string, additionalParams bool, opPrefix string) *restful.RouteBuilder {
|
|
||||||
ret := ws.Method(method).
|
|
||||||
Path("/test/{path:*}").
|
|
||||||
Doc(fmt.Sprintf("%s test input", method)).
|
|
||||||
Operation(fmt.Sprintf("%s%sTestInput", method, opPrefix)).
|
|
||||||
Produces(restful.MIME_JSON).
|
|
||||||
Consumes(restful.MIME_JSON).
|
|
||||||
Param(ws.PathParameter("path", "path to the resource").DataType("string")).
|
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
|
||||||
Reads(TestInput{}).
|
|
||||||
Returns(200, "OK", TestOutput{}).
|
|
||||||
Writes(TestOutput{}).
|
|
||||||
To(noOp)
|
|
||||||
if additionalParams {
|
|
||||||
ret.Param(ws.HeaderParameter("hparam", "a test head parameter").DataType("integer"))
|
|
||||||
ret.Param(ws.FormParameter("fparam", "a test form parameter").DataType("number"))
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfig(fullMethods bool) (*openapi.Config, *restful.Container) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
container := restful.NewContainer()
|
|
||||||
container.ServeMux = mux
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path("/foo")
|
|
||||||
ws.Route(getTestRoute(ws, "get", true, "foo"))
|
|
||||||
if fullMethods {
|
|
||||||
ws.Route(getTestRoute(ws, "post", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "put", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "head", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "patch", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "options", false, "foo")).
|
|
||||||
Route(getTestRoute(ws, "delete", false, "foo"))
|
|
||||||
|
|
||||||
}
|
|
||||||
ws.Path("/bar")
|
|
||||||
ws.Route(getTestRoute(ws, "get", true, "bar"))
|
|
||||||
if fullMethods {
|
|
||||||
ws.Route(getTestRoute(ws, "post", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "put", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "head", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "patch", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "options", false, "bar")).
|
|
||||||
Route(getTestRoute(ws, "delete", false, "bar"))
|
|
||||||
|
|
||||||
}
|
|
||||||
container.Add(ws)
|
|
||||||
return &openapi.Config{
|
|
||||||
ProtocolList: []string{"https"},
|
|
||||||
Info: &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "TestAPI",
|
|
||||||
Description: "Test API",
|
|
||||||
Version: "unversioned",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GetDefinitions: func(_ openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
|
|
||||||
return map[string]openapi.OpenAPIDefinition{
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
|
||||||
// Bazel changes the package name, this is ok for testing, but we need to fix it if it happened
|
|
||||||
// in the main code.
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi/go_default_test.TestInput": *TestInput{}.OpenAPIDefinition(),
|
|
||||||
"k8s.io/apiserver/pkg/server/openapi/go_default_test.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GetDefinitionName: func(name string) (string, spec.Extensions) {
|
|
||||||
friendlyName := name[strings.LastIndex(name, "/")+1:]
|
|
||||||
if strings.HasPrefix(friendlyName, "go_default_test") {
|
|
||||||
friendlyName = "openapi" + friendlyName[len("go_default_test"):]
|
|
||||||
}
|
|
||||||
return friendlyName, spec.Extensions{"x-test2": "test2"}
|
|
||||||
},
|
|
||||||
}, container
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestOperation(method string, opPrefix string) *spec.Operation {
|
|
||||||
return &spec.Operation{
|
|
||||||
OperationProps: spec.OperationProps{
|
|
||||||
Description: fmt.Sprintf("%s test input", method),
|
|
||||||
Consumes: []string{"application/json"},
|
|
||||||
Produces: []string{"application/json"},
|
|
||||||
Schemes: []string{"https"},
|
|
||||||
Parameters: []spec.Parameter{},
|
|
||||||
Responses: getTestResponses(),
|
|
||||||
ID: fmt.Sprintf("%s%sTestInput", method, opPrefix),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem {
|
|
||||||
ret := spec.PathItem{
|
|
||||||
PathItemProps: spec.PathItemProps{
|
|
||||||
Get: getTestOperation("get", opPrefix),
|
|
||||||
Parameters: getTestCommonParameters(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret.Get.Parameters = getAdditionalTestParameters()
|
|
||||||
if allMethods {
|
|
||||||
ret.Put = getTestOperation("put", opPrefix)
|
|
||||||
ret.Put.Parameters = getTestParameters()
|
|
||||||
ret.Post = getTestOperation("post", opPrefix)
|
|
||||||
ret.Post.Parameters = getTestParameters()
|
|
||||||
ret.Head = getTestOperation("head", opPrefix)
|
|
||||||
ret.Head.Parameters = getTestParameters()
|
|
||||||
ret.Patch = getTestOperation("patch", opPrefix)
|
|
||||||
ret.Patch.Parameters = getTestParameters()
|
|
||||||
ret.Delete = getTestOperation("delete", opPrefix)
|
|
||||||
ret.Delete.Parameters = getTestParameters()
|
|
||||||
ret.Options = getTestOperation("options", opPrefix)
|
|
||||||
ret.Options.Parameters = getTestParameters()
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRefSchema(ref string) *spec.Schema {
|
|
||||||
return &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Ref: spec.MustCreateRef(ref),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestResponses() *spec.Responses {
|
|
||||||
ret := spec.Responses{
|
|
||||||
ResponsesProps: spec.ResponsesProps{
|
|
||||||
StatusCodeResponses: map[int]spec.Response{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret.StatusCodeResponses[200] = spec.Response{
|
|
||||||
ResponseProps: spec.ResponseProps{
|
|
||||||
Description: "OK",
|
|
||||||
Schema: getRefSchema("#/definitions/openapi.TestOutput"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestCommonParameters() []spec.Parameter {
|
|
||||||
ret := make([]spec.Parameter, 2)
|
|
||||||
ret[0] = spec.Parameter{
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Description: "path to the resource",
|
|
||||||
Name: "path",
|
|
||||||
In: "path",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret[1] = spec.Parameter{
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "string",
|
|
||||||
},
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Description: "If 'true', then the output is pretty printed.",
|
|
||||||
Name: "pretty",
|
|
||||||
In: "query",
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestParameters() []spec.Parameter {
|
|
||||||
ret := make([]spec.Parameter, 1)
|
|
||||||
ret[0] = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: "body",
|
|
||||||
In: "body",
|
|
||||||
Required: true,
|
|
||||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAdditionalTestParameters() []spec.Parameter {
|
|
||||||
ret := make([]spec.Parameter, 3)
|
|
||||||
ret[0] = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: "body",
|
|
||||||
In: "body",
|
|
||||||
Required: true,
|
|
||||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret[1] = spec.Parameter{
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Name: "fparam",
|
|
||||||
Description: "a test form parameter",
|
|
||||||
In: "formData",
|
|
||||||
},
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "number",
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ret[2] = spec.Parameter{
|
|
||||||
SimpleSchema: spec.SimpleSchema{
|
|
||||||
Type: "integer",
|
|
||||||
},
|
|
||||||
ParamProps: spec.ParamProps{
|
|
||||||
Description: "a test head parameter",
|
|
||||||
Name: "hparam",
|
|
||||||
In: "header",
|
|
||||||
},
|
|
||||||
CommonValidations: spec.CommonValidations{
|
|
||||||
UniqueItems: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestInputDefinition() spec.Schema {
|
|
||||||
return spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Test input",
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"id": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "ID of the input",
|
|
||||||
Type: spec.StringOrArray{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the input",
|
|
||||||
Type: spec.StringOrArray{"string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: spec.StringOrArray{"array"},
|
|
||||||
Items: &spec.SchemaOrArray{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: spec.StringOrArray{"string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-test": "test",
|
|
||||||
"x-test2": "test2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestOutputDefinition() spec.Schema {
|
|
||||||
return spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Test output",
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"count": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Number of outputs",
|
|
||||||
Type: spec.StringOrArray{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Name of the output",
|
|
||||||
Type: spec.StringOrArray{"string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-test2": "test2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildSwaggerSpec(t *testing.T) {
|
|
||||||
o, container, assert := setUp(t, true)
|
|
||||||
expected := &spec.Swagger{
|
|
||||||
SwaggerProps: spec.SwaggerProps{
|
|
||||||
Info: &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "TestAPI",
|
|
||||||
Description: "Test API",
|
|
||||||
Version: "unversioned",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Swagger: "2.0",
|
|
||||||
Paths: &spec.Paths{
|
|
||||||
Paths: map[string]spec.PathItem{
|
|
||||||
"/foo/test/{path}": getTestPathItem(true, "foo"),
|
|
||||||
"/bar/test/{path}": getTestPathItem(true, "bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Definitions: spec.Definitions{
|
|
||||||
"openapi.TestInput": getTestInputDefinition(),
|
|
||||||
"openapi.TestOutput": getTestOutputDefinition(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := o.init(container.RegisteredWebServices())
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expected_json, err := json.Marshal(expected)
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
actual_json, err := json.Marshal(o.swagger)
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(string(expected_json), string(actual_json))
|
|
||||||
}
|
|
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type parameters []spec.Parameter
|
|
||||||
|
|
||||||
func (s parameters) Len() int { return len(s) }
|
|
||||||
func (s parameters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
|
|
||||||
// byNameIn used in sorting parameters by Name and In fields.
|
|
||||||
type byNameIn struct {
|
|
||||||
parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s byNameIn) Less(i, j int) bool {
|
|
||||||
return s.parameters[i].Name < s.parameters[j].Name || (s.parameters[i].Name == s.parameters[j].Name && s.parameters[i].In < s.parameters[j].In)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortParameters sorts parameters by Name and In fields.
|
|
||||||
func sortParameters(p []spec.Parameter) {
|
|
||||||
sort.Sort(byNameIn{p})
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route {
|
|
||||||
pathToRoutes := make(map[string][]restful.Route)
|
|
||||||
for _, r := range routes {
|
|
||||||
pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r)
|
|
||||||
}
|
|
||||||
return pathToRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapKeyFromParam(param *restful.Parameter) interface{} {
|
|
||||||
return struct {
|
|
||||||
Name string
|
|
||||||
Kind int
|
|
||||||
}{
|
|
||||||
Name: param.Data().Name,
|
|
||||||
Kind: param.Data().Kind,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -18,22 +18,21 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/apiserver/pkg/server/mux"
|
|
||||||
apiserveropenapi "k8s.io/apiserver/pkg/server/openapi"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/server/mux"
|
||||||
|
"k8s.io/kube-openapi/pkg/common"
|
||||||
|
"k8s.io/kube-openapi/pkg/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OpenAPI installs spec endpoints for each web service.
|
// OpenAPI installs spec endpoints for each web service.
|
||||||
type OpenAPI struct {
|
type OpenAPI struct {
|
||||||
Config *openapi.Config
|
Config *common.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install adds the SwaggerUI webservice to the given mux.
|
// Install adds the SwaggerUI webservice to the given mux.
|
||||||
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
|
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
|
||||||
err := apiserveropenapi.RegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
|
_, err := handler.BuildAndRegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Failed to register open api spec for root: %v", err)
|
glog.Fatalf("Failed to register open api spec for root: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["trie.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package trie
|
|
||||||
|
|
||||||
// A simple trie implementation with Add an HasPrefix methods only.
|
|
||||||
type Trie struct {
|
|
||||||
children map[byte]*Trie
|
|
||||||
wordTail bool
|
|
||||||
word string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a Trie and add all strings in the provided list to it.
|
|
||||||
func New(list []string) Trie {
|
|
||||||
ret := Trie{
|
|
||||||
children: make(map[byte]*Trie),
|
|
||||||
wordTail: false,
|
|
||||||
}
|
|
||||||
for _, v := range list {
|
|
||||||
ret.Add(v)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a word to this trie
|
|
||||||
func (t *Trie) Add(v string) {
|
|
||||||
root := t
|
|
||||||
for _, b := range []byte(v) {
|
|
||||||
child, exists := root.children[b]
|
|
||||||
if !exists {
|
|
||||||
child = &Trie{
|
|
||||||
children: make(map[byte]*Trie),
|
|
||||||
wordTail: false,
|
|
||||||
}
|
|
||||||
root.children[b] = child
|
|
||||||
}
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
root.wordTail = true
|
|
||||||
root.word = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPrefix returns true of v has any of the prefixes stored in this trie.
|
|
||||||
func (t *Trie) HasPrefix(v string) bool {
|
|
||||||
_, has := t.GetPrefix(v)
|
|
||||||
return has
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrefix is like HasPrefix but return the prefix in case of match or empty string otherwise.
|
|
||||||
func (t *Trie) GetPrefix(v string) (string, bool) {
|
|
||||||
root := t
|
|
||||||
if root.wordTail {
|
|
||||||
return root.word, true
|
|
||||||
}
|
|
||||||
for _, b := range []byte(v) {
|
|
||||||
child, exists := root.children[b]
|
|
||||||
if !exists {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
if child.wordTail {
|
|
||||||
return child.word, true
|
|
||||||
}
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
8
staging/src/k8s.io/kube-gen/Godeps/Godeps.json
generated
8
staging/src/k8s.io/kube-gen/Godeps/Godeps.json
generated
@@ -206,18 +206,10 @@
|
|||||||
"ImportPath": "github.com/mailru/easyjson/jwriter",
|
"ImportPath": "github.com/mailru/easyjson/jwriter",
|
||||||
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
|
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
|
||||||
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/pflag",
|
"ImportPath": "github.com/spf13/pflag",
|
||||||
"Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
|
"Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/stretchr/testify/assert",
|
|
||||||
"Rev": "f6abca593680b2315d2075e0f5e2a9751e3f431a"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/ugorji/go/codec",
|
"ImportPath": "github.com/ugorji/go/codec",
|
||||||
"Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"
|
"Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_binary",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_binary(
|
|
||||||
name = "openapi-gen",
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["main.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/args:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-gen/cmd/openapi-gen/generators:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
@@ -1,37 +0,0 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["openapi.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/openapi:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/args:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/generator:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/namer:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/types:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["openapi_test.go"],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/generator:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/namer:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/parser:go_default_library",
|
|
||||||
"//vendor/k8s.io/gengo/types:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
@@ -1,612 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package generators
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/openapi"
|
|
||||||
"k8s.io/gengo/args"
|
|
||||||
"k8s.io/gengo/generator"
|
|
||||||
"k8s.io/gengo/namer"
|
|
||||||
"k8s.io/gengo/types"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is the comment tag that carries parameters for open API generation.
|
|
||||||
const tagName = "k8s:openapi-gen"
|
|
||||||
const tagOptional = "optional"
|
|
||||||
|
|
||||||
// Known values for the tag.
|
|
||||||
const (
|
|
||||||
tagValueTrue = "true"
|
|
||||||
tagValueFalse = "false"
|
|
||||||
tagExtensionPrefix = "x-kubernetes-"
|
|
||||||
tagPatchStrategy = "patchStrategy"
|
|
||||||
tagPatchMergeKey = "patchMergeKey"
|
|
||||||
patchStrategyExtensionName = "patch-strategy"
|
|
||||||
patchMergeKeyExtensionName = "patch-merge-key"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getOpenAPITagValue(comments []string) []string {
|
|
||||||
return types.ExtractCommentTags("+", comments)[tagName]
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSingleTagsValue(comments []string, tag string) (string, error) {
|
|
||||||
tags, ok := types.ExtractCommentTags("+", comments)[tag]
|
|
||||||
if !ok || len(tags) == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if len(tags) > 1 {
|
|
||||||
return "", fmt.Errorf("Multiple values are not allowed for tag %s", tag)
|
|
||||||
}
|
|
||||||
return tags[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasOpenAPITagValue(comments []string, value string) bool {
|
|
||||||
tagValues := getOpenAPITagValue(comments)
|
|
||||||
for _, val := range tagValues {
|
|
||||||
if val == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasOptionalTag returns true if the member has +optional in its comments or
|
|
||||||
// omitempty in its json tags.
|
|
||||||
func hasOptionalTag(m *types.Member) bool {
|
|
||||||
hasOptionalCommentTag := types.ExtractCommentTags(
|
|
||||||
"+", m.CommentLines)[tagOptional] != nil
|
|
||||||
hasOptionalJsonTag := strings.Contains(
|
|
||||||
reflect.StructTag(m.Tags).Get("json"), "omitempty")
|
|
||||||
return hasOptionalCommentTag || hasOptionalJsonTag
|
|
||||||
}
|
|
||||||
|
|
||||||
type identityNamer struct{}
|
|
||||||
|
|
||||||
func (_ identityNamer) Name(t *types.Type) string {
|
|
||||||
return t.Name.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ namer.Namer = identityNamer{}
|
|
||||||
|
|
||||||
// NameSystems returns the name system used by the generators in this package.
|
|
||||||
func NameSystems() namer.NameSystems {
|
|
||||||
return namer.NameSystems{
|
|
||||||
"raw": namer.NewRawNamer("", nil),
|
|
||||||
"sorting_namer": identityNamer{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultNameSystem returns the default name system for ordering the types to be
|
|
||||||
// processed by the generators in this package.
|
|
||||||
func DefaultNameSystem() string {
|
|
||||||
return "sorting_namer"
|
|
||||||
}
|
|
||||||
|
|
||||||
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
|
|
||||||
boilerplate, err := arguments.LoadGoBoilerplate()
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("Failed loading boilerplate: %v", err)
|
|
||||||
}
|
|
||||||
header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...)
|
|
||||||
header = append(header, []byte(
|
|
||||||
`
|
|
||||||
// This file was autogenerated by openapi-gen. Do not edit it manually!
|
|
||||||
|
|
||||||
`)...)
|
|
||||||
|
|
||||||
if err := context.AddDir(arguments.OutputPackagePath); err != nil {
|
|
||||||
glog.Fatalf("Failed to load output package: %v", err)
|
|
||||||
}
|
|
||||||
pkg := context.Universe[arguments.OutputPackagePath]
|
|
||||||
if pkg == nil {
|
|
||||||
glog.Fatalf("Got nil output package: %v", err)
|
|
||||||
}
|
|
||||||
return generator.Packages{
|
|
||||||
&generator.DefaultPackage{
|
|
||||||
PackageName: strings.Split(filepath.Base(pkg.Path), ".")[0],
|
|
||||||
PackagePath: pkg.Path,
|
|
||||||
HeaderText: header,
|
|
||||||
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
|
|
||||||
return []generator.Generator{NewOpenAPIGen(arguments.OutputFileBaseName, pkg, context)}
|
|
||||||
},
|
|
||||||
FilterFunc: func(c *generator.Context, t *types.Type) bool {
|
|
||||||
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
|
|
||||||
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
pkg := context.Universe.Package(t.Name.Package)
|
|
||||||
if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
|
|
||||||
return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
|
|
||||||
}
|
|
||||||
if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
specPackagePath = "github.com/go-openapi/spec"
|
|
||||||
openAPICommonPackagePath = "k8s.io/apimachinery/pkg/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// openApiGen produces a file with auto-generated OpenAPI functions.
|
|
||||||
type openAPIGen struct {
|
|
||||||
generator.DefaultGen
|
|
||||||
// TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions.
|
|
||||||
targetPackage *types.Package
|
|
||||||
imports namer.ImportTracker
|
|
||||||
context *generator.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenAPIGen(sanitizedName string, targetPackage *types.Package, context *generator.Context) generator.Generator {
|
|
||||||
return &openAPIGen{
|
|
||||||
DefaultGen: generator.DefaultGen{
|
|
||||||
OptionalName: sanitizedName,
|
|
||||||
},
|
|
||||||
imports: generator.NewImportTracker(),
|
|
||||||
targetPackage: targetPackage,
|
|
||||||
context: context,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
|
|
||||||
// Have the raw namer for this file track what it imports.
|
|
||||||
return namer.NameSystems{
|
|
||||||
"raw": namer.NewRawNamer(g.targetPackage.Path, g.imports),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool {
|
|
||||||
// There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen
|
|
||||||
if strings.HasPrefix(t.Name.Name, "codecSelfer") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) isOtherPackage(pkg string) bool {
|
|
||||||
if pkg == g.targetPackage.Path {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(pkg, "\""+g.targetPackage.Path+"\"") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) Imports(c *generator.Context) []string {
|
|
||||||
importLines := []string{}
|
|
||||||
for _, singleImport := range g.imports.ImportLines() {
|
|
||||||
importLines = append(importLines, singleImport)
|
|
||||||
}
|
|
||||||
return importLines
|
|
||||||
}
|
|
||||||
|
|
||||||
func argsFromType(t *types.Type) generator.Args {
|
|
||||||
return generator.Args{
|
|
||||||
"type": t,
|
|
||||||
"ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
|
|
||||||
"OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
|
|
||||||
"SpecSchemaType": types.Ref(specPackagePath, "Schema"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
|
|
||||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
|
||||||
sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
|
|
||||||
sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
|
|
||||||
return sw.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error {
|
|
||||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
|
||||||
sw.Do("}\n", nil)
|
|
||||||
sw.Do("}\n", nil)
|
|
||||||
return sw.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
|
|
||||||
glog.V(5).Infof("generating for type %v", t)
|
|
||||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
|
||||||
err := newOpenAPITypeWriter(sw).generate(t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return sw.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJsonTags(m *types.Member) []string {
|
|
||||||
jsonTag := reflect.StructTag(m.Tags).Get("json")
|
|
||||||
if jsonTag == "" {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return strings.Split(jsonTag, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPatchTags(m *types.Member) (string, string) {
|
|
||||||
return reflect.StructTag(m.Tags).Get(tagPatchMergeKey), reflect.StructTag(m.Tags).Get(tagPatchStrategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReferableName(m *types.Member) string {
|
|
||||||
jsonTags := getJsonTags(m)
|
|
||||||
if len(jsonTags) > 0 {
|
|
||||||
if jsonTags[0] == "-" {
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return jsonTags[0]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return m.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldInlineMembers(m *types.Member) bool {
|
|
||||||
jsonTags := getJsonTags(m)
|
|
||||||
return len(jsonTags) > 1 && jsonTags[1] == "inline"
|
|
||||||
}
|
|
||||||
|
|
||||||
type openAPITypeWriter struct {
|
|
||||||
*generator.SnippetWriter
|
|
||||||
refTypes map[string]*types.Type
|
|
||||||
GetDefinitionInterface *types.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter {
|
|
||||||
return openAPITypeWriter{
|
|
||||||
SnippetWriter: sw,
|
|
||||||
refTypes: map[string]*types.Type{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasOpenAPIDefinitionMethod(t *types.Type) bool {
|
|
||||||
for mn, mt := range t.Methods {
|
|
||||||
if mn != "OpenAPIDefinition" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
r := mt.Signature.Results[0]
|
|
||||||
if r.Name.Name != "OpenAPIDefinition" || r.Name.Package != openAPICommonPackagePath {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name.
|
|
||||||
func typeShortName(t *types.Type) string {
|
|
||||||
return filepath.Base(t.Name.Package) + "." + t.Name.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
|
|
||||||
var err error
|
|
||||||
for _, m := range t.Members {
|
|
||||||
if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if shouldInlineMembers(&m) {
|
|
||||||
required, err = g.generateMembers(m.Type, required)
|
|
||||||
if err != nil {
|
|
||||||
return required, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := getReferableName(&m)
|
|
||||||
if name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !hasOptionalTag(&m) {
|
|
||||||
required = append(required, name)
|
|
||||||
}
|
|
||||||
if err = g.generateProperty(&m, t); err != nil {
|
|
||||||
return required, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return required, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generate(t *types.Type) error {
|
|
||||||
// Only generate for struct type and ignore the rest
|
|
||||||
switch t.Kind {
|
|
||||||
case types.Struct:
|
|
||||||
args := argsFromType(t)
|
|
||||||
g.Do("\"$.$\": ", t.Name)
|
|
||||||
if hasOpenAPIDefinitionMethod(t) {
|
|
||||||
g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
g.Do("{\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
|
||||||
g.generateDescription(t.CommentLines)
|
|
||||||
g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
|
|
||||||
required, err := g.generateMembers(t, []string{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
g.Do("},\n", nil)
|
|
||||||
if len(required) > 0 {
|
|
||||||
g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
|
|
||||||
}
|
|
||||||
g.Do("},\n", nil)
|
|
||||||
if err := g.generateExtensions(t.CommentLines); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
g.Do("},\n", nil)
|
|
||||||
g.Do("Dependencies: []string{\n", args)
|
|
||||||
// Map order is undefined, sort them or we may get a different file generated each time.
|
|
||||||
keys := []string{}
|
|
||||||
for k := range g.refTypes {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
v := g.refTypes[k]
|
|
||||||
if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" {
|
|
||||||
// This is a known type, we do not need a reference to it
|
|
||||||
// Will eliminate special case of time.Time
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
g.Do("\"$.$\",", k)
|
|
||||||
}
|
|
||||||
g.Do("},\n},\n", nil)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateExtensions(CommentLines []string) error {
|
|
||||||
tagValues := getOpenAPITagValue(CommentLines)
|
|
||||||
type NameValue struct {
|
|
||||||
Name, Value string
|
|
||||||
}
|
|
||||||
extensions := []NameValue{}
|
|
||||||
for _, val := range tagValues {
|
|
||||||
if strings.HasPrefix(val, tagExtensionPrefix) {
|
|
||||||
parts := strings.SplitN(val, ":", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("Invalid extension value: %v", val)
|
|
||||||
}
|
|
||||||
extensions = append(extensions, NameValue{parts[0], parts[1]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
patchMergeKeyTag, err := getSingleTagsValue(CommentLines, tagPatchMergeKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(patchMergeKeyTag) > 0 {
|
|
||||||
extensions = append(extensions, NameValue{tagExtensionPrefix + patchMergeKeyExtensionName, patchMergeKeyTag})
|
|
||||||
}
|
|
||||||
patchStrategyTag, err := getSingleTagsValue(CommentLines, tagPatchStrategy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(patchStrategyTag) > 0 {
|
|
||||||
extensions = append(extensions, NameValue{tagExtensionPrefix + patchStrategyExtensionName, patchStrategyTag})
|
|
||||||
}
|
|
||||||
if len(extensions) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
|
|
||||||
for _, extension := range extensions {
|
|
||||||
g.Do("\"$.$\": ", extension.Name)
|
|
||||||
g.Do("\"$.$\",\n", extension.Value)
|
|
||||||
}
|
|
||||||
g.Do("},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(#44005): Move this validation outside of this generator (probably to policy verifier)
|
|
||||||
func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
|
|
||||||
patchMergeKeyStructTag, patchStrategyStructTag := getPatchTags(m)
|
|
||||||
patchMergeKeyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchMergeKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
patchStrategyCommentTag, err := getSingleTagsValue(m.CommentLines, tagPatchStrategy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if patchMergeKeyStructTag != patchMergeKeyCommentTag {
|
|
||||||
return fmt.Errorf("patchMergeKey in comment and struct tags should match for member (%s) of (%s)",
|
|
||||||
m.Name, parent.Name.String())
|
|
||||||
}
|
|
||||||
if patchStrategyStructTag != patchStrategyCommentTag {
|
|
||||||
return fmt.Errorf("patchStrategy in comment and struct tags should match for member (%s) of (%s)",
|
|
||||||
m.Name, parent.Name.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateDescription(CommentLines []string) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
delPrevChar := func() {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range CommentLines {
|
|
||||||
// Ignore all lines after ---
|
|
||||||
if line == "---" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
line = strings.TrimRight(line, " ")
|
|
||||||
leading := strings.TrimLeft(line, " ")
|
|
||||||
switch {
|
|
||||||
case len(line) == 0: // Keep paragraphs
|
|
||||||
delPrevChar()
|
|
||||||
buffer.WriteString("\n\n")
|
|
||||||
case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs
|
|
||||||
case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl
|
|
||||||
default:
|
|
||||||
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
|
||||||
delPrevChar()
|
|
||||||
line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..."
|
|
||||||
} else {
|
|
||||||
line += " "
|
|
||||||
}
|
|
||||||
buffer.WriteString(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
postDoc := strings.TrimRight(buffer.String(), "\n")
|
|
||||||
postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to "
|
|
||||||
postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape "
|
|
||||||
postDoc = strings.Replace(postDoc, "\n", "\\n", -1)
|
|
||||||
postDoc = strings.Replace(postDoc, "\t", "\\t", -1)
|
|
||||||
postDoc = strings.Trim(postDoc, " ")
|
|
||||||
if postDoc != "" {
|
|
||||||
g.Do("Description: \"$.$\",\n", postDoc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
|
|
||||||
name := getReferableName(m)
|
|
||||||
if name == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := g.validatePatchTags(m, parent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
g.Do("\"$.$\": {\n", name)
|
|
||||||
if err := g.generateExtensions(m.CommentLines); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
g.Do("SchemaProps: spec.SchemaProps{\n", nil)
|
|
||||||
g.generateDescription(m.CommentLines)
|
|
||||||
jsonTags := getJsonTags(m)
|
|
||||||
if len(jsonTags) > 1 && jsonTags[1] == "string" {
|
|
||||||
g.generateSimpleProperty("string", "")
|
|
||||||
g.Do("},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := resolveAliasAndPtrType(m.Type)
|
|
||||||
// If we can get a openAPI type and format for this type, we consider it to be simple property
|
|
||||||
typeString, format := openapi.GetOpenAPITypeFormat(t.String())
|
|
||||||
if typeString != "" {
|
|
||||||
g.generateSimpleProperty(typeString, format)
|
|
||||||
g.Do("},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch t.Kind {
|
|
||||||
case types.Builtin:
|
|
||||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
|
|
||||||
case types.Map:
|
|
||||||
if err := g.generateMapProperty(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case types.Slice, types.Array:
|
|
||||||
if err := g.generateSliceProperty(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case types.Struct, types.Interface:
|
|
||||||
g.generateReferenceProperty(t)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cannot generate spec for type %v", t)
|
|
||||||
}
|
|
||||||
g.Do("},\n},\n", nil)
|
|
||||||
return g.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
|
|
||||||
g.Do("Type: []string{\"$.$\"},\n", typeString)
|
|
||||||
g.Do("Format: \"$.$\",\n", format)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
|
|
||||||
g.refTypes[t.Name.String()] = t
|
|
||||||
g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveAliasAndPtrType(t *types.Type) *types.Type {
|
|
||||||
var prev *types.Type
|
|
||||||
for prev != t {
|
|
||||||
prev = t
|
|
||||||
if t.Kind == types.Alias {
|
|
||||||
t = t.Underlying
|
|
||||||
}
|
|
||||||
if t.Kind == types.Pointer {
|
|
||||||
t = t.Elem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
|
|
||||||
keyType := resolveAliasAndPtrType(t.Key)
|
|
||||||
elemType := resolveAliasAndPtrType(t.Elem)
|
|
||||||
|
|
||||||
// According to OpenAPI examples, only map from string is supported
|
|
||||||
if keyType.Name.Name != "string" {
|
|
||||||
return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
|
|
||||||
}
|
|
||||||
g.Do("Type: []string{\"object\"},\n", nil)
|
|
||||||
g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
|
||||||
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
|
|
||||||
if typeString != "" {
|
|
||||||
g.generateSimpleProperty(typeString, format)
|
|
||||||
g.Do("},\n},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch elemType.Kind {
|
|
||||||
case types.Builtin:
|
|
||||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function.", elemType)
|
|
||||||
case types.Struct:
|
|
||||||
g.generateReferenceProperty(elemType)
|
|
||||||
case types.Slice, types.Array:
|
|
||||||
g.generateSliceProperty(elemType)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
|
|
||||||
}
|
|
||||||
g.Do("},\n},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
|
|
||||||
elemType := resolveAliasAndPtrType(t.Elem)
|
|
||||||
g.Do("Type: []string{\"array\"},\n", nil)
|
|
||||||
g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
|
|
||||||
typeString, format := openapi.GetOpenAPITypeFormat(elemType.String())
|
|
||||||
if typeString != "" {
|
|
||||||
g.generateSimpleProperty(typeString, format)
|
|
||||||
g.Do("},\n},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch elemType.Kind {
|
|
||||||
case types.Builtin:
|
|
||||||
return fmt.Errorf("please add type %v to getOpenAPITypeFormat function.", elemType)
|
|
||||||
case types.Struct:
|
|
||||||
g.generateReferenceProperty(elemType)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
|
|
||||||
}
|
|
||||||
g.Do("},\n},\n},\n", nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,375 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package generators
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"k8s.io/gengo/generator"
|
|
||||||
"k8s.io/gengo/namer"
|
|
||||||
"k8s.io/gengo/parser"
|
|
||||||
"k8s.io/gengo/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
|
|
||||||
b := parser.New()
|
|
||||||
for name, src := range files {
|
|
||||||
if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u, err := b.FindTypes()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
orderer := namer.Orderer{Namer: testNamer}
|
|
||||||
o := orderer.OrderUniverse(u)
|
|
||||||
return b, u, o
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOpenAPITypeWritter(t *testing.T, code string) (error, *assert.Assertions, *bytes.Buffer) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
var testFiles = map[string]string{
|
|
||||||
"base/foo/bar.go": code,
|
|
||||||
}
|
|
||||||
rawNamer := namer.NewRawNamer("o", nil)
|
|
||||||
namers := namer.NameSystems{
|
|
||||||
"raw": namer.NewRawNamer("", nil),
|
|
||||||
}
|
|
||||||
builder, universe, _ := construct(t, testFiles, rawNamer)
|
|
||||||
context, err := generator.NewContext(builder, namers, "raw")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
buffer := &bytes.Buffer{}
|
|
||||||
sw := generator.NewSnippetWriter(buffer, context, "$", "$")
|
|
||||||
blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
|
|
||||||
return newOpenAPITypeWriter(sw).generate(blahT), assert, buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimple(t *testing.T) {
|
|
||||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
|
||||||
package foo
|
|
||||||
|
|
||||||
// Blah is a test.
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
|
|
||||||
type Blah struct {
|
|
||||||
// A simple string
|
|
||||||
String string
|
|
||||||
// A simple int
|
|
||||||
Int int `+"`"+`json:",omitempty"`+"`"+`
|
|
||||||
// An int considered string simple int
|
|
||||||
IntString int `+"`"+`json:",string"`+"`"+`
|
|
||||||
// A simple int64
|
|
||||||
Int64 int64
|
|
||||||
// A simple int32
|
|
||||||
Int32 int32
|
|
||||||
// A simple int16
|
|
||||||
Int16 int16
|
|
||||||
// A simple int8
|
|
||||||
Int8 int8
|
|
||||||
// A simple int
|
|
||||||
Uint uint
|
|
||||||
// A simple int64
|
|
||||||
Uint64 uint64
|
|
||||||
// A simple int32
|
|
||||||
Uint32 uint32
|
|
||||||
// A simple int16
|
|
||||||
Uint16 uint16
|
|
||||||
// A simple int8
|
|
||||||
Uint8 uint8
|
|
||||||
// A simple byte
|
|
||||||
Byte byte
|
|
||||||
// A simple boolean
|
|
||||||
Bool bool
|
|
||||||
// A simple float64
|
|
||||||
Float64 float64
|
|
||||||
// A simple float32
|
|
||||||
Float32 float32
|
|
||||||
// a base64 encoded characters
|
|
||||||
ByteArray []byte
|
|
||||||
// a member with an extension
|
|
||||||
// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
|
|
||||||
WithExtension string
|
|
||||||
// a member with struct tag as extension
|
|
||||||
// +patchStrategy=ps
|
|
||||||
// +patchMergeKey=pmk
|
|
||||||
WithStructTagExtension string `+"`"+`patchStrategy:"ps" patchMergeKey:"pmk"`+"`"+`
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assert.Equal(`"base/foo.Blah": {
|
|
||||||
Schema: spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Blah is a test.",
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"String": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple string",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Int64": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int64",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int64",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Int32": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int32",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Int16": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int16",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Int8": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int8",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "byte",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Uint": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Uint64": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int64",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int64",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Uint32": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int32",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int64",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Uint16": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int16",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int32",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Uint8": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple int8",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "byte",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Byte": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple byte",
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "byte",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Bool": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple boolean",
|
|
||||||
Type: []string{"boolean"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Float64": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple float64",
|
|
||||||
Type: []string{"number"},
|
|
||||||
Format: "double",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Float32": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A simple float32",
|
|
||||||
Type: []string{"number"},
|
|
||||||
Format: "float",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ByteArray": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "a base64 encoded characters",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "byte",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"WithExtension": {
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-member-tag": "member_test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "a member with an extension",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"WithStructTagExtension": {
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-patch-merge-key": "pmk",
|
|
||||||
"x-kubernetes-patch-strategy": "ps",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "a member with struct tag as extension",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension"},
|
|
||||||
},
|
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-type-tag": "type_test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Dependencies: []string{
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`, buffer.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFailingSample1(t *testing.T) {
|
|
||||||
err, assert, _ := testOpenAPITypeWritter(t, `
|
|
||||||
package foo
|
|
||||||
|
|
||||||
// Map sample tests openAPIGen.generateMapProperty method.
|
|
||||||
type Blah struct {
|
|
||||||
// A sample String to String map
|
|
||||||
StringToArray map[string]map[string]string
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
if assert.Error(err, "An error was expected") {
|
|
||||||
assert.Equal(err, fmt.Errorf("map Element kind Map is not supported in map[string]map[string]string"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFailingSample2(t *testing.T) {
|
|
||||||
err, assert, _ := testOpenAPITypeWritter(t, `
|
|
||||||
package foo
|
|
||||||
|
|
||||||
// Map sample tests openAPIGen.generateMapProperty method.
|
|
||||||
type Blah struct {
|
|
||||||
// A sample String to String map
|
|
||||||
StringToArray map[int]string
|
|
||||||
} `)
|
|
||||||
if assert.Error(err, "An error was expected") {
|
|
||||||
assert.Equal(err, fmt.Errorf("map with non-string keys are not supported by OpenAPI in map[int]string"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPointer(t *testing.T) {
|
|
||||||
err, assert, buffer := testOpenAPITypeWritter(t, `
|
|
||||||
package foo
|
|
||||||
|
|
||||||
// PointerSample demonstrate pointer's properties
|
|
||||||
type Blah struct {
|
|
||||||
// A string pointer
|
|
||||||
StringPointer *string
|
|
||||||
// A struct pointer
|
|
||||||
StructPointer *Blah
|
|
||||||
// A slice pointer
|
|
||||||
SlicePointer *[]string
|
|
||||||
// A map pointer
|
|
||||||
MapPointer *map[string]string
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assert.Equal(`"base/foo.Blah": {
|
|
||||||
Schema: spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "PointerSample demonstrate pointer's properties",
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"StringPointer": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A string pointer",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"StructPointer": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A struct pointer",
|
|
||||||
Ref: ref("base/foo.Blah"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"SlicePointer": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A slice pointer",
|
|
||||||
Type: []string{"array"},
|
|
||||||
Items: &spec.SchemaOrArray{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"MapPointer": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "A map pointer",
|
|
||||||
Type: []string{"object"},
|
|
||||||
AdditionalProperties: &spec.SchemaOrBool{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Dependencies: []string{
|
|
||||||
"base/foo.Blah",},
|
|
||||||
},
|
|
||||||
`, buffer.String())
|
|
||||||
}
|
|
@@ -23,7 +23,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"k8s.io/gengo/args"
|
"k8s.io/gengo/args"
|
||||||
"k8s.io/kube-gen/cmd/openapi-gen/generators"
|
"k8s.io/kube-openapi/pkg/generators"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user