Move generated openAPI specs out of genericapiserver and make it configurable

This commit is contained in:
mbohlool 2016-09-13 17:11:36 -07:00
parent 02e0d5ab75
commit 38b2567d8b
11 changed files with 102 additions and 51 deletions

View File

@ -45,6 +45,7 @@ import (
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller/informers" "k8s.io/kubernetes/pkg/controller/informers"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/generated/openapi"
"k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/genericapiserver/authorizer" "k8s.io/kubernetes/pkg/genericapiserver/authorizer"
genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options" genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options"
@ -282,6 +283,8 @@ func Run(s *options.APIServer) error {
genericConfig.ProxyTLSClientConfig = proxyTLSClientConfig genericConfig.ProxyTLSClientConfig = proxyTLSClientConfig
genericConfig.Serializer = api.Codecs genericConfig.Serializer = api.Codecs
genericConfig.OpenAPIInfo.Title = "Kubernetes" genericConfig.OpenAPIInfo.Title = "Kubernetes"
genericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions
genericConfig.EnableOpenAPISupport = true
config := &master.Config{ config := &master.Config{
Config: genericConfig, Config: genericConfig,

View File

@ -85,7 +85,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
`)...) `)...)
targets := []*types.Type{} targets := []*types.Package{}
for i := range inputs { for i := range inputs {
glog.V(5).Infof("considering pkg %q", i) glog.V(5).Infof("considering pkg %q", i)
pkg, ok := context.Universe[i] pkg, ok := context.Universe[i]
@ -93,11 +93,9 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
// If the input had no Go files, for example. // If the input had no Go files, for example.
continue continue
} }
for _, t := range pkg.Types { if hasOpenAPITagValue(pkg.Comments, tagTargetType) || hasOpenAPITagValue(pkg.DocComments, tagTargetType) {
if hasOpenAPITagValue(t.CommentLines, tagTargetType) { glog.V(5).Infof("target package : %q", pkg)
glog.V(5).Infof("target type : %q", t) targets = append(targets, pkg)
targets = append(targets, t)
}
} }
} }
switch len(targets) { switch len(targets) {
@ -106,7 +104,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
// and build excluded the target package. // and build excluded the target package.
return generator.Packages{} return generator.Packages{}
case 1: case 1:
pkg := context.Universe[targets[0].Name.Package] pkg := targets[0]
return generator.Packages{&generator.DefaultPackage{ return generator.Packages{&generator.DefaultPackage{
PackageName: strings.Split(filepath.Base(pkg.Path), ".")[0], PackageName: strings.Split(filepath.Base(pkg.Path), ".")[0],
PackagePath: pkg.Path, PackagePath: pkg.Path,
@ -144,27 +142,27 @@ const (
// openApiGen produces a file with auto-generated OpenAPI functions. // openApiGen produces a file with auto-generated OpenAPI functions.
type openAPIGen struct { type openAPIGen struct {
generator.DefaultGen generator.DefaultGen
// TargetType is the type that will get OpenAPIDefinitions method returning all definitions. // TargetPackage is the package that will get OpenAPIDefinitions variable contains all open API definitions.
targetType *types.Type targetPackage *types.Package
imports namer.ImportTracker imports namer.ImportTracker
context *generator.Context context *generator.Context
} }
func NewOpenAPIGen(sanitizedName string, targetType *types.Type, context *generator.Context) generator.Generator { func NewOpenAPIGen(sanitizedName string, targetPackage *types.Package, context *generator.Context) generator.Generator {
return &openAPIGen{ return &openAPIGen{
DefaultGen: generator.DefaultGen{ DefaultGen: generator.DefaultGen{
OptionalName: sanitizedName, OptionalName: sanitizedName,
}, },
imports: generator.NewImportTracker(), imports: generator.NewImportTracker(),
targetType: targetType, targetPackage: targetPackage,
context: context, context: context,
} }
} }
func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems { func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
// Have the raw namer for this file track what it imports. // Have the raw namer for this file track what it imports.
return namer.NameSystems{ return namer.NameSystems{
"raw": namer.NewRawNamer(g.targetType.Name.Package, g.imports), "raw": namer.NewRawNamer(g.targetPackage.Path, g.imports),
} }
} }
@ -177,10 +175,10 @@ func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool {
} }
func (g *openAPIGen) isOtherPackage(pkg string) bool { func (g *openAPIGen) isOtherPackage(pkg string) bool {
if pkg == g.targetType.Name.Package { if pkg == g.targetPackage.Path {
return false return false
} }
if strings.HasSuffix(pkg, "\""+g.targetType.Name.Package+"\"") { if strings.HasSuffix(pkg, "\""+g.targetPackage.Path+"\"") {
return false return false
} }
return true return true
@ -205,14 +203,14 @@ func argsFromType(t *types.Type) generator.Args {
func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error { func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$") sw := generator.NewSnippetWriter(w, c, "$", "$")
sw.Do("func (_ $.type|raw$) OpenAPIDefinitions() *$.OpenAPIDefinitions|raw$ {\n", argsFromType(g.targetType)) sw.Do("var OpenAPIDefinitions *$.OpenAPIDefinitions|raw$ = ", argsFromType(nil))
sw.Do("return &$.OpenAPIDefinitions|raw${\n", argsFromType(nil)) sw.Do("&$.OpenAPIDefinitions|raw${\n", argsFromType(nil))
return sw.Error() return sw.Error()
} }
func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error { func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$") sw := generator.NewSnippetWriter(w, c, "$", "$")
sw.Do("}\n}\n", nil) sw.Do("}\n", nil)
return sw.Error() return sw.Error()
} }

View File

@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apiserver/authenticator" "k8s.io/kubernetes/pkg/apiserver/authenticator"
"k8s.io/kubernetes/pkg/controller/informers" "k8s.io/kubernetes/pkg/controller/informers"
"k8s.io/kubernetes/pkg/generated/openapi"
"k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/genericapiserver/authorizer" "k8s.io/kubernetes/pkg/genericapiserver/authorizer"
genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options" genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options"
@ -185,6 +186,8 @@ func Run(s *options.ServerRunOptions) error {
genericConfig.APIResourceConfigSource = storageFactory.APIResourceConfigSource genericConfig.APIResourceConfigSource = storageFactory.APIResourceConfigSource
genericConfig.MasterServiceNamespace = s.MasterServiceNamespace genericConfig.MasterServiceNamespace = s.MasterServiceNamespace
genericConfig.Serializer = api.Codecs genericConfig.Serializer = api.Codecs
genericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions
genericConfig.EnableOpenAPISupport = true
// TODO: Move this to generic api server (Need to move the command line flag). // TODO: Move this to generic api server (Need to move the command line flag).
if s.EnableWatchCache { if s.EnableWatchCache {

19
pkg/generated/doc.go Normal file
View File

@ -0,0 +1,19 @@
/*
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.
*/
// generated package is the destination for all generated files. Not all generated files are currently use this package
// but the plan is to move as much of them as possible to this package.
package generated

View File

@ -0,0 +1,19 @@
/*
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.
*/
// openapi generated definitions.
// +k8s:openapi-gen=target
package openapi

View File

@ -32,6 +32,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
"k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators/common"
"k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
@ -157,6 +158,10 @@ type Config struct {
// OpenAPIDefaultResponse will be used if an web service operation does not have any responses listed. // OpenAPIDefaultResponse will be used if an web service operation does not have any responses listed.
OpenAPIDefaultResponse spec.Response 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
} }
func NewConfig(options *options.ServerRunOptions) *Config { func NewConfig(options *options.ServerRunOptions) *Config {
@ -183,7 +188,6 @@ func NewConfig(options *options.ServerRunOptions) *Config {
ReadWritePort: options.SecurePort, ReadWritePort: options.SecurePort,
ServiceClusterIPRange: &options.ServiceClusterIPRange, ServiceClusterIPRange: &options.ServiceClusterIPRange,
ServiceNodePortRange: options.ServiceNodePortRange, ServiceNodePortRange: options.ServiceNodePortRange,
EnableOpenAPISupport: true,
OpenAPIDefaultResponse: spec.Response{ OpenAPIDefaultResponse: spec.Response{
ResponseProps: spec.ResponseProps{ ResponseProps: spec.ResponseProps{
Description: "Default Response."}}, Description: "Default Response."}},
@ -308,6 +312,7 @@ func (c Config) New() (*GenericAPIServer, error) {
enableOpenAPISupport: c.EnableOpenAPISupport, enableOpenAPISupport: c.EnableOpenAPISupport,
openAPIInfo: c.OpenAPIInfo, openAPIInfo: c.OpenAPIInfo,
openAPIDefaultResponse: c.OpenAPIDefaultResponse, openAPIDefaultResponse: c.OpenAPIDefaultResponse,
openAPIDefinitions: c.OpenAPIDefinitions,
} }
if c.EnableWatchCache { if c.EnableWatchCache {

View File

@ -35,6 +35,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
"k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen/generators/common"
"k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/rest"
@ -171,6 +172,7 @@ type GenericAPIServer struct {
PostStartHooks map[string]PostStartHookFunc PostStartHooks map[string]PostStartHookFunc
postStartHookLock sync.Mutex postStartHookLock sync.Mutex
postStartHooksCalled bool postStartHooksCalled bool
openAPIDefinitions *common.OpenAPIDefinitions
} }
func (s *GenericAPIServer) StorageDecorator() generic.StorageDecorator { func (s *GenericAPIServer) StorageDecorator() generic.StorageDecorator {
@ -565,24 +567,26 @@ func (s *GenericAPIServer) InstallOpenAPI() {
info := s.openAPIInfo info := s.openAPIInfo
info.Title = info.Title + " " + w.RootPath() info.Title = info.Title + " " + w.RootPath()
err := openapi.RegisterOpenAPIService(&openapi.Config{ err := openapi.RegisterOpenAPIService(&openapi.Config{
OpenAPIServePath: w.RootPath() + "/swagger.json", OpenAPIServePath: w.RootPath() + "/swagger.json",
WebServices: []*restful.WebService{w}, WebServices: []*restful.WebService{w},
ProtocolList: []string{"https"}, ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"}, IgnorePrefixes: []string{"/swaggerapi"},
Info: &info, Info: &info,
DefaultResponse: &s.openAPIDefaultResponse, DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer) }, s.HandlerContainer)
if err != nil { if err != nil {
glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err) glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err)
} }
} }
err := openapi.RegisterOpenAPIService(&openapi.Config{ err := openapi.RegisterOpenAPIService(&openapi.Config{
OpenAPIServePath: "/swagger.json", OpenAPIServePath: "/swagger.json",
WebServices: s.HandlerContainer.RegisteredWebServices(), WebServices: s.HandlerContainer.RegisteredWebServices(),
ProtocolList: []string{"https"}, ProtocolList: []string{"https"},
IgnorePrefixes: []string{"/swaggerapi"}, IgnorePrefixes: []string{"/swaggerapi"},
Info: &s.openAPIInfo, Info: &s.openAPIInfo,
DefaultResponse: &s.openAPIDefaultResponse, DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer) }, s.HandlerContainer)
if err != nil { if err != nil {
glog.Fatalf("Failed to register open api spec for root: %v", err) glog.Fatalf("Failed to register open api spec for root: %v", err)

View File

@ -50,14 +50,16 @@ type Config struct {
DefaultResponse *spec.Response DefaultResponse *spec.Response
// List of webservice's path prefixes to ignore // List of webservice's path prefixes to ignore
IgnorePrefixes []string 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.
OpenAPIDefinitions *common.OpenAPIDefinitions
} }
// +k8s:openapi-gen=target
type openAPI struct { type openAPI struct {
config *Config config *Config
swagger *spec.Swagger swagger *spec.Swagger
protocolList []string protocolList []string
openAPIDefinitions *common.OpenAPIDefinitions
} }
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification. // RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
@ -91,17 +93,10 @@ func RegisterOpenAPIService(config *Config, containers *restful.Container) (err
} }
func (o *openAPI) init() error { func (o *openAPI) init() error {
if o.openAPIDefinitions == nil {
// Compilation error here means the code generator need to run first.
o.openAPIDefinitions = o.OpenAPIDefinitions()
}
err := o.buildPaths() err := o.buildPaths()
if err != nil { if err != nil {
return err return err
} }
// no need to the keep type list in memory
o.openAPIDefinitions = nil
return nil return nil
} }
@ -109,7 +104,7 @@ func (o *openAPI) buildDefinitionRecursively(name string) error {
if _, ok := o.swagger.Definitions[name]; ok { if _, ok := o.swagger.Definitions[name]; ok {
return nil return nil
} }
if item, ok := (*o.openAPIDefinitions)[name]; ok { if item, ok := (*o.config.OpenAPIDefinitions)[name]; ok {
o.swagger.Definitions[name] = item.Schema o.swagger.Definitions[name] = item.Schema
for _, v := range item.Dependencies { for _, v := range item.Dependencies {
if err := o.buildDefinitionRecursively(v); err != nil { if err := o.buildDefinitionRecursively(v); err != nil {

View File

@ -42,10 +42,6 @@ func setUp(t *testing.T, fullMethods bool) (openAPI, *assert.Assertions) {
Info: config.Info, Info: config.Info,
}, },
}, },
openAPIDefinitions: &common.OpenAPIDefinitions{
"openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
"openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
},
}, assert }, assert
} }
@ -193,6 +189,10 @@ func getConfig(fullMethods bool) *Config {
Description: "Test API", Description: "Test API",
}, },
}, },
OpenAPIDefinitions: &common.OpenAPIDefinitions{
"openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
"openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
},
} }
} }

View File

@ -71,6 +71,7 @@ import (
"github.com/go-openapi/validate" "github.com/go-openapi/validate"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context" "golang.org/x/net/context"
"k8s.io/kubernetes/pkg/generated/openapi"
) )
// setUp is a convience function for setting up for (most) tests. // setUp is a convience function for setting up for (most) tests.
@ -1251,6 +1252,7 @@ func TestValidOpenAPISpec(t *testing.T) {
_, etcdserver, config, assert := setUp(t) _, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t) defer etcdserver.Terminate(t)
config.OpenAPIDefinitions = openapi.OpenAPIDefinitions
config.EnableOpenAPISupport = true config.EnableOpenAPISupport = true
config.EnableIndex = true config.EnableIndex = true
config.OpenAPIInfo = spec.Info{ config.OpenAPIInfo = spec.Info{

View File

@ -58,6 +58,7 @@ import (
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"k8s.io/kubernetes/pkg/generated/openapi"
) )
const ( const (
@ -235,6 +236,8 @@ func NewMasterConfig() *master.Config {
ServiceClusterIPRange: parseCIDROrDie("10.0.0.0/24"), ServiceClusterIPRange: parseCIDROrDie("10.0.0.0/24"),
ServiceNodePortRange: utilnet.PortRange{Base: 30000, Size: 2768}, ServiceNodePortRange: utilnet.PortRange{Base: 30000, Size: 2768},
EnableVersion: true, EnableVersion: true,
OpenAPIDefinitions: openapi.OpenAPIDefinitions,
EnableOpenAPISupport: true,
}, },
KubeletClient: kubeletclient.FakeKubeletClient{}, KubeletClient: kubeletclient.FakeKubeletClient{},
} }