Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate unique Operation IDs for root OpenAPI spec #33629

Merged
merged 3 commits into from
Oct 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
964 changes: 528 additions & 436 deletions api/openapi-spec/root_swagger.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions api/openapi-spec/v1beta1.extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,7 @@
"schemes": [
"https"
],
"operationId": "readNamespacedDeploymentsScale",
"responses": {
"200": {
"description": "OK",
Expand All @@ -1267,6 +1268,7 @@
"schemes": [
"https"
],
"operationId": "replaceNamespacedDeploymentsScale",
"parameters": [
{
"name": "body",
Expand Down Expand Up @@ -1301,6 +1303,7 @@
"schemes": [
"https"
],
"operationId": "patchNamespacedDeploymentsScale",
"parameters": [
{
"name": "body",
Expand Down Expand Up @@ -3527,6 +3530,7 @@
"schemes": [
"https"
],
"operationId": "readNamespacedReplicasetsScale",
"responses": {
"200": {
"description": "OK",
Expand All @@ -3549,6 +3553,7 @@
"schemes": [
"https"
],
"operationId": "replaceNamespacedReplicasetsScale",
"parameters": [
{
"name": "body",
Expand Down Expand Up @@ -3583,6 +3588,7 @@
"schemes": [
"https"
],
"operationId": "patchNamespacedReplicasetsScale",
"parameters": [
{
"name": "body",
Expand Down Expand Up @@ -3760,6 +3766,7 @@
"schemes": [
"https"
],
"operationId": "readNamespacedReplicationcontrollersScale",
"responses": {
"200": {
"description": "OK",
Expand All @@ -3782,6 +3789,7 @@
"schemes": [
"https"
],
"operationId": "replaceNamespacedReplicationcontrollersScale",
"parameters": [
{
"name": "body",
Expand Down Expand Up @@ -3816,6 +3824,7 @@
"schemes": [
"https"
],
"operationId": "patchNamespacedReplicationcontrollersScale",
"parameters": [
{
"name": "body",
Expand Down
8 changes: 5 additions & 3 deletions cmd/kube-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ import (
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/apiserver/authenticator"
"k8s.io/kubernetes/pkg/apiserver/openapi"
authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller/informers"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/generated/openapi"
generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi"
"k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/genericapiserver/authorizer"
genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options"
Expand Down Expand Up @@ -334,8 +335,9 @@ func Run(s *options.APIServer) error {
genericConfig.ProxyDialer = proxyDialerFn
genericConfig.ProxyTLSClientConfig = proxyTLSClientConfig
genericConfig.Serializer = api.Codecs
genericConfig.OpenAPIInfo.Title = "Kubernetes"
genericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
genericConfig.OpenAPIConfig.Definitions = generatedopenapi.OpenAPIDefinitions
genericConfig.OpenAPIConfig.GetOperationID = openapi.GetOperationID
genericConfig.EnableOpenAPISupport = true

config := &master.Config{
Expand Down
7 changes: 7 additions & 0 deletions federation/apis/openapi-spec/root_swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"schemes": [
"https"
],
"operationId": "getCoreAPIVersions",
"responses": {
"200": {
"description": "OK",
Expand All @@ -47,6 +48,7 @@
"schemes": [
"https"
],
"operationId": "getCoreV1APIResources",
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -2748,6 +2750,7 @@
"schemes": [
"https"
],
"operationId": "getAPIVersions",
"responses": {
"200": {
"description": "OK",
Expand All @@ -2774,6 +2777,7 @@
"schemes": [
"https"
],
"operationId": "getExtensionsAPIGroup",
"responses": {
"200": {
"description": "OK",
Expand All @@ -2800,6 +2804,7 @@
"schemes": [
"https"
],
"operationId": "getExtensionsV1beta1APIResources",
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -4464,6 +4469,7 @@
"schemes": [
"https"
],
"operationId": "getFederationAPIGroup",
"responses": {
"200": {
"description": "OK",
Expand All @@ -4490,6 +4496,7 @@
"schemes": [
"https"
],
"operationId": "getFederationV1beta1APIResources",
"responses": {
"200": {
"description": "OK",
Expand Down
6 changes: 5 additions & 1 deletion federation/cmd/federation-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apiserver/authenticator"
apiserveropenapi "k8s.io/kubernetes/pkg/apiserver/openapi"
authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/controller/informers"
Expand Down Expand Up @@ -221,7 +222,10 @@ func Run(s *options.ServerRunOptions) error {
genericConfig.APIResourceConfigSource = storageFactory.APIResourceConfigSource
genericConfig.MasterServiceNamespace = s.MasterServiceNamespace
genericConfig.Serializer = api.Codecs
genericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions
genericConfig.OpenAPIConfig.Definitions = openapi.OpenAPIDefinitions
// Reusing api-server's GetOperationID function. if federation and api-server spec diverge and
// this method does not provide good operation IDs for federation, we should create federation's own GetOperationID.
genericConfig.OpenAPIConfig.GetOperationID = apiserveropenapi.GetOperationID
genericConfig.EnableOpenAPISupport = true

// TODO: Move this to generic api server (Need to move the command line flag).
Expand Down
1 change: 1 addition & 0 deletions hack/.linted_packages
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pkg/apis/rbac/install
pkg/apis/storage/install
pkg/apis/storage/validation
pkg/apiserver/audit
pkg/apiserver/openapi
pkg/auth/authenticator
pkg/auth/authorizer/union
pkg/client/metrics
Expand Down
81 changes: 81 additions & 0 deletions pkg/apiserver/openapi/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
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"
"fmt"
"strings"
"unicode"

"github.com/emicklei/go-restful"

"k8s.io/kubernetes/pkg/util"
)

var verbs = util.CreateTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})

// ToValidOperationID makes an string a valid op ID (e.g. removing punctuations and whitespaces and make it camel case)
func ToValidOperationID(s string, capitalizeFirstLetter bool) string {
var buffer bytes.Buffer
capitalize := capitalizeFirstLetter
for i, r := range s {
if unicode.IsLetter(r) || r == '_' || (i != 0 && unicode.IsDigit(r)) {
if capitalize {
buffer.WriteRune(unicode.ToUpper(r))
capitalize = false
} else {
buffer.WriteRune(r)
}
} else {
capitalize = true
}
}
return buffer.String()
}

// GetOperationID returns a customize operation ID for kubernetes API server's OpenAPI spec to prevent duplicate IDs.
func GetOperationID(servePath string, r *restful.Route) (string, error) {
op := r.Operation
path := r.Path
// TODO: This is hacky, figure out where this name conflict is created and fix it at the root.
if strings.HasPrefix(path, "/apis/extensions/v1beta1/namespaces/{namespace}/") && strings.HasSuffix(op, "ScaleScale") {
op = op[:len(op)-10] + strings.Title(strings.Split(path[48:], "/")[0]) + "Scale"
}
switch servePath {
case "/swagger.json":
prefix, exists := verbs.GetPrefix(op)
if !exists {
return op, fmt.Errorf("operation names should start with a verb. Cannot determine operation verb from %v", op)
}
op = op[len(prefix):]
parts := strings.Split(strings.Trim(path, "/"), "/")
// Assume /api is /apis/core, remove this when we actually server /api/... on /apis/core/...
if len(parts) >= 1 && parts[0] == "api" {
parts = append([]string{"apis", "core"}, parts[1:]...)
}
if len(parts) >= 2 && parts[0] == "apis" {
prefix = prefix + ToValidOperationID(strings.TrimSuffix(parts[1], ".k8s.io"), prefix != "")
if len(parts) > 2 {
prefix = prefix + ToValidOperationID(parts[2], prefix != "")
}
}
return prefix + ToValidOperationID(op, prefix != ""), nil
default:
return op, nil
}
}
37 changes: 17 additions & 20 deletions pkg/genericapiserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,8 @@ type Config struct {
// EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec.
EnableOpenAPISupport bool

// OpenAPIInfo will be directly available as Info section of Open API spec.
OpenAPIInfo spec.Info

// OpenAPIDefaultResponse will be used if an web service operation does not have any responses listed.
OpenAPIDefaultResponse spec.Response

// OpenAPIDefinitions is a map of type to OpenAPI spec for all types used in this API server. Failure to provide
// this map or any of the models used by the server APIs will result in spec generation failure.
OpenAPIDefinitions *common.OpenAPIDefinitions
// OpenAPIConfig will be used in generating OpenAPI spec.
OpenAPIConfig *common.Config

// MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further
// request has to wait.
Expand Down Expand Up @@ -253,13 +246,19 @@ func NewConfig(options *options.ServerRunOptions) *Config {
ReadWritePort: options.SecurePort,
ServiceClusterIPRange: &options.ServiceClusterIPRange,
ServiceNodePortRange: options.ServiceNodePortRange,
OpenAPIDefaultResponse: spec.Response{
ResponseProps: spec.ResponseProps{
Description: "Default Response."}},
OpenAPIInfo: spec.Info{
InfoProps: spec.InfoProps{
Title: "Generic API Server",
Version: "unversioned",
OpenAPIConfig: &common.Config{
ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"},
Info: &spec.Info{
InfoProps: spec.InfoProps{
Title: "Generic API Server",
Version: "unversioned",
},
},
DefaultResponse: &spec.Response{
ResponseProps: spec.ResponseProps{
Description: "Default Response.",
},
},
},
MaxRequestsInFlight: options.MaxRequestsInFlight,
Expand Down Expand Up @@ -386,10 +385,8 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
KubernetesServiceNodePort: c.KubernetesServiceNodePort,
apiGroupsForDiscovery: map[string]unversioned.APIGroup{},

enableOpenAPISupport: c.EnableOpenAPISupport,
openAPIInfo: c.OpenAPIInfo,
openAPIDefaultResponse: c.OpenAPIDefaultResponse,
openAPIDefinitions: c.OpenAPIDefinitions,
enableOpenAPISupport: c.EnableOpenAPISupport,
openAPIConfig: c.OpenAPIConfig,
}

s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)
Expand Down
37 changes: 14 additions & 23 deletions pkg/genericapiserver/genericapiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import (
systemd "github.com/coreos/go-systemd/daemon"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful/swagger"
"github.com/go-openapi/spec"
"github.com/golang/glog"

"github.com/go-openapi/spec"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
Expand Down Expand Up @@ -147,6 +147,11 @@ type GenericAPIServer struct {
apiGroupsForDiscoveryLock sync.RWMutex
apiGroupsForDiscovery map[string]unversioned.APIGroup

// See Config.$name for documentation of these flags

enableOpenAPISupport bool
openAPIConfig *common.Config

// PostStartHooks are each called after the server has started listening, in a separate go func for each
// with no guaranteee of ordering between them. The map key is a name used for error reporting.
// It may kill the process with a panic if it wishes to by returning an error
Expand All @@ -156,7 +161,6 @@ type GenericAPIServer struct {

// See Config.$name for documentation of these flags:

enableOpenAPISupport bool
openAPIInfo spec.Info
openAPIDefaultResponse spec.Response
openAPIDefinitions *common.OpenAPIDefinitions
Expand Down Expand Up @@ -462,33 +466,20 @@ func (s *GenericAPIServer) InstallOpenAPI() {
// Install one spec per web service, an ideal client will have a ClientSet containing one client
// per each of these specs.
for _, w := range s.HandlerContainer.RegisteredWebServices() {
if w.RootPath() == "/swaggerapi" {
if strings.HasPrefix(w.RootPath(), "/swaggerapi") {
continue
}
info := s.openAPIInfo
info.Title = info.Title + " " + w.RootPath()
err := openapi.RegisterOpenAPIService(&openapi.Config{
OpenAPIServePath: w.RootPath() + "/swagger.json",
WebServices: []*restful.WebService{w},
ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"},
Info: &info,
DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer.Container)
config := *s.openAPIConfig
config.Info = new(spec.Info)
*config.Info = *s.openAPIConfig.Info
config.Info.Title = config.Info.Title + " " + w.RootPath()
err := openapi.RegisterOpenAPIService(w.RootPath()+"/swagger.json", []*restful.WebService{w}, &config, s.HandlerContainer.Container)
if err != nil {
glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err)
}
}
err := openapi.RegisterOpenAPIService(&openapi.Config{
OpenAPIServePath: "/swagger.json",
WebServices: s.HandlerContainer.RegisteredWebServices(),
ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"},
Info: &s.openAPIInfo,
DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer.Container)
err := openapi.RegisterOpenAPIService("/swagger.json", s.HandlerContainer.RegisteredWebServices(), s.openAPIConfig, s.HandlerContainer.Container)

if err != nil {
glog.Fatalf("Failed to register open api spec for root: %v", err)
}
Expand Down