From f85ac9023a07183eb639d77f37fa84af42dcd911 Mon Sep 17 00:00:00 2001 From: Andy Goldstein Date: Tue, 25 Oct 2016 15:00:13 -0400 Subject: [PATCH] Add lister-gen --- .../go2idl/lister-gen/.import-restrictions | 1 + .../go2idl/lister-gen/generators/lister.go | 345 ++++++++++++++++++ cmd/libs/go2idl/lister-gen/generators/tags.go | 33 ++ cmd/libs/go2idl/lister-gen/main.go | 48 +++ hack/.linted_packages | 1 + hack/update-codegen.sh | 16 + 6 files changed, 444 insertions(+) create mode 100644 cmd/libs/go2idl/lister-gen/.import-restrictions create mode 100644 cmd/libs/go2idl/lister-gen/generators/lister.go create mode 100644 cmd/libs/go2idl/lister-gen/generators/tags.go create mode 100644 cmd/libs/go2idl/lister-gen/main.go diff --git a/cmd/libs/go2idl/lister-gen/.import-restrictions b/cmd/libs/go2idl/lister-gen/.import-restrictions new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/cmd/libs/go2idl/lister-gen/.import-restrictions @@ -0,0 +1 @@ +{} diff --git a/cmd/libs/go2idl/lister-gen/generators/lister.go b/cmd/libs/go2idl/lister-gen/generators/lister.go new file mode 100644 index 00000000000..59f226b7f76 --- /dev/null +++ b/cmd/libs/go2idl/lister-gen/generators/lister.go @@ -0,0 +1,345 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generators + +import ( + "fmt" + "io" + "path/filepath" + "strings" + + "k8s.io/gengo/args" + "k8s.io/gengo/generator" + "k8s.io/gengo/namer" + "k8s.io/gengo/types" + clientgentypes "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/types" + + "github.com/golang/glog" + "github.com/spf13/pflag" +) + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + pluralExceptions := map[string]string{ + "Endpoints": "Endpoints", + } + return namer.NameSystems{ + "public": namer.NewPublicNamer(0), + "private": namer.NewPrivateNamer(0), + "raw": namer.NewRawNamer("", nil), + "publicPlural": namer.NewPublicPluralNamer(pluralExceptions), + "allLowercasePlural": namer.NewAllLowercasePluralNamer(pluralExceptions), + "lowercaseSingular": &lowercaseSingularNamer{}, + } +} + +// lowercaseSingularNamer implements Namer +type lowercaseSingularNamer struct{} + +// Name returns t's name in all lowercase. +func (n *lowercaseSingularNamer) Name(t *types.Type) string { + return strings.ToLower(t.Name.Name) +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// generatedBy returns information about the arguments used to invoke +// lister-gen. +func generatedBy() string { + var cmdArgs string + pflag.VisitAll(func(f *pflag.Flag) { + if !f.Changed || f.Name == "verify-only" { + return + } + cmdArgs += fmt.Sprintf("--%s=%s ", f.Name, f.Value) + }) + return fmt.Sprintf("\n// This file was automatically generated by lister-gen with arguments: %s\n\n", cmdArgs) +} + +// Packages makes the client package definition. +func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { + boilerplate, err := arguments.LoadGoBoilerplate() + if err != nil { + glog.Fatalf("Failed loading boilerplate: %v", err) + } + + boilerplate = append(boilerplate, []byte(generatedBy())...) + + var packageList generator.Packages + for _, inputDir := range arguments.InputDirs { + p := context.Universe.Package(inputDir) + + objectMeta, err := objectMetaForPackage(p) + if err != nil { + glog.Fatal(err) + } + if objectMeta == nil { + // no types in this package had genclient + continue + } + + var gv clientgentypes.GroupVersion + + if isInternal(objectMeta) { + lastSlash := strings.LastIndex(p.Path, "/") + if lastSlash == -1 { + glog.Fatalf("error constructing internal group version for package %q", p.Path) + } + gv.Group = clientgentypes.Group(p.Path[lastSlash+1:]) + } else { + parts := strings.Split(p.Path, "/") + gv.Group = clientgentypes.Group(parts[len(parts)-2]) + gv.Version = clientgentypes.Version(parts[len(parts)-1]) + } + + packageList = append(packageList, &generator.DefaultPackage{ + PackageName: strings.ToLower(gv.Version.NonEmpty()), + PackagePath: filepath.Join(arguments.OutputPackagePath, strings.ToLower(gv.Group.NonEmpty()), strings.ToLower(gv.Version.NonEmpty())), + HeaderText: boilerplate, + GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { + for _, t := range p.Types { + // filter out types which dont have genclient=true. + if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false { + continue + } + generators = append(generators, &listerGenerator{ + DefaultGen: generator.DefaultGen{ + OptionalName: arguments.OutputFileBaseName + "." + strings.ToLower(t.Name.Name), + }, + outputPackage: arguments.OutputPackagePath, + groupVersion: gv, + typeToGenerate: t, + imports: generator.NewImportTracker(), + objectMeta: objectMeta, + }) + } + return generators + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + // piggy-back on types that are tagged for client-gen + return extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == true + }, + }) + } + + return packageList +} + +// objectMetaForPackage returns the type of ObjectMeta used by package p. +func objectMetaForPackage(p *types.Package) (*types.Type, error) { + generatingForPackage := false + for _, t := range p.Types { + // filter out types which dont have genclient=true. + if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false { + continue + } + generatingForPackage = true + for _, member := range t.Members { + if member.Name == "ObjectMeta" { + return member.Type, nil + } + } + } + if generatingForPackage { + return nil, fmt.Errorf("unable to find ObjectMeta for any types in package %s", p.Path) + } + return nil, nil +} + +// isInternal returns true if t's package is k8s.io/kubernetes/pkg/api. +func isInternal(t *types.Type) bool { + return t.Name.Package == "k8s.io/kubernetes/pkg/api" +} + +// listerGenerator produces a file of listers for a given GroupVersion and +// type. +type listerGenerator struct { + generator.DefaultGen + outputPackage string + groupVersion clientgentypes.GroupVersion + typeToGenerate *types.Type + imports namer.ImportTracker + objectMeta *types.Type +} + +var _ generator.Generator = &listerGenerator{} + +func (g *listerGenerator) Filter(c *generator.Context, t *types.Type) bool { + return t == g.typeToGenerate +} + +func (g *listerGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *listerGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + imports = append(imports, "k8s.io/kubernetes/pkg/api/errors") + imports = append(imports, "k8s.io/kubernetes/pkg/labels") + // for Indexer + imports = append(imports, "k8s.io/kubernetes/pkg/client/cache") + return +} + +func (g *listerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + glog.V(5).Infof("processing type %v", t) + m := map[string]interface{}{ + "group": g.groupVersion.Group.String(), + "type": t, + "objectMeta": g.objectMeta, + } + + namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines) + if namespaced { + sw.Do(typeListerInterface, m) + } else { + sw.Do(typeListerInterface_NonNamespaced, m) + } + + sw.Do(typeListerStruct, m) + sw.Do(typeListerConstructor, m) + sw.Do(typeLister_List, m) + + if namespaced { + sw.Do(typeLister_NamespaceLister, m) + sw.Do(namespaceListerInterface, m) + sw.Do(namespaceListerStruct, m) + sw.Do(namespaceLister_List, m) + sw.Do(namespaceLister_Get, m) + } else { + sw.Do(typeLister_NonNamespacedGet, m) + } + + return sw.Error() +} + +var typeListerInterface = ` +// $.type|public$Lister helps list $.type|publicPlural$. +type $.type|public$Lister interface { + // List lists all $.type|publicPlural$ in the indexer. + List(selector labels.Selector) (ret []*$.type|raw$, err error) + // $.type|publicPlural$ returns an object that can list and get $.type|publicPlural$. + $.type|publicPlural$(namespace string) $.type|public$NamespaceLister +} +` + +var typeListerInterface_NonNamespaced = ` +// $.type|public$Lister helps list $.type|publicPlural$. +type $.type|public$Lister interface { + // List lists all $.type|publicPlural$ in the indexer. + List(selector labels.Selector) (ret []*$.type|raw$, err error) + // Get retrieves the $.type|public$ from the index for a given name. + Get(name string) (*$.type|raw$, error) +} +` + +var typeListerStruct = ` +// $.type|private$Lister implements the $.type|public$Lister interface. +type $.type|private$Lister struct { + indexer cache.Indexer +} +` + +var typeListerConstructor = ` +// New$.type|public$Lister returns a new $.type|public$Lister. +func New$.type|public$Lister(indexer cache.Indexer) $.type|public$Lister { + return &$.type|private$Lister{indexer: indexer} +} +` + +var typeLister_List = ` +// List lists all $.type|publicPlural$ in the indexer. +func (s *$.type|private$Lister) List(selector labels.Selector) (ret []*$.type|raw$, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*$.type|raw$)) + }) + return ret, err +} +` + +var typeLister_NamespaceLister = ` +// $.type|publicPlural$ returns an object that can list and get $.type|publicPlural$. +func (s *$.type|private$Lister) $.type|publicPlural$(namespace string) $.type|public$NamespaceLister { + return $.type|private$NamespaceLister{indexer: s.indexer, namespace: namespace} +} +` + +var typeLister_NonNamespacedGet = ` +// Get retrieves the $.type|public$ from the index for a given name. +func (s *$.type|private$Lister) Get(name string) (*$.type|raw$, error) { + key := &$.type|raw${ObjectMeta: $.objectMeta|raw${Name: name}} + obj, exists, err := s.indexer.Get(key) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound($.group$.Resource("$.type|lowercaseSingular$"), name) + } + return obj.(*$.type|raw$), nil +} +` + +var namespaceListerInterface = ` +// $.type|public$NamespaceLister helps list and get $.type|publicPlural$. +type $.type|public$NamespaceLister interface { + // List lists all $.type|publicPlural$ in the indexer for a given namespace. + List(selector labels.Selector) (ret []*$.type|raw$, err error) + // Get retrieves the $.type|public$ from the indexer for a given namespace and name. + Get(name string) (*$.type|raw$, error) +} +` + +var namespaceListerStruct = ` +// $.type|private$NamespaceLister implements the $.type|public$NamespaceLister +// interface. +type $.type|private$NamespaceLister struct { + indexer cache.Indexer + namespace string +} +` + +var namespaceLister_List = ` +// List lists all $.type|publicPlural$ in the indexer for a given namespace. +func (s $.type|private$NamespaceLister) List(selector labels.Selector) (ret []*$.type|raw$, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*$.type|raw$)) + }) + return ret, err +} +` + +var namespaceLister_Get = ` +// Get retrieves the $.type|public$ from the indexer for a given namespace and name. +func (s $.type|private$NamespaceLister) Get(name string) (*$.type|raw$, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound($.group$.Resource("$.type|lowercaseSingular$"), name) + } + return obj.(*$.type|raw$), nil +} +` diff --git a/cmd/libs/go2idl/lister-gen/generators/tags.go b/cmd/libs/go2idl/lister-gen/generators/tags.go new file mode 100644 index 00000000000..34aa77231fa --- /dev/null +++ b/cmd/libs/go2idl/lister-gen/generators/tags.go @@ -0,0 +1,33 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generators + +import ( + "github.com/golang/glog" + "k8s.io/gengo/types" +) + +// extractBoolTagOrDie gets the comment-tags for the key and asserts that, if +// it exists, the value is boolean. If the tag did not exist, it returns +// false. +func extractBoolTagOrDie(key string, lines []string) bool { + val, err := types.ExtractSingleBoolCommentTag("+", key, false, lines) + if err != nil { + glog.Fatalf(err.Error()) + } + return val +} diff --git a/cmd/libs/go2idl/lister-gen/main.go b/cmd/libs/go2idl/lister-gen/main.go new file mode 100644 index 00000000000..8bddb161d69 --- /dev/null +++ b/cmd/libs/go2idl/lister-gen/main.go @@ -0,0 +1,48 @@ +/* +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 main + +import ( + "path/filepath" + + "k8s.io/gengo/args" + "k8s.io/kubernetes/cmd/libs/go2idl/lister-gen/generators" + + "github.com/golang/glog" + "github.com/spf13/pflag" +) + +func main() { + arguments := &args.GeneratorArgs{ + OutputBase: args.DefaultSourceTree(), + GoHeaderFilePath: filepath.Join(args.DefaultSourceTree(), "k8s.io/kubernetes/hack/boilerplate/boilerplate.go.txt"), + GeneratedBuildTag: "ignore_autogenerated", + OutputFileBaseName: "zz_generated", + OutputPackagePath: "k8s.io/kubernetes/pkg/client/listers", + } + arguments.AddFlags(pflag.CommandLine) + + // Run it. + if err := arguments.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + generators.Packages, + ); err != nil { + glog.Fatalf("Error: %v", err) + } + glog.V(2).Info("Completed successfully.") +} diff --git a/hack/.linted_packages b/hack/.linted_packages index 8ebac0b8591..fbcccb8eea4 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -33,6 +33,7 @@ cmd/libs/go2idl/generator cmd/libs/go2idl/go-to-protobuf cmd/libs/go2idl/go-to-protobuf/protoc-gen-gogo cmd/libs/go2idl/import-boss +cmd/libs/go2idl/lister-gen cmd/libs/go2idl/openapi-gen cmd/libs/go2idl/parser cmd/libs/go2idl/set-gen diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 653845df6c5..45c821a575f 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -26,11 +26,13 @@ kube::golang::setup_env BUILD_TARGETS=( cmd/libs/go2idl/client-gen cmd/libs/go2idl/set-gen + cmd/libs/go2idl/lister-gen ) make -C "${KUBE_ROOT}" WHAT="${BUILD_TARGETS[*]}" clientgen=$(kube::util::find-binary "client-gen") setgen=$(kube::util::find-binary "set-gen") +listergen=$(kube::util::find-binary "lister-gen") # Please do not add any logic to this shell script. Add logic to the go code # that generates the set-gen program. @@ -63,4 +65,18 @@ ${clientgen} --clientset-name=federation_internalclientset --clientset-path=k8s. ${clientgen} --clientset-name=federation_release_1_5 --clientset-path=k8s.io/kubernetes/federation/client/clientset_generated --input="../../federation/apis/federation/v1beta1","api/v1","extensions/v1beta1" --included-types-overrides="api/v1/Service,api/v1/Namespace,extensions/v1beta1/ReplicaSet,api/v1/Secret,extensions/v1beta1/Ingress,extensions/v1beta1/Deployment,extensions/v1beta1/DaemonSet,api/v1/ConfigMap,api/v1/Event" "$@" ${setgen} "$@" +LISTERGEN_APIS=( +pkg/api +pkg/api/v1 +$( + cd ${KUBE_ROOT} + find pkg/apis -name types.go | xargs dirname | sort +) +) + +LISTERGEN_APIS=(${LISTERGEN_APIS[@]/#/k8s.io/kubernetes/}) +LISTERGEN_APIS=$(IFS=,; echo "${LISTERGEN_APIS[*]}") + +${listergen} --input-dirs "${LISTERGEN_APIS}" "$@" + # You may add additional calls of code generators like set-gen above.