diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index ccae17c608d..ef45d51e77a 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -38,7 +38,6 @@ import ( "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/openapi" "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilnet "k8s.io/apimachinery/pkg/util/net" @@ -53,6 +52,7 @@ import ( serverstorage "k8s.io/apiserver/pkg/server/storage" aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" //aggregatorinformers "k8s.io/kube-aggregator/pkg/client/informers/internalversion" + openapi "k8s.io/kube-openapi/pkg/common" clientgoinformers "k8s.io/client-go/informers" clientgoclientset "k8s.io/client-go/kubernetes" diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index b7d503a9fb7..fd169f7471b 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -32,7 +32,6 @@ import ( apiv1 "k8s.io/api/core/v1" extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" - apimachineryopenapi "k8s.io/apimachinery/pkg/openapi" "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" @@ -40,6 +39,7 @@ import ( "k8s.io/apiserver/pkg/server/filters" serverstorage "k8s.io/apiserver/pkg/server/storage" clientset "k8s.io/client-go/kubernetes" + openapicommon "k8s.io/kube-openapi/pkg/common" federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" "k8s.io/kubernetes/federation/cmd/federation-apiserver/app/options" "k8s.io/kubernetes/pkg/api" @@ -452,7 +452,7 @@ func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swag } s.Definitions[k] = spec.Schema{ 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), }, } diff --git a/hack/.golint_failures b/hack/.golint_failures index 6b3980597cb..674d2177194 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -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/unstructured 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/serializer 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/healthz 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/routes/data/swagger 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/proxy 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/wsstream 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/informer-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/v1alpha1 staging/src/k8s.io/metrics/pkg/apis/metrics diff --git a/hack/verify-staging-imports.sh b/hack/verify-staging-imports.sh index 3b774026440..feb21fd0214 100755 --- a/hack/verify-staging-imports.sh +++ b/hack/verify-staging-imports.sh @@ -65,15 +65,16 @@ function print_forbidden_imports () { } 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 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 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 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 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 exit ${RC} fi diff --git a/pkg/api/unversioned/time.go b/pkg/api/unversioned/time.go index 8799817a8fd..32f9edb7453 100644 --- a/pkg/api/unversioned/time.go +++ b/pkg/api/unversioned/time.go @@ -20,7 +20,7 @@ import ( "encoding/json" "time" - "k8s.io/apimachinery/pkg/openapi" + openapi "k8s.io/kube-openapi/pkg/common" "github.com/go-openapi/spec" "github.com/google/gofuzz" diff --git a/pkg/apis/meta/v1/time.go b/pkg/apis/meta/v1/time.go index 49d2ac18ec2..c73d7ca6335 100644 --- a/pkg/apis/meta/v1/time.go +++ b/pkg/apis/meta/v1/time.go @@ -20,7 +20,7 @@ import ( "encoding/json" "time" - "k8s.io/apimachinery/pkg/openapi" + openapi "k8s.io/kube-openapi/pkg/common" "github.com/go-openapi/spec" "github.com/google/gofuzz" diff --git a/pkg/generated/openapi/def.bzl b/pkg/generated/openapi/def.bzl index 5844809d44f..3b1f1164bee 100644 --- a/pkg/generated/openapi/def.bzl +++ b/pkg/generated/openapi/def.bzl @@ -4,7 +4,7 @@ load("@io_kubernetes_build//defs:go.bzl", "go_genrule") def openapi_library(name, tags, srcs, openapi_targets=[], vendor_targets=[]): deps = [ "//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] go_library( name=name, diff --git a/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go index 3a956088212..1839d78a34b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go @@ -29,7 +29,7 @@ import ( "github.com/go-openapi/spec" 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. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go index d55f446b0d3..cdfbcb54fda 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go @@ -20,7 +20,7 @@ import ( "encoding/json" "time" - "k8s.io/apimachinery/pkg/openapi" + openapi "k8s.io/kube-openapi/pkg/common" "github.com/go-openapi/spec" "github.com/google/gofuzz" diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go index 49d2ac18ec2..c73d7ca6335 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/time.go @@ -20,7 +20,7 @@ import ( "encoding/json" "time" - "k8s.io/apimachinery/pkg/openapi" + openapi "k8s.io/kube-openapi/pkg/common" "github.com/go-openapi/spec" "github.com/google/gofuzz" diff --git a/staging/src/k8s.io/apimachinery/pkg/openapi/common.go b/staging/src/k8s.io/apimachinery/pkg/openapi/common.go deleted file mode 100644 index bfab64a1c29..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/openapi/common.go +++ /dev/null @@ -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 -} diff --git a/staging/src/k8s.io/apimachinery/pkg/openapi/doc.go b/staging/src/k8s.io/apimachinery/pkg/openapi/doc.go deleted file mode 100644 index 5ed572cc136..00000000000 --- a/staging/src/k8s.io/apimachinery/pkg/openapi/doc.go +++ /dev/null @@ -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 diff --git a/staging/src/k8s.io/apimachinery/pkg/util/intstr/intstr.go b/staging/src/k8s.io/apimachinery/pkg/util/intstr/intstr.go index 02586b34810..04a77bb6b4b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/intstr/intstr.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/intstr/intstr.go @@ -24,7 +24,7 @@ import ( "strconv" "strings" - "k8s.io/apimachinery/pkg/openapi" + openapi "k8s.io/kube-openapi/pkg/common" "github.com/go-openapi/spec" "github.com/golang/glog" diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go b/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go index 352ebcb2ffd..7b8f3589e02 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go @@ -30,10 +30,10 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "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 ( extensionGVK = "x-kubernetes-group-version-kind" diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index a0bf338f5d5..e97313d9253 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -33,7 +33,6 @@ import ( "github.com/go-openapi/spec" "github.com/pborman/uuid" - openapicommon "k8s.io/apimachinery/pkg/openapi" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/sets" @@ -61,6 +60,7 @@ import ( "k8s.io/client-go/informers" restclient "k8s.io/client-go/rest" certutil "k8s.io/client-go/util/cert" + openapicommon "k8s.io/kube-openapi/pkg/common" _ "k8s.io/apiserver/pkg/apis/apiserver/install" ) diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index c9df180590f..266f53ec2b9 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -30,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/apimachinery" "k8s.io/apimachinery/pkg/apimachinery/registered" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - openapicommon "k8s.io/apimachinery/pkg/openapi" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -44,6 +43,7 @@ import ( "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/routes" restclient "k8s.io/client-go/rest" + openapicommon "k8s.io/kube-openapi/pkg/common" ) // Info about an API group. diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go index b8aed64c1f8..a69e0240b15 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go @@ -34,7 +34,6 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apimachinery" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/openapi" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -51,6 +50,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" + openapi "k8s.io/kube-openapi/pkg/common" ) const ( diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/OWNERS b/staging/src/k8s.io/apiserver/pkg/server/openapi/OWNERS deleted file mode 100755 index 7c8ad60c562..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/OWNERS +++ /dev/null @@ -1,7 +0,0 @@ -reviewers: -- yujuhong -- gmarek -- mbohlool -- philips -approvers: -- mbohlool diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/doc.go b/staging/src/k8s.io/apiserver/pkg/server/openapi/doc.go deleted file mode 100644 index 091580bc474..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/doc.go +++ /dev/null @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi.go b/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi.go deleted file mode 100644 index f0fcc1311af..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi.go +++ /dev/null @@ -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 -} diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_test.go b/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_test.go deleted file mode 100644 index 64d51955390..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_test.go +++ /dev/null @@ -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)) -} diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/util.go b/staging/src/k8s.io/apiserver/pkg/server/openapi/util.go deleted file mode 100644 index a4480cd50a5..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/util.go +++ /dev/null @@ -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, - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go index 9ced93008f7..1bbfacf43ab 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/openapi.go @@ -18,22 +18,21 @@ package routes import ( "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" + + "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. type OpenAPI struct { - Config *openapi.Config + Config *common.Config } // Install adds the SwaggerUI webservice to the given mux. 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 { glog.Fatalf("Failed to register open api spec for root: %v", err) } diff --git a/staging/src/k8s.io/apiserver/pkg/util/trie/BUILD b/staging/src/k8s.io/apiserver/pkg/util/trie/BUILD deleted file mode 100644 index 42aee264ec8..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/util/trie/BUILD +++ /dev/null @@ -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"], -) diff --git a/staging/src/k8s.io/apiserver/pkg/util/trie/trie.go b/staging/src/k8s.io/apiserver/pkg/util/trie/trie.go deleted file mode 100644 index 90189e7a3d6..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/util/trie/trie.go +++ /dev/null @@ -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 -} diff --git a/staging/src/k8s.io/kube-gen/Godeps/Godeps.json b/staging/src/k8s.io/kube-gen/Godeps/Godeps.json index 54028264e4c..760fd5a6cd1 100644 --- a/staging/src/k8s.io/kube-gen/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-gen/Godeps/Godeps.json @@ -206,18 +206,10 @@ "ImportPath": "github.com/mailru/easyjson/jwriter", "Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" }, - { - "ImportPath": "github.com/pmezard/go-difflib/difflib", - "Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d" - }, { "ImportPath": "github.com/spf13/pflag", "Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7" }, - { - "ImportPath": "github.com/stretchr/testify/assert", - "Rev": "f6abca593680b2315d2075e0f5e2a9751e3f431a" - }, { "ImportPath": "github.com/ugorji/go/codec", "Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74" diff --git a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/BUILD b/staging/src/k8s.io/kube-gen/cmd/openapi-gen/BUILD deleted file mode 100644 index b5343fd9c33..00000000000 --- a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/BUILD +++ /dev/null @@ -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", - ], -) diff --git a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/BUILD b/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/BUILD deleted file mode 100644 index db51ec8763c..00000000000 --- a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/BUILD +++ /dev/null @@ -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", - ], -) diff --git a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/openapi.go b/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/openapi.go deleted file mode 100644 index 7999a587880..00000000000 --- a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/openapi.go +++ /dev/null @@ -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 -} diff --git a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/openapi_test.go b/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/openapi_test.go deleted file mode 100644 index 70e52ea1ac9..00000000000 --- a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/generators/openapi_test.go +++ /dev/null @@ -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()) -} diff --git a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/main.go b/staging/src/k8s.io/kube-gen/cmd/openapi-gen/main.go index b56bab79e21..afb69578618 100644 --- a/staging/src/k8s.io/kube-gen/cmd/openapi-gen/main.go +++ b/staging/src/k8s.io/kube-gen/cmd/openapi-gen/main.go @@ -23,7 +23,7 @@ import ( "path/filepath" "k8s.io/gengo/args" - "k8s.io/kube-gen/cmd/openapi-gen/generators" + "k8s.io/kube-openapi/pkg/generators" "github.com/golang/glog" )