Merge pull request #5507 from mikesplain/fix_gendocs

Fix api-gen-docs dependencies
This commit is contained in:
k8s-ci-robot 2018-07-28 22:46:56 -07:00 committed by GitHub
commit ed7408540b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 3334 additions and 32 deletions

4
Gopkg.lock generated
View File

@ -621,6 +621,8 @@
[[projects]]
name = "github.com/kubernetes-incubator/apiserver-builder"
packages = [
"cmd/apiregister-gen",
"cmd/apiregister-gen/generators",
"cmd/apiserver-boot",
"cmd/apiserver-boot/boot/build",
"cmd/apiserver-boot/boot/create",
@ -1786,6 +1788,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "b2c3876a387bc89849c0113df69d24ad9987cbccea885c60ed499162851e4039"
inputs-digest = "c083db22a03ca0fe3ccc11361c91a444e674aa1ec6fb0aba6447b4b4982c0355"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -12,6 +12,7 @@ required = [
# Needed for docs generation
"k8s.io/code-generator/cmd/openapi-gen",
"github.com/kubernetes-incubator/apiserver-builder/cmd/apiserver-boot",
"github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen",
"github.com/kubernetes-incubator/reference-docs/gen-apidocs",
# Needed for bazel generation / verification

View File

@ -33,11 +33,13 @@ cp ${WORK_DIR}/go/bin/openapi-gen ${GOPATH}/bin/
# Install the apiserver-builder commands
GOPATH=${WORK_DIR}/go/ go install github.com/kubernetes-incubator/apiserver-builder/cmd/...
cp ${WORK_DIR}/go/bin/openapi-gen ${GOPATH}/bin/
cp ${WORK_DIR}/go/bin/apiserver-boot ${GOPATH}/bin/
cp ${WORK_DIR}/go/bin/apiregister-gen ${GOPATH}/bin/
# Install the reference docs commands (apiserver-builder commands invoke these)
GOPATH=${WORK_DIR}/go/ go install github.com/kubernetes-incubator/reference-docs/gen-apidocs/...
cp ${WORK_DIR}/go/bin/openapi-gen ${GOPATH}/bin/
cp ${WORK_DIR}/go/bin/gen-apidocs ${GOPATH}/bin/
# More code generators
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/lister-gen
@ -46,3 +48,15 @@ cp ${WORK_DIR}/go/bin/lister-gen ${GOPATH}/bin/
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/informer-gen
cp ${WORK_DIR}/go/bin/informer-gen ${GOPATH}/bin/
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/client-gen
cp ${WORK_DIR}/go/bin/client-gen ${GOPATH}/bin/
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/deepcopy-gen
cp ${WORK_DIR}/go/bin/deepcopy-gen ${GOPATH}/bin/
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/conversion-gen
cp ${WORK_DIR}/go/bin/conversion-gen ${GOPATH}/bin/
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/defaulter-gen
cp ${WORK_DIR}/go/bin/defaulter-gen ${GOPATH}/bin/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = ["main.go"],
importmap = "vendor/github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen",
importpath = "github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen",
visibility = ["//visibility:private"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen/generators:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library",
"//vendor/k8s.io/gengo/args:go_default_library",
],
)
go_binary(
name = "apiregister-gen",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,28 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"apis_generator.go",
"controller_generator.go",
"install_generator.go",
"package.go",
"parser.go",
"unversioned_generator.go",
"util.go",
"versioned_generator.go",
],
importmap = "vendor/github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen/generators",
importpath = "github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen/generators",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/markbates/inflect:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets: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",
],
)

View File

@ -0,0 +1,100 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"text/template"
"fmt"
"k8s.io/gengo/generator"
)
type apiGenerator struct {
generator.DefaultGen
apis *APIs
}
var _ generator.Generator = &apiGenerator{}
func CreateApisGenerator(apis *APIs, filename string) generator.Generator {
return &apiGenerator{
generator.DefaultGen{OptionalName: filename},
apis,
}
}
func (d *apiGenerator) Imports(c *generator.Context) []string {
imports := []string{
"github.com/kubernetes-incubator/apiserver-builder/pkg/builders",
}
for _, group := range d.apis.Groups {
imports = append(imports, group.PkgPath)
for _, version := range group.Versions {
imports = append(imports, fmt.Sprintf(
"%s%s \"%s\"", group.Group, version.Version, version.Pkg.Path))
}
}
return imports
}
func (d *apiGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("apis-template").Parse(APIsTemplate))
err := temp.Execute(w, d.apis)
if err != nil {
return err
}
return err
}
var APIsTemplate = `
// GetAllApiBuilders returns all known APIGroupBuilders
// so they can be registered with the apiserver
func GetAllApiBuilders() []*builders.APIGroupBuilder {
return []*builders.APIGroupBuilder{
{{ range $group := .Groups -}}
Get{{ $group.GroupTitle }}APIBuilder(),
{{ end -}}
}
}
{{ range $group := .Groups -}}
var {{ $group.Group }}ApiGroup = builders.NewApiGroupBuilder(
"{{ $group.Group }}.{{ $group.Domain }}",
"{{ $group.PkgPath}}").
WithUnVersionedApi({{ $group.Group }}.ApiVersion).
WithVersionedApis(
{{ range $version := $group.Versions -}}
{{ $group.Group }}{{ $version.Version }}.ApiVersion,
{{ end -}}
).
WithRootScopedKinds(
{{ range $version := $group.Versions -}}
{{ range $res := $version.Resources -}}
{{ if $res.NonNamespaced -}}
"{{ $res.Kind }}",
{{ end -}}
{{ end -}}
{{ end -}}
)
func Get{{ $group.GroupTitle }}APIBuilder() *builders.APIGroupBuilder {
return {{ $group.Group }}ApiGroup
}
{{ end -}}
`

View File

@ -0,0 +1,357 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"strings"
"text/template"
"github.com/markbates/inflect"
"k8s.io/gengo/generator"
)
type controllerGenerator struct {
generator.DefaultGen
controller Controller
}
var _ generator.Generator = &controllerGenerator{}
func CreateControllerGenerator(controller Controller, filename string) generator.Generator {
return &controllerGenerator{
generator.DefaultGen{OptionalName: filename},
controller,
}
}
func (d *controllerGenerator) Imports(c *generator.Context) []string {
im := []string{
"github.com/golang/glog",
"github.com/kubernetes-incubator/apiserver-builder/pkg/controller",
"k8s.io/apimachinery/pkg/api/errors",
"k8s.io/client-go/rest",
"k8s.io/client-go/tools/cache",
"k8s.io/client-go/util/workqueue",
d.controller.Repo + "/pkg/controller/sharedinformers",
}
return im
}
func (d *controllerGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("controller-template").Funcs(
template.FuncMap{
"title": strings.Title,
"plural": inflect.NewDefaultRuleset().Pluralize,
},
).Parse(ControllerAPITemplate))
return temp.Execute(w, d.controller)
}
var ControllerAPITemplate = `
// {{.Target.Kind}}Controller implements the controller.{{.Target.Kind}}Controller interface
type {{.Target.Kind}}Controller struct {
queue *controller.QueueWorker
// Handles messages
controller *{{.Target.Kind}}ControllerImpl
Name string
BeforeReconcile func(key string)
AfterReconcile func(key string, err error)
Informers *sharedinformers.SharedInformers
}
// NewController returns a new {{.Target.Kind}}Controller for responding to {{.Target.Kind}} events
func New{{.Target.Kind}}Controller(config *rest.Config, si *sharedinformers.SharedInformers) *{{.Target.Kind}}Controller {
q := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "{{.Target.Kind}}")
queue := &controller.QueueWorker{q, 10, "{{.Target.Kind}}", nil}
c := &{{.Target.Kind}}Controller{queue, nil, "{{.Target.Kind}}", nil, nil, si}
// For non-generated code to add events
uc := &{{.Target.Kind}}ControllerImpl{}
var ci sharedinformers.Controller = uc
// Call the Init method that is implemented.
// Support multiple Init methods for backwards compatibility
if i, ok := ci.(sharedinformers.LegacyControllerInit); ok {
i.Init(config, si, c.LookupAndReconcile)
} else if i, ok := ci.(sharedinformers.ControllerInit); ok {
i.Init(&sharedinformers.ControllerInitArgumentsImpl{si, config, c.LookupAndReconcile})
}
c.controller = uc
queue.Reconcile = c.reconcile
if c.Informers.WorkerQueues == nil {
c.Informers.WorkerQueues = map[string]*controller.QueueWorker{}
}
c.Informers.WorkerQueues["{{.Target.Kind}}"] = queue
si.Factory.{{title .Target.Group}}().{{title .Target.Version}}().{{plural .Target.Kind }}().Informer().
AddEventHandler(&controller.QueueingEventHandler{q, nil, false})
return c
}
func (c *{{.Target.Kind}}Controller) GetName() string {
return c.Name
}
func (c *{{.Target.Kind}}Controller) LookupAndReconcile(key string) (err error) {
return c.reconcile(key)
}
func (c *{{.Target.Kind}}Controller) reconcile(key string) (err error) {
var namespace, name string
if c.BeforeReconcile != nil {
c.BeforeReconcile(key)
}
if c.AfterReconcile != nil {
// Wrap in a function so err is evaluated after it is set
defer func() { c.AfterReconcile(key, err) }()
}
namespace, name, err = cache.SplitMetaNamespaceKey(key)
if err != nil {
return
}
u, err := c.controller.Get(namespace, name)
if errors.IsNotFound(err) {
glog.Infof("Not doing work for {{.Target.Kind}} %v because it has been deleted", key)
// Set error so it is picked up by AfterReconcile and the return function
err = nil
return
}
if err != nil {
glog.Errorf("Unable to retrieve {{.Target.Kind}} %v from store: %v", key, err)
return
}
// Set error so it is picked up by AfterReconcile and the return function
err = c.controller.Reconcile(u)
return
}
func (c *{{.Target.Kind}}Controller) Run(stopCh <-chan struct{}) {
for _, q := range c.Informers.WorkerQueues {
q.Run(stopCh)
}
controller.GetDefaults(c.controller).Run(stopCh)
}
`
type allControllerGenerator struct {
generator.DefaultGen
Controllers []Controller
}
var _ generator.Generator = &allControllerGenerator{}
func CreateAllControllerGenerator(controllers []Controller, filename string) generator.Generator {
return &allControllerGenerator{
generator.DefaultGen{OptionalName: filename},
controllers,
}
}
func (d *allControllerGenerator) Imports(c *generator.Context) []string {
if len(d.Controllers) == 0 {
return []string{}
}
repo := d.Controllers[0].Repo
im := []string{
"k8s.io/client-go/rest",
"github.com/kubernetes-incubator/apiserver-builder/pkg/controller",
repo + "/pkg/controller/sharedinformers",
}
// Import package for each controller
repos := map[string]string{}
for _, c := range d.Controllers {
repos[c.Pkg.Path] = ""
}
for k, _ := range repos {
im = append(im, k)
}
return im
}
func (d *allControllerGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("all-controller-template").Funcs(
template.FuncMap{
"title": strings.Title,
"plural": inflect.NewDefaultRuleset().Pluralize,
},
).Parse(AllControllerAPITemplate))
return temp.Execute(w, d)
}
var AllControllerAPITemplate = `
func GetAllControllers(config *rest.Config) ([]controller.Controller, chan struct{}) {
shutdown := make(chan struct{})
si := sharedinformers.NewSharedInformers(config, shutdown)
return []controller.Controller{
{{ range $c := .Controllers -}}
{{ $c.Pkg.Name }}.New{{ $c.Target.Kind }}Controller(config, si),
{{ end -}}
}, shutdown
}
`
type informersGenerator struct {
generator.DefaultGen
Controllers []Controller
}
var _ generator.Generator = &informersGenerator{}
func CreateInformersGenerator(controllers []Controller, filename string) generator.Generator {
return &informersGenerator{
generator.DefaultGen{OptionalName: filename},
controllers,
}
}
func (d *informersGenerator) Imports(c *generator.Context) []string {
if len(d.Controllers) == 0 {
return []string{}
}
repo := d.Controllers[0].Repo
return []string{
"time",
"github.com/kubernetes-incubator/apiserver-builder/pkg/controller",
"k8s.io/client-go/rest",
repo + "/pkg/client/clientset_generated/clientset",
repo + "/pkg/client/informers_generated/externalversions",
"k8s.io/client-go/tools/cache",
}
}
func (d *informersGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("informersGenerator-template").Funcs(
template.FuncMap{
"title": strings.Title,
"plural": inflect.NewDefaultRuleset().Pluralize,
},
).Parse(InformersTemplate))
return temp.Execute(w, d.Controllers)
}
var InformersTemplate = `
// SharedInformers wraps all informers used by controllers so that
// they are shared across controller implementations
type SharedInformers struct {
controller.SharedInformersDefaults
Factory externalversions.SharedInformerFactory
}
// newSharedInformers returns a set of started informers
func NewSharedInformers(config *rest.Config, shutdown <-chan struct{}) *SharedInformers {
si := &SharedInformers{
controller.SharedInformersDefaults{},
externalversions.NewSharedInformerFactory(clientset.NewForConfigOrDie(config), 10*time.Minute),
}
if si.SetupKubernetesTypes() {
si.InitKubernetesInformers(config)
}
si.Init()
si.startInformers(shutdown)
si.StartAdditionalInformers(shutdown)
return si
}
// startInformers starts all of the informers
func (si *SharedInformers) startInformers(shutdown <-chan struct{}) {
{{ range $c := . -}}
go si.Factory.{{title $c.Target.Group}}().{{title $c.Target.Version}}().{{plural $c.Target.Kind}}().Informer().Run(shutdown)
{{ end -}}
}
// ControllerInitArguments are arguments provided to the Init function for a new controller.
type ControllerInitArguments interface {
// GetSharedInformers returns the SharedInformers that can be used to access
// informers and listers for watching and indexing Kubernetes Resources
GetSharedInformers() *SharedInformers
// GetRestConfig returns the Config to create new client-go clients
GetRestConfig() *rest.Config
// Watch uses resourceInformer to watch a resource. When create, update, or deletes
// to the resource type are encountered, watch uses watchResourceToReconcileResourceKey
// to lookup the key for the resource reconciled by the controller (maybe a different type
// than the watched resource), and enqueue it to be reconciled.
// watchName: name of the informer. may appear in logs
// resourceInformer: gotten from the SharedInformer. controls which resource type is watched
// getReconcileKey: takes an instance of the watched resource and returns
// a key for the reconciled resource type to enqueue.
Watch(watchName string, resourceInformer cache.SharedIndexInformer,
getReconcileKey func(interface{}) (string, error))
}
type ControllerInitArgumentsImpl struct {
Si *SharedInformers
Rc *rest.Config
Rk func(key string) error
}
func (c ControllerInitArgumentsImpl) GetSharedInformers() *SharedInformers {
return c.Si
}
func (c ControllerInitArgumentsImpl) GetRestConfig() *rest.Config {
return c.Rc
}
// Watch uses resourceInformer to watch a resource. When create, update, or deletes
// to the resource type are encountered, watch uses watchResourceToReconcileResourceKey
// to lookup the key for the resource reconciled by the controller (maybe a different type
// than the watched resource), and enqueue it to be reconciled.
// watchName: name of the informer. may appear in logs
// resourceInformer: gotten from the SharedInformer. controls which resource type is watched
// getReconcileKey: takes an instance of the watched resource and returns
// a key for the reconciled resource type to enqueue.
func (c ControllerInitArgumentsImpl) Watch(
watchName string, resourceInformer cache.SharedIndexInformer,
getReconcileKey func(interface{}) (string, error)) {
c.Si.Watch(watchName, resourceInformer, getReconcileKey, c.Rk)
}
type Controller interface {}
// LegacyControllerInit old controllers may implement this, and we keep
// it for backwards compatibility.
type LegacyControllerInit interface {
Init(config *rest.Config, si *SharedInformers, r func(key string) error)
}
// ControllerInit new controllers should implement this. It is more flexible in
// allowing additional options to be passed in
type ControllerInit interface {
Init(args ControllerInitArguments)
}
`

View File

@ -0,0 +1,68 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"text/template"
"path"
"k8s.io/gengo/generator"
)
type installGenerator struct {
generator.DefaultGen
apigroup *APIGroup
}
var _ generator.Generator = &unversionedGenerator{}
func CreateInstallGenerator(apigroup *APIGroup, filename string) generator.Generator {
return &installGenerator{
generator.DefaultGen{OptionalName: filename},
apigroup,
}
}
func (d *installGenerator) Imports(c *generator.Context) []string {
return []string{
"k8s.io/apimachinery/pkg/apimachinery/announced",
"k8s.io/apimachinery/pkg/apimachinery/registered",
"k8s.io/apimachinery/pkg/runtime",
path.Dir(d.apigroup.Pkg.Path),
}
}
func (d *installGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("install-template").Parse(InstallAPITemplate))
err := temp.Execute(w, d.apigroup)
if err != nil {
return err
}
return err
}
var InstallAPITemplate = `
func Install(
groupFactoryRegistry announced.APIGroupFactoryRegistry,
registry *registered.APIRegistrationManager,
scheme *runtime.Scheme) {
apis.Get{{ .GroupTitle }}APIBuilder().Install(groupFactoryRegistry, registry, scheme)
}
`

View File

@ -0,0 +1,178 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"path"
"path/filepath"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/gengo/args"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"github.com/pkg/errors"
)
// CustomArgs is used tby the go2idl framework to pass args specific to this
// generator.
type CustomArgs struct{}
type Gen struct {
p []generator.Package
}
func (g *Gen) Execute(arguments *args.GeneratorArgs) error {
return arguments.Execute(
g.NameSystems(),
g.DefaultNameSystem(),
g.Packages)
}
// DefaultNameSystem returns the default name system for ordering the types to be
// processed by the generators in this package.
func (g *Gen) DefaultNameSystem() string {
return "public"
}
// NameSystems returns the name system used by the generators in this package.
func (g *Gen) NameSystems() namer.NameSystems {
return namer.NameSystems{
"public": namer.NewPublicNamer(1),
"raw": namer.NewRawNamer("", nil),
}
}
func (g *Gen) ParsePackages(context *generator.Context, arguments *args.GeneratorArgs) (sets.String, sets.String, string, string) {
versionedPkgs := sets.NewString()
unversionedPkgs := sets.NewString()
mainPkg := ""
apisPkg := ""
for _, o := range context.Order {
if IsAPIResource(o) {
versioned := o.Name.Package
versionedPkgs.Insert(versioned)
unversioned := filepath.Dir(versioned)
unversionedPkgs.Insert(unversioned)
if apis := filepath.Dir(unversioned); apis != apisPkg && len(apisPkg) > 0 {
panic(errors.Errorf(
"Found multiple apis directory paths: %v and %v", apisPkg, apis))
} else {
apisPkg = apis
mainPkg = filepath.Dir(apisPkg)
}
}
}
return versionedPkgs, unversionedPkgs, apisPkg, mainPkg
}
func (g *Gen) Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
g.p = generator.Packages{}
b := NewAPIsBuilder(context, arguments)
for _, apigroup := range b.APIs.Groups {
for _, apiversion := range apigroup.Versions {
factory := &packageFactory{apiversion.Pkg.Path, arguments}
// Add generators for versioned types
gen := CreateVersionedGenerator(apiversion, apigroup, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(gen))
}
factory := &packageFactory{apigroup.Pkg.Path, arguments}
gen := CreateUnversionedGenerator(apigroup, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(gen))
factory = &packageFactory{path.Join(apigroup.Pkg.Path, "install"), arguments}
gen = CreateInstallGenerator(apigroup, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(gen))
}
factory := &packageFactory{b.APIs.Pkg.Path, arguments}
gen := CreateApisGenerator(b.APIs, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(gen))
// Add generators for Controllers.
repo := ""
for _, c := range b.Controllers {
repo = c.Repo
factory = &packageFactory{c.Pkg.Path, arguments}
cgen := CreateControllerGenerator(c, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(cgen))
}
if len(b.Controllers) > 0 {
factory = &packageFactory{context.Universe[repo+"/pkg/controller"].Path, arguments}
cgen := CreateAllControllerGenerator(b.Controllers, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(cgen))
}
if len(b.Controllers) > 0 {
factory = &packageFactory{context.Universe[repo+"/pkg/controller/sharedinformers"].Path, arguments}
cgen := CreateInformersGenerator(b.Controllers, arguments.OutputFileBaseName)
g.p = append(g.p, factory.createPackage(cgen))
}
return g.p
}
type packageFactory struct {
path string
arguments *args.GeneratorArgs
}
// Creates a package with a generator
func (f *packageFactory) createPackage(gen generator.Generator) generator.Package {
path := f.path
name := strings.Split(filepath.Base(f.path), ".")[0]
return &generator.DefaultPackage{
PackageName: name,
PackagePath: path,
HeaderText: f.getHeader(),
GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) {
return []generator.Generator{gen}
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
return t.Name.Package == f.path
},
}
}
// Returns the header for generated files
func (f *packageFactory) getHeader() []byte {
header := []byte(`/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by apiregister-gen. Do not edit it manually!
`)
return header
}

View File

@ -0,0 +1,761 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"fmt"
"log"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/gengo/args"
"k8s.io/gengo/generator"
"k8s.io/gengo/types"
)
type APIs struct {
// Domain is the domain portion of the group - e.g. k8s.io
Domain string
// Package is the name of the go package the api group is under - e.g. github.com/pwittrock/apiserver-helloworld/apis
Package string
Pkg *types.Package
// Groups is a list of API groups
Groups map[string]*APIGroup
}
type Controller struct {
Target schema.GroupVersionKind
Resource string
Pkg *types.Package
Repo string
}
type APIGroup struct {
// Package is the name of the go package the api group is under - e.g. github.com/pwittrock/apiserver-helloworld/apis
Package string
// Domain is the domain portion of the group - e.g. k8s.io
Domain string
// Group is the short name of the group - e.g. mushroomkingdom
Group string
GroupTitle string
// Versions is the list of all versions for this group keyed by name
Versions map[string]*APIVersion
UnversionedResources map[string]*APIResource
// Structs is a list of unversioned definitions that must be generated
Structs []*Struct
Pkg *types.Package
PkgPath string
}
type Struct struct {
// Name is the name of the type
Name string
// GenClient
GenClient bool
GenDeepCopy bool
NonNamespaced bool
GenUnversioned bool
// Fields is the list of fields appearing in the struct
Fields []*Field
}
type Field struct {
// Name is the name of the field
Name string
// For versioned Kubernetes types, this is the versioned package
VersionedPackage string
// For versioned Kubernetes types, this is the unversioned package
UnversionedImport string
UnversionedType string
}
type APIVersion struct {
// Domain is the group domain - e.g. k8s.io
Domain string
// Group is the group name - e.g. mushroomkingdom
Group string
// Version is the api version - e.g. v1beta1
Version string
// Resources is a list of resources appearing in the API version keyed by name
Resources map[string]*APIResource
// Pkg is the Package object from code-gen
Pkg *types.Package
}
type APIResource struct {
// Domain is the group domain - e.g. k8s.io
Domain string
// Group is the group name - e.g. mushroomkingdom
Group string
// Version is the api version - e.g. v1beta1
Version string
// Kind is the resource name - e.g. PeachesCastle
Kind string
// Resource is the resource name - e.g. peachescastles
Resource string
// REST is the rest.Storage implementation used to handle requests
// This field is optional. The standard REST implementation will be used
// by default.
REST string
// Subresources is a map of subresources keyed by name
Subresources map[string]*APISubresource
// Type is the Type object from code-gen
Type *types.Type
// Strategy is name of the struct to use for the strategy
Strategy string
// Strategy is name of the struct to use for the strategy
StatusStrategy string
// NonNamespaced indicates that the resource kind is non namespaced
NonNamespaced bool
}
type APISubresource struct {
// Domain is the group domain - e.g. k8s.io
Domain string
// Group is the group name - e.g. mushroomkingdom
Group string
// Version is the api version - e.g. v1beta1
Version string
// Kind is the resource name - e.g. PeachesCastle
Kind string
// Resource is the resource name - e.g. peachescastles
Resource string
// Request is the subresource request type - e.g. ScaleCastle
Request string
// REST is the rest.Storage implementation used to handle requests
REST string
// Path is the subresource path - e.g. scale
Path string
// ImportPackage is the import statement that must appear for the Request
ImportPackage string
// RequestType is the type of the request
RequestType *types.Type
// RESTType is the type of the request handler
RESTType *types.Type
}
type APIsBuilder struct {
context *generator.Context
arguments *args.GeneratorArgs
Domain string
VersionedPkgs sets.String
UnversionedPkgs sets.String
APIsPkg string
APIsPkgRaw *types.Package
GroupNames sets.String
APIs *APIs
Controllers []Controller
ByGroupKindVersion map[string]map[string]map[string]*APIResource
ByGroupVersionKind map[string]map[string]map[string]*APIResource
SubByGroupVersionKind map[string]map[string]map[string]*types.Type
Groups map[string]types.Package
}
func NewAPIsBuilder(context *generator.Context, arguments *args.GeneratorArgs) *APIsBuilder {
b := &APIsBuilder{
context: context,
arguments: arguments,
}
b.ParsePackages()
b.ParseDomain()
b.ParseGroupNames()
b.ParseIndex()
b.ParseControllers()
b.ParseAPIs()
return b
}
func (b *APIsBuilder) ParseControllers() {
for _, c := range b.context.Order {
if IsController(c) {
tags := ParseControllerTag(b.GetControllerTag(c))
repo := strings.Split(c.Name.Package, "/pkg/controller")[0]
pkg := b.context.Universe[c.Name.Package]
b.Controllers = append(b.Controllers, Controller{
tags.gvk, tags.resource, pkg, repo})
}
}
}
func (b *APIsBuilder) ParseAPIs() {
apis := &APIs{
Domain: b.Domain,
Package: b.APIsPkg,
Groups: map[string]*APIGroup{},
}
for group, versionMap := range b.ByGroupVersionKind {
apiGroup := &APIGroup{
Group: group,
GroupTitle: strings.Title(group),
Domain: b.Domain,
Versions: map[string]*APIVersion{},
UnversionedResources: map[string]*APIResource{},
}
for version, kindMap := range versionMap {
apiVersion := &APIVersion{
Domain: b.Domain,
Group: group,
Version: version,
Resources: map[string]*APIResource{},
}
for kind, resource := range kindMap {
apiResource := &APIResource{
Domain: resource.Domain,
Version: resource.Version,
Group: resource.Group,
Resource: resource.Resource,
Type: resource.Type,
REST: resource.REST,
Kind: resource.Kind,
Subresources: resource.Subresources,
StatusStrategy: resource.StatusStrategy,
Strategy: resource.Strategy,
NonNamespaced: resource.NonNamespaced,
}
apiVersion.Resources[kind] = apiResource
// Set the package for the api version
apiVersion.Pkg = b.context.Universe[resource.Type.Name.Package]
// Set the package for the api group
apiGroup.Pkg = b.context.Universe[filepath.Dir(resource.Type.Name.Package)]
apiGroup.PkgPath = apiGroup.Pkg.Path
apiGroup.UnversionedResources[kind] = apiResource
}
apiGroup.Versions[version] = apiVersion
}
b.ParseStructs(apiGroup)
apis.Groups[group] = apiGroup
}
apis.Pkg = b.context.Universe[b.APIsPkg]
b.APIs = apis
}
// ParseIndex indexes all types with the comment "// +resource=RESOURCE" by GroupVersionKind and
// GroupKindVersion
func (b *APIsBuilder) ParseIndex() {
b.ByGroupVersionKind = map[string]map[string]map[string]*APIResource{}
b.ByGroupKindVersion = map[string]map[string]map[string]*APIResource{}
b.SubByGroupVersionKind = map[string]map[string]map[string]*types.Type{}
for _, c := range b.context.Order {
if IsAPISubresource(c) {
group := GetGroup(c)
version := GetVersion(c, group)
kind := GetKind(c, group)
if _, f := b.SubByGroupVersionKind[group]; !f {
b.SubByGroupVersionKind[group] = map[string]map[string]*types.Type{}
}
if _, f := b.SubByGroupVersionKind[group][version]; !f {
b.SubByGroupVersionKind[group][version] = map[string]*types.Type{}
}
b.SubByGroupVersionKind[group][version][kind] = c
}
if !IsAPIResource(c) {
continue
}
r := &APIResource{
Type: c,
NonNamespaced: IsNonNamespaced(c),
}
r.Group = GetGroup(c)
r.Version = GetVersion(c, r.Group)
r.Kind = GetKind(c, r.Group)
r.Domain = b.Domain
rt := ParseResourceTag(b.GetResourceTag(c))
r.Resource = rt.Resource
r.REST = rt.REST
r.Strategy = rt.Strategy
// If not defined, default the strategy to the {{.Kind}}Strategy for backwards compatibility
if len(r.Strategy) == 0 {
r.Strategy = fmt.Sprintf("%sStrategy", r.Kind)
}
// Copy the Status strategy to mirror the non-status strategy
r.StatusStrategy = strings.TrimSuffix(r.Strategy, "Strategy")
r.StatusStrategy = fmt.Sprintf("%sStatusStrategy", r.StatusStrategy)
if _, f := b.ByGroupKindVersion[r.Group]; !f {
b.ByGroupKindVersion[r.Group] = map[string]map[string]*APIResource{}
}
if _, f := b.ByGroupKindVersion[r.Group][r.Kind]; !f {
b.ByGroupKindVersion[r.Group][r.Kind] = map[string]*APIResource{}
}
if _, f := b.ByGroupVersionKind[r.Group]; !f {
b.ByGroupVersionKind[r.Group] = map[string]map[string]*APIResource{}
}
if _, f := b.ByGroupVersionKind[r.Group][r.Version]; !f {
b.ByGroupVersionKind[r.Group][r.Version] = map[string]*APIResource{}
}
b.ByGroupKindVersion[r.Group][r.Kind][r.Version] = r
b.ByGroupVersionKind[r.Group][r.Version][r.Kind] = r
// Do subresources
if !HasSubresource(c) {
continue
}
r.Type = c
r.Subresources = b.GetSubresources(r)
}
}
func (b *APIsBuilder) GetSubresources(c *APIResource) map[string]*APISubresource {
r := map[string]*APISubresource{}
subresources := b.GetSubresourceTags(c.Type)
if len(subresources) == 0 {
// Not a subresource
return r
}
for _, subresource := range subresources {
// Parse the values for each subresource
tags := ParseSubresourceTag(c, subresource)
sr := &APISubresource{
Kind: tags.Kind,
Request: tags.RequestKind,
Path: tags.Path,
REST: tags.REST,
Domain: b.Domain,
Version: c.Version,
Resource: c.Resource,
Group: c.Group,
}
if !b.IsInPackage(tags) {
// Out of package Request types require an import and are prefixed with the
// package name - e.g. v1.Scale
sr.Request, sr.ImportPackage = b.GetNameAndImport(tags)
}
if v, found := r[sr.Path]; found {
log.Fatalf("Multiple subresources registered for path %s: %v %v",
sr.Path, v, subresource)
}
r[sr.Path] = sr
}
return r
}
// Returns true if the subresource Request type is in the same package as the resource type
func (b *APIsBuilder) IsInPackage(tags SubresourceTags) bool {
return !strings.Contains(tags.RequestKind, ".")
}
// GetNameAndImport converts
func (b *APIsBuilder) GetNameAndImport(tags SubresourceTags) (string, string) {
last := strings.LastIndex(tags.RequestKind, ".")
importPackage := tags.RequestKind[:last]
// Set the request kind to the struct name
tags.RequestKind = tags.RequestKind[last+1:]
// Find the package
pkg := filepath.Base(importPackage)
// Prefix the struct name with the package it is in
return strings.Join([]string{pkg, tags.RequestKind}, "."), importPackage
}
// ResourceTags contains the tags present in a "+resource=" comment
type ResourceTags struct {
Resource string
REST string
Strategy string
}
// ParseResourceTag parses the tags in a "+resource=" comment into a ResourceTags struct
func ParseResourceTag(tag string) ResourceTags {
result := ResourceTags{}
for _, elem := range strings.Split(tag, ",") {
kv := strings.Split(elem, "=")
if len(kv) != 2 {
log.Fatalf("// +resource: tags must be key value pairs. Expected "+
"keys [path=<subresourcepath>] "+
"Got string: [%s]", tag)
}
value := kv[1]
switch kv[0] {
case "rest":
result.REST = value
case "path":
result.Resource = value
case "strategy":
result.Strategy = value
}
}
return result
}
// ResourceTags contains the tags present in a "+resource=" comment
type ControllerTags struct {
gvk schema.GroupVersionKind
resource string
}
// ParseResourceTag parses the tags in a "+resource=" comment into a ResourceTags struct
func ParseControllerTag(tag string) ControllerTags {
result := ControllerTags{}
for _, elem := range strings.Split(tag, ",") {
kv := strings.Split(elem, "=")
if len(kv) != 2 {
log.Fatalf("// +controller: tags must be key value pairs. Expected "+
"keys [group=<group>,version=<version>,kind=<kind>,resource=<resource>] "+
"Got string: [%s]", tag)
}
value := kv[1]
switch kv[0] {
case "group":
result.gvk.Group = value
case "version":
result.gvk.Version = value
case "kind":
result.gvk.Kind = value
case "resource":
result.resource = value
}
}
return result
}
// SubresourceTags contains the tags present in a "+subresource=" comment
type SubresourceTags struct {
Path string
Kind string
RequestKind string
REST string
}
// ParseSubresourceTag parses the tags in a "+subresource=" comment into a SubresourceTags struct
func ParseSubresourceTag(c *APIResource, tag string) SubresourceTags {
result := SubresourceTags{}
for _, elem := range strings.Split(tag, ",") {
kv := strings.Split(elem, "=")
if len(kv) != 2 {
log.Fatalf("// +subresource: tags must be key value pairs. Expected "+
"keys [request=<requestType>,rest=<restImplType>,path=<subresourcepath>] "+
"Got string: [%s]", tag)
}
value := kv[1]
switch kv[0] {
case "request":
result.RequestKind = value
case "rest":
result.REST = value
case "path":
// Strip the parent resource
result.Path = strings.Replace(value, c.Resource+"/", "", -1)
}
}
return result
}
// GetResourceTag returns the value of the "+resource=" comment tag
func (b *APIsBuilder) GetResourceTag(c *types.Type) string {
comments := Comments(c.CommentLines)
resource := comments.GetTag("resource", ":")
if len(resource) == 0 {
panic(errors.Errorf("Must specify +resource comment for type %v", c.Name))
}
return resource
}
func (b *APIsBuilder) GenClient(c *types.Type) bool {
comments := Comments(c.CommentLines)
resource := comments.GetTag("resource", ":")
return len(resource) > 0
}
func (b *APIsBuilder) GenDeepCopy(c *types.Type) bool {
comments := Comments(c.CommentLines)
return comments.HasTag("subresource-request")
}
func (b *APIsBuilder) GetControllerTag(c *types.Type) string {
comments := Comments(c.CommentLines)
resource := comments.GetTag("controller", ":")
if len(resource) == 0 {
panic(errors.Errorf("Must specify +controller comment for type %v", c.Name))
}
return resource
}
func (b *APIsBuilder) GetSubresourceTags(c *types.Type) []string {
comments := Comments(c.CommentLines)
return comments.GetTags("subresource", ":")
}
// ParseGroupNames initializes b.GroupNames with the set of all groups
func (b *APIsBuilder) ParseGroupNames() {
b.GroupNames = sets.String{}
for p := range b.UnversionedPkgs {
pkg := b.context.Universe[p]
if pkg == nil {
// If the input had no Go files, for example.
continue
}
b.GroupNames.Insert(filepath.Base(p))
}
}
// ParsePackages parses out the sets of Versioned, Unversioned packages and identifies the root Apis package.
func (b *APIsBuilder) ParsePackages() {
b.VersionedPkgs = sets.NewString()
b.UnversionedPkgs = sets.NewString()
for _, o := range b.context.Order {
if IsAPIResource(o) {
versioned := o.Name.Package
b.VersionedPkgs.Insert(versioned)
unversioned := filepath.Dir(versioned)
b.UnversionedPkgs.Insert(unversioned)
if apis := filepath.Dir(unversioned); apis != b.APIsPkg && len(b.APIsPkg) > 0 {
panic(errors.Errorf(
"Found multiple apis directory paths: %v and %v. "+
"Do you have a +resource tag on a resource that is not in a version "+
"directory?", b.APIsPkg, apis))
} else {
b.APIsPkg = apis
}
}
}
}
// ParseDomain parses the domain from the apis/doc.go file comment "// +domain=YOUR_DOMAIN".
func (b *APIsBuilder) ParseDomain() {
pkg := b.context.Universe[b.APIsPkg]
if pkg == nil {
// If the input had no Go files, for example.
panic(errors.Errorf("Missing apis package."))
}
comments := Comments(pkg.Comments)
b.Domain = comments.GetTag("domain", "=")
if len(b.Domain) == 0 {
panic("Could not find string matching // +domain=.+ in apis/doc.go")
}
}
type GenUnversionedType struct {
Type *types.Type
Resource *APIResource
}
func (b *APIsBuilder) ParseStructs(apigroup *APIGroup) {
remaining := []GenUnversionedType{}
for _, version := range apigroup.Versions {
for _, resource := range version.Resources {
remaining = append(remaining, GenUnversionedType{resource.Type, resource})
}
}
for _, version := range b.SubByGroupVersionKind[apigroup.Group] {
for _, kind := range version {
remaining = append(remaining, GenUnversionedType{kind, nil})
}
}
done := sets.String{}
for len(remaining) > 0 {
// Pop the next element from the list
next := remaining[0]
remaining[0] = remaining[len(remaining)-1]
remaining = remaining[:len(remaining)-1]
// Already processed this type. Skip it
if done.Has(next.Type.Name.Name) {
continue
}
done.Insert(next.Type.Name.Name)
// Generate the struct and append to the list
result, additionalTypes := apigroup.DoType(next.Type)
// This is a resource, so generate the client
if b.GenClient(next.Type) {
result.GenClient = true
result.GenDeepCopy = true
}
if next.Resource != nil {
result.NonNamespaced = IsNonNamespaced(next.Type)
}
if b.GenDeepCopy(next.Type) {
result.GenDeepCopy = true
}
apigroup.Structs = append(apigroup.Structs, result)
// Add the newly discovered subtypes
for _, at := range additionalTypes {
remaining = append(remaining, GenUnversionedType{at, nil})
}
}
}
func (apigroup *APIGroup) DoType(t *types.Type) (*Struct, []*types.Type) {
remaining := []*types.Type{}
s := &Struct{
Name: t.Name.Name,
GenClient: false,
GenUnversioned: true, // Generate unversioned structs by default
}
for _, c := range t.CommentLines {
if strings.Contains(c, "+genregister:unversioned=false") {
// Don't generate the unversioned struct
s.GenUnversioned = false
}
}
for _, member := range t.Members {
uType := member.Type.Name.Name
memberName := member.Name
uImport := ""
// Use the element type for Pointers, Maps and Slices
mSubType := member.Type
hasElem := false
for mSubType.Elem != nil {
mSubType = mSubType.Elem
hasElem = true
}
if hasElem {
// Strip the package from the field type
uType = strings.Replace(member.Type.String(), mSubType.Name.Package+".", "", 1)
}
base := filepath.Base(member.Type.String())
samepkg := t.Name.Package == mSubType.Name.Package
// If not in the same package, calculate the import pkg
if !samepkg {
parts := strings.Split(base, ".")
if len(parts) > 1 {
// Don't generate unversioned types for core types, just use the versioned types
if strings.HasPrefix(mSubType.Name.Package, "k8s.io/api/") {
// Import the package under an alias so it doesn't conflict with other groups
// having the same version
importAlias := path.Base(path.Dir(mSubType.Name.Package)) + path.Base(mSubType.Name.Package)
uImport = fmt.Sprintf("%s \"%s\"", importAlias, mSubType.Name.Package)
if hasElem {
// Replace the full package with the alias when referring to the type
uType = strings.Replace(member.Type.String(), mSubType.Name.Package, importAlias, 1)
} else {
// Replace the full package with the alias when referring to the type
uType = fmt.Sprintf("%s.%s", importAlias, parts[1])
}
} else {
switch member.Type.Name.Package {
case "k8s.io/apimachinery/pkg/apis/meta/v1":
// Use versioned types for meta/v1
uImport = fmt.Sprintf("%s \"%s\"", "metav1", "k8s.io/apimachinery/pkg/apis/meta/v1")
uType = "metav1." + parts[1]
default:
// Use unversioned types for everything else
t := member.Type
if t.Elem != nil {
// Handle Pointers, Maps, Slices
// We need to parse the package from the Type String
t = t.Elem
str := member.Type.String()
startPkg := strings.LastIndexAny(str, "*]")
endPkg := strings.LastIndexAny(str, ".")
pkg := str[startPkg+1 : endPkg]
name := str[endPkg+1:]
prefix := str[:startPkg+1]
uImportBase := path.Base(pkg)
uImportName := path.Base(path.Dir(pkg)) + uImportBase
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
uType = prefix + uImportName + "." + name
//fmt.Printf("\nDifferent Parent Package: %s\nChild Package: %s\nKind: %s (Kind.String() %s)\nImport stmt: %s\nType: %s\n\n",
// pkg,
// member.Type.Name.Package,
// member.Type.Kind,
// member.Type.String(),
// uImport,
// uType)
} else {
// Handle non- Pointer, Maps, Slices
pkg := t.Name.Package
name := t.Name.Name
// Come up with the alias the package is imported under
// Concatenate with directory package to reduce naming collisions
uImportBase := path.Base(pkg)
uImportName := path.Base(path.Dir(pkg)) + uImportBase
// Create the import statement
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
// Create the field type name - should be <pkgalias>.<TypeName>
uType = uImportName + "." + name
//fmt.Printf("\nDifferent Parent Package: %s\nChild Package: %s\nKind: %s (Kind.String() %s)\nImport stmt: %s\nType: %s\n\n",
// pkg,
// member.Type.Name.Package,
// member.Type.Kind,
// member.Type.String(),
// uImport,
// uType)
}
}
}
}
}
if member.Embedded {
memberName = ""
}
s.Fields = append(s.Fields, &Field{
Name: memberName,
VersionedPackage: member.Type.Name.Package,
UnversionedImport: uImport,
UnversionedType: uType,
})
// Add this member Type for processing if it isn't a primitive and
// is part of the same API group
if !mSubType.IsPrimitive() && GetGroup(mSubType) == GetGroup(t) {
remaining = append(remaining, mSubType)
}
}
return s, remaining
}

View File

@ -0,0 +1,292 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"text/template"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/gengo/generator"
)
type unversionedGenerator struct {
generator.DefaultGen
apigroup *APIGroup
}
var _ generator.Generator = &unversionedGenerator{}
func CreateUnversionedGenerator(apigroup *APIGroup, filename string) generator.Generator {
return &unversionedGenerator{
generator.DefaultGen{OptionalName: filename},
apigroup,
}
}
func (d *unversionedGenerator) Imports(c *generator.Context) []string {
imports := sets.NewString(
"fmt",
"github.com/kubernetes-incubator/apiserver-builder/pkg/builders",
"k8s.io/apimachinery/pkg/apis/meta/internalversion",
"k8s.io/apimachinery/pkg/runtime",
"k8s.io/apimachinery/pkg/runtime/schema",
"k8s.io/apiserver/pkg/endpoints/request",
"k8s.io/apiserver/pkg/registry/rest")
// Get imports for all fields
for _, s := range d.apigroup.Structs {
for _, f := range s.Fields {
if len(f.UnversionedImport) > 0 {
imports.Insert(f.UnversionedImport)
}
}
}
return imports.List()
}
func (d *unversionedGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("unversioned-wiring-template").Parse(UnversionedAPITemplate))
err := temp.Execute(w, d.apigroup)
if err != nil {
return err
}
return err
}
var UnversionedAPITemplate = `
var (
{{ range $api := .UnversionedResources -}}
Internal{{ $api.Kind }} = builders.NewInternalResource(
"{{ $api.Resource }}",
"{{ $api.Kind }}",
func() runtime.Object { return &{{ $api.Kind }}{} },
func() runtime.Object { return &{{ $api.Kind }}List{} },
)
Internal{{ $api.Kind }}Status = builders.NewInternalResourceStatus(
"{{ $api.Resource }}",
"{{ $api.Kind }}Status",
func() runtime.Object { return &{{ $api.Kind }}{} },
func() runtime.Object { return &{{ $api.Kind }}List{} },
)
{{ range $subresource := .Subresources -}}
Internal{{$subresource.REST}} = builders.NewInternalSubresource(
"{{$subresource.Resource}}", "{{$subresource.Request}}", "{{$subresource.Path}}",
func() runtime.Object { return &{{$subresource.Request}}{} },
)
{{ end -}}
{{ end -}}
// Registered resources and subresources
ApiVersion = builders.NewApiGroup("{{.Group}}.{{.Domain}}").WithKinds(
{{ range $api := .UnversionedResources -}}
Internal{{$api.Kind}},
Internal{{$api.Kind}}Status,
{{ range $subresource := $api.Subresources -}}
Internal{{$subresource.REST}},
{{ end -}}
{{ end -}}
)
// Required by code generated by go2idl
AddToScheme = ApiVersion.SchemaBuilder.AddToScheme
SchemeBuilder = ApiVersion.SchemaBuilder
localSchemeBuilder = &SchemeBuilder
SchemeGroupVersion = ApiVersion.GroupVersion
)
// Required by code generated by go2idl
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Required by code generated by go2idl
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
{{ range $s := .Structs -}}
{{ if $s.GenUnversioned -}}
{{ if $s.GenClient }}// +genclient{{end}}
{{ if $s.GenClient }}// +genclient{{ if $s.NonNamespaced }}:nonNamespaced{{end}}{{end}}
{{ if $s.GenDeepCopy }}// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object{{end}}
type {{ $s.Name }} struct {
{{ range $f := $s.Fields -}}
{{ $f.Name }} {{ $f.UnversionedType }}
{{ end -}}
}
{{ end -}}
{{ end -}}
{{ range $api := .UnversionedResources -}}
//
// {{.Kind}} Functions and Structs
//
// +k8s:deepcopy-gen=false
type {{.Strategy}} struct {
builders.DefaultStorageStrategy
}
// +k8s:deepcopy-gen=false
type {{.StatusStrategy}} struct {
builders.DefaultStatusStorageStrategy
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$api.Kind}}List struct {
metav1.TypeMeta
metav1.ListMeta
Items []{{$api.Kind}}
}
{{ range $subresource := $api.Subresources -}}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$subresource.Request}}List struct {
metav1.TypeMeta
metav1.ListMeta
Items []{{$subresource.Request}}
}
{{ end -}}
func ({{$api.Kind}}) NewStatus() interface{} {
return {{$api.Kind}}Status{}
}
func (pc *{{$api.Kind}}) GetStatus() interface{} {
return pc.Status
}
func (pc *{{$api.Kind}}) SetStatus(s interface{}) {
pc.Status = s.({{$api.Kind}}Status)
}
func (pc *{{$api.Kind}}) GetSpec() interface{} {
return pc.Spec
}
func (pc *{{$api.Kind}}) SetSpec(s interface{}) {
pc.Spec = s.({{$api.Kind}}Spec)
}
func (pc *{{$api.Kind}}) GetObjectMeta() *metav1.ObjectMeta {
return &pc.ObjectMeta
}
func (pc *{{$api.Kind}}) SetGeneration(generation int64) {
pc.ObjectMeta.Generation = generation
}
func (pc {{$api.Kind}}) GetGeneration() int64 {
return pc.ObjectMeta.Generation
}
// Registry is an interface for things that know how to store {{.Kind}}.
// +k8s:deepcopy-gen=false
type {{.Kind}}Registry interface {
List{{.Kind}}s(ctx request.Context, options *internalversion.ListOptions) (*{{.Kind}}List, error)
Get{{.Kind}}(ctx request.Context, id string, options *metav1.GetOptions) (*{{.Kind}}, error)
Create{{.Kind}}(ctx request.Context, id *{{.Kind}}) (*{{.Kind}}, error)
Update{{.Kind}}(ctx request.Context, id *{{.Kind}}) (*{{.Kind}}, error)
Delete{{.Kind}}(ctx request.Context, id string) (bool, error)
}
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched types will panic.
func New{{.Kind}}Registry(sp builders.StandardStorageProvider) {{.Kind}}Registry {
return &storage{{.Kind}}{sp}
}
// Implement Registry
// storage puts strong typing around storage calls
// +k8s:deepcopy-gen=false
type storage{{.Kind}} struct {
builders.StandardStorageProvider
}
func (s *storage{{.Kind}}) List{{.Kind}}s(ctx request.Context, options *internalversion.ListOptions) (*{{.Kind}}List, error) {
if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() {
return nil, fmt.Errorf("field selector not supported yet")
}
st := s.GetStandardStorage()
obj, err := st.List(ctx, options)
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}List), err
}
func (s *storage{{.Kind}}) Get{{.Kind}}(ctx request.Context, id string, options *metav1.GetOptions) (*{{.Kind}}, error) {
st := s.GetStandardStorage()
obj, err := st.Get(ctx, id, options)
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}), nil
}
func (s *storage{{.Kind}}) Create{{.Kind}}(ctx request.Context, object *{{.Kind}}) (*{{.Kind}}, error) {
st := s.GetStandardStorage()
obj, err := st.Create(ctx, object, nil, true)
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}), nil
}
func (s *storage{{.Kind}}) Update{{.Kind}}(ctx request.Context, object *{{.Kind}}) (*{{.Kind}}, error) {
st := s.GetStandardStorage()
obj, _, err := st.Update(ctx, object.Name, rest.DefaultUpdatedObjectInfo(object), nil, nil)
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}), nil
}
func (s *storage{{.Kind}}) Delete{{.Kind}}(ctx request.Context, id string) (bool, error) {
st := s.GetStandardStorage()
_, sync, err := st.Delete(ctx, id, nil)
return sync, err
}
{{ end -}}
`
var installTemplate = `
{{.BoilerPlate}}
package install
import (
"github.com/kubernetes-incubator/apiserver-builder/example/pkg/apis"
"k8s.io/apimachinery/pkg/apimachinery/announced"
"k8s.io/apimachinery/pkg/apimachinery/registered"
"k8s.io/apimachinery/pkg/runtime"
)
func Install(
groupFactoryRegistry announced.APIGroupFactoryRegistry,
registry *registered.APIRegistrationManager,
scheme *runtime.Scheme) {
apis.{{ title .Group }}APIBuilder().Install(groupFactoryRegistry, registry, scheme)
}
`

View File

@ -0,0 +1,167 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"fmt"
"path/filepath"
"strings"
"github.com/pkg/errors"
"k8s.io/gengo/types"
)
// IsAPIResource returns true if t has a +resource comment tag
func IsAPIResource(t *types.Type) bool {
for _, c := range t.CommentLines {
if strings.Contains(c, "+resource") {
return true
}
}
return false
}
// IsNonNamespaced returns true if t has a +nonNamespaced comment tag
func IsNonNamespaced(t *types.Type) bool {
if !IsAPIResource(t) {
return false
}
for _, c := range t.CommentLines {
if strings.Contains(c, "+genclient:nonNamespaced") {
return true
}
}
for _, c := range t.SecondClosestCommentLines {
if strings.Contains(c, "+genclient:nonNamespaced") {
return true
}
}
return false
}
func IsController(t *types.Type) bool {
for _, c := range t.CommentLines {
if strings.Contains(c, "+controller") {
return true
}
}
return false
}
// IsAPISubresource returns true if t has a +subresource-request comment tag
func IsAPISubresource(t *types.Type) bool {
for _, c := range t.CommentLines {
if strings.Contains(c, "+subresource-request") {
return true
}
}
return false
}
// HasSubresource returns true if t is an APIResource with one or more Subresources
func HasSubresource(t *types.Type) bool {
if !IsAPIResource(t) {
return false
}
for _, c := range t.CommentLines {
if strings.Contains(c, "+subresource") {
return true
}
}
return false
}
func IsUnversioned(t *types.Type, group string) bool {
return IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) && GetGroup(t) == group
}
func IsVersioned(t *types.Type, group string) bool {
dir := filepath.Base(filepath.Dir(filepath.Dir(t.Name.Package)))
return IsApisDir(dir) && GetGroup(t) == group
}
func GetVersion(t *types.Type, group string) string {
if !IsVersioned(t, group) {
panic(errors.Errorf("Cannot get version for unversioned type %v", t.Name))
}
return filepath.Base(t.Name.Package)
}
func GetGroup(t *types.Type) string {
return filepath.Base(GetGroupPackage(t))
}
func GetGroupPackage(t *types.Type) string {
if IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) {
return t.Name.Package
}
return filepath.Dir(t.Name.Package)
}
func GetKind(t *types.Type, group string) string {
if !IsVersioned(t, group) && !IsUnversioned(t, group) {
panic(errors.Errorf("Cannot get kind for type not in group %v", t.Name))
}
return t.Name.Name
}
// IsApisDir returns true if a directory path is a Kubernetes api directory
func IsApisDir(dir string) bool {
return dir == "apis" || dir == "api"
}
// Comments is a structure for using comment tags on go structs and fields
type Comments []string
// GetTags returns the value for the first comment with a prefix matching "+name="
// e.g. "+name=foo\n+name=bar" would return "foo"
func (c Comments) GetTag(name, sep string) string {
for _, c := range c {
prefix := fmt.Sprintf("+%s%s", name, sep)
if strings.HasPrefix(c, prefix) {
return strings.Replace(c, prefix, "", 1)
}
}
return ""
}
func (c Comments) HasTag(name string) bool {
for _, c := range c {
prefix := fmt.Sprintf("+%s", name)
if strings.HasPrefix(c, prefix) {
return true
}
}
return false
}
// GetTags returns the value for all comments with a prefix and separator. E.g. for "name" and "="
// "+name=foo\n+name=bar" would return []string{"foo", "bar"}
func (c Comments) GetTags(name, sep string) []string {
tags := []string{}
for _, c := range c {
prefix := fmt.Sprintf("+%s%s", name, sep)
if strings.HasPrefix(c, prefix) {
tags = append(tags, strings.Replace(c, prefix, "", 1))
}
}
return tags
}

View File

@ -0,0 +1,173 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generators
import (
"io"
"text/template"
"k8s.io/gengo/generator"
)
type versionedGenerator struct {
generator.DefaultGen
apiversion *APIVersion
apigroup *APIGroup
}
var _ generator.Generator = &versionedGenerator{}
func CreateVersionedGenerator(apiversion *APIVersion, apigroup *APIGroup, filename string) generator.Generator {
return &versionedGenerator{
generator.DefaultGen{OptionalName: filename},
apiversion,
apigroup,
}
}
func hasSubresources(version *APIVersion) bool {
for _, v := range version.Resources {
if len(v.Subresources) != 0 {
return true
}
}
return false
}
func (d *versionedGenerator) Imports(c *generator.Context) []string {
imports := []string{
"metav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"",
"k8s.io/apimachinery/pkg/runtime",
"github.com/kubernetes-incubator/apiserver-builder/pkg/builders",
"k8s.io/apimachinery/pkg/runtime/schema",
d.apigroup.Pkg.Path,
}
if hasSubresources(d.apiversion) {
imports = append(imports, "k8s.io/apiserver/pkg/registry/rest")
}
return imports
}
func (d *versionedGenerator) Finalize(context *generator.Context, w io.Writer) error {
temp := template.Must(template.New("versioned-template").Parse(VersionedAPITemplate))
return temp.Execute(w, d.apiversion)
}
var VersionedAPITemplate = `
var (
{{ range $api := .Resources -}}
{{ if $api.REST -}}
{{$api.Group}}{{$api.Kind}}Storage = builders.NewApiResourceWithStorage( // Resource status endpoint
{{ $api.Group }}.Internal{{ $api.Kind }},
{{.Kind}}SchemeFns{},
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
New{{ $api.REST }},
)
{{ else -}}
{{$api.Group}}{{$api.Kind}}Storage = builders.NewApiResource( // Resource status endpoint
{{ $api.Group }}.Internal{{ $api.Kind }},
{{.Kind}}SchemeFns{},
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
&{{ $api.Strategy }}{builders.StorageStrategySingleton},
)
{{ end -}}
{{ end -}}
ApiVersion = builders.NewApiVersion("{{.Group}}.{{.Domain}}", "{{.Version}}").WithResources(
{{ range $api := .Resources -}}
{{$api.Group}}{{$api.Kind}}Storage,
{{ if $api.REST }}{{ else -}}
builders.NewApiResource( // Resource status endpoint
{{ $api.Group }}.Internal{{ $api.Kind }}Status,
{{.Kind}}SchemeFns{},
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
&{{ $api.StatusStrategy }}{builders.StatusStorageStrategySingleton},
),{{ end -}}
{{ range $subresource := $api.Subresources -}}
builders.NewApiResourceWithStorage(
{{ $api.Group }}.Internal{{ $subresource.REST }},
builders.SchemeFnsSingleton,
func() runtime.Object { return &{{ $subresource.Request }}{} }, // Register versioned resource
nil,
func() rest.Storage { return &{{ $subresource.REST }}{ {{$api.Group}}.New{{$api.Kind}}Registry({{$api.Group}}{{$api.Kind}}Storage) } },
),
{{ end -}}
{{ end -}}
)
// Required by code generated by go2idl
AddToScheme = ApiVersion.SchemaBuilder.AddToScheme
SchemeBuilder = ApiVersion.SchemaBuilder
localSchemeBuilder = &SchemeBuilder
SchemeGroupVersion = ApiVersion.GroupVersion
)
// Required by code generated by go2idl
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Required by code generated by go2idl
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
{{ range $api := .Resources -}}
//
// {{.Kind}} Functions and Structs
//
// +k8s:deepcopy-gen=false
type {{.Kind}}SchemeFns struct {
builders.DefaultSchemeFns
}
// +k8s:deepcopy-gen=false
type {{.Strategy}} struct {
builders.DefaultStorageStrategy
}
// +k8s:deepcopy-gen=false
type {{.StatusStrategy}} struct {
builders.DefaultStatusStorageStrategy
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$api.Kind}}List struct {
metav1.TypeMeta ` + "`json:\",inline\"`" + `
metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + `
Items []{{$api.Kind}} ` + "`json:\"items\"`" + `
}
{{ range $subresource := $api.Subresources -}}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$subresource.Request}}List struct {
metav1.TypeMeta ` + "`json:\",inline\"`" + `
metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + `
Items []{{$subresource.Request}} ` + "`json:\"items\"`" + `
}
{{ end }}{{ end -}}
`

View File

@ -0,0 +1,51 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"os"
"runtime"
"github.com/golang/glog"
"github.com/kubernetes-incubator/apiserver-builder/cmd/apiregister-gen/generators"
"k8s.io/apiserver/pkg/util/logs"
"k8s.io/gengo/args"
)
func main() {
logs.InitLogs()
defer logs.FlushLogs()
if len(os.Getenv("GOMAXPROCS")) == 0 {
runtime.GOMAXPROCS(runtime.NumCPU())
}
arguments := args.Default()
// Override defaults.
arguments.OutputFileBaseName = "zz_generated.api.register"
// Custom args.
customArgs := &generators.CustomArgs{}
arguments.CustomArgs = customArgs
g := generators.Gen{}
if err := g.Execute(arguments); err != nil {
glog.Fatalf("Error: %v", err)
}
glog.V(2).Info("Completed successfully.")
}