From 64083a05766b30c111baa48964373b17fd8b1123 Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Sun, 27 Dec 2015 21:54:08 -0800 Subject: [PATCH] Generate the clientset --- cmd/libs/go2idl/args/args.go | 5 +- .../client-gen/generators/client-generator.go | 63 ++++++- .../generators/generator-for-clientset.go | 174 ++++++++++++++++++ .../generators/generator-for-group.go | 2 + .../generators/generator-for-type.go | 2 + cmd/libs/go2idl/client-gen/main.go | 96 +++++++--- .../test_release_1_1/clientset.go | 66 +++++++ hack/verify-flags/known-flags.txt | 3 + .../release_1_1/clientset.go | 80 ++++++++ 9 files changed, 465 insertions(+), 26 deletions(-) create mode 100644 cmd/libs/go2idl/client-gen/generators/generator-for-clientset.go create mode 100644 cmd/libs/go2idl/client-gen/testoutput/clientset_generated/test_release_1_1/clientset.go create mode 100644 pkg/client/clientset_generated/release_1_1/clientset.go diff --git a/cmd/libs/go2idl/args/args.go b/cmd/libs/go2idl/args/args.go index e3c86b9418..61543edc89 100644 --- a/cmd/libs/go2idl/args/args.go +++ b/cmd/libs/go2idl/args/args.go @@ -45,7 +45,7 @@ func Default() *GeneratorArgs { return generatorArgs } -// GeneratorArgs has arguments common to most generators. +// GeneratorArgs has arguments that are passed to generators. type GeneratorArgs struct { // Which directories to parse. InputDirs []string @@ -61,6 +61,9 @@ type GeneratorArgs struct { // If true, only verify, don't write anything. VerifyOnly bool + + // Any custom arguments go here + CustomArgs interface{} } func (g *GeneratorArgs) AddFlags(fs *pflag.FlagSet) { diff --git a/cmd/libs/go2idl/client-gen/generators/client-generator.go b/cmd/libs/go2idl/client-gen/generators/client-generator.go index 1c7b9d0474..3b5ea73710 100644 --- a/cmd/libs/go2idl/client-gen/generators/client-generator.go +++ b/cmd/libs/go2idl/client-gen/generators/client-generator.go @@ -26,10 +26,29 @@ import ( "k8s.io/kubernetes/cmd/libs/go2idl/generator" "k8s.io/kubernetes/cmd/libs/go2idl/namer" "k8s.io/kubernetes/cmd/libs/go2idl/types" + "k8s.io/kubernetes/pkg/api/unversioned" "github.com/golang/glog" ) +// ClientGenArgs is a wrapper for arguments to client-gen. +type ClientGenArgs struct { + // TODO: we should make another type declaration of GroupVersion out of the + // unversioned package, which is part of our API. Tools like client-gen + // shouldn't depend on an API. + GroupVersions []unversioned.GroupVersion + // ClientsetName is the name of the clientset to be generated. It's + // populated from command-line arguments. + ClientsetName string + // ClientsetOutputPath is the path the clientset will be generated at. It's + // populated from command-line arguments. + ClientsetOutputPath string + // ClientsetOnly determines if we should generate the clients for groups and + // types along with the clientset. It's populated from command-line + // arguments. + ClientsetOnly bool +} + // NameSystems returns the name system used by the generators in this package. func NameSystems() namer.NameSystems { pluralExceptions := map[string]string{ @@ -110,6 +129,33 @@ func packageForGroup(group string, version string, typeList []*types.Type, packa } } +func packageForClientset(customArgs ClientGenArgs, typedClientBasePath string, boilerplate []byte) generator.Package { + return &generator.DefaultPackage{ + PackageName: customArgs.ClientsetName, + PackagePath: filepath.Join(customArgs.ClientsetOutputPath, customArgs.ClientsetName), + HeaderText: boilerplate, + PackageDocumentation: []byte( + `// This package has the automatically generated clientset. +`), + // GeneratorFunc returns a list of generators. Each generator generates a + // single file. + GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + &genClientset{ + DefaultGen: generator.DefaultGen{ + OptionalName: "clientset", + }, + groupVersions: customArgs.GroupVersions, + typedClientPath: typedClientBasePath, + outputPackage: customArgs.ClientsetName, + imports: generator.NewImportTracker(), + }, + } + return generators + }, + } +} + // Packages makes the client package definition. func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { boilerplate, err := arguments.LoadGoBoilerplate() @@ -136,11 +182,20 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat } } - var packageList []generator.Package - orderer := namer.Orderer{namer.NewPrivateNamer(0)} - for group, types := range groupToTypes { - packageList = append(packageList, packageForGroup(group, "unversioned", orderer.OrderTypes(types), arguments.OutputPackagePath, arguments.OutputBase, boilerplate)) + customArgs, ok := arguments.CustomArgs.(ClientGenArgs) + if !ok { + glog.Fatalf("cannot convert arguments.CustomArgs to ClientGenArgs") } + var packageList []generator.Package + // If --clientset-only=true, we don't regenerate the individual typed clients. + if !customArgs.ClientsetOnly { + orderer := namer.Orderer{namer.NewPrivateNamer(0)} + for group, types := range groupToTypes { + packageList = append(packageList, packageForGroup(group, "unversioned", orderer.OrderTypes(types), arguments.OutputPackagePath, arguments.OutputBase, boilerplate)) + } + } + + packageList = append(packageList, packageForClientset(customArgs, arguments.OutputPackagePath, boilerplate)) return generator.Packages(packageList) } diff --git a/cmd/libs/go2idl/client-gen/generators/generator-for-clientset.go b/cmd/libs/go2idl/client-gen/generators/generator-for-clientset.go new file mode 100644 index 0000000000..86e72f625a --- /dev/null +++ b/cmd/libs/go2idl/client-gen/generators/generator-for-clientset.go @@ -0,0 +1,174 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + + "k8s.io/kubernetes/cmd/libs/go2idl/generator" + "k8s.io/kubernetes/cmd/libs/go2idl/namer" + "k8s.io/kubernetes/cmd/libs/go2idl/types" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// genClientset generates a package for a clientset. +type genClientset struct { + generator.DefaultGen + groupVersions []unversioned.GroupVersion + typedClientPath string + outputPackage string + imports *generator.ImportTracker +} + +var _ generator.Generator = &genClientset{} + +func (g *genClientset) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +var generate_clientset = true + +// We only want to call GenerateType() once. +func (g *genClientset) Filter(c *generator.Context, t *types.Type) bool { + ret := generate_clientset + generate_clientset = false + return ret +} + +func normalizeGroup(group string) string { + if group == "api" { + return "legacy" + } + return group +} + +func normalizeVersion(version string) string { + if version == "" { + return "unversioned" + } + return version +} + +func (g *genClientset) Imports(c *generator.Context) (imports []string) { + for _, gv := range g.groupVersions { + group := normalizeGroup(gv.Group) + version := normalizeVersion(gv.Version) + typedClientPath := filepath.Join(g.typedClientPath, group, version) + imports = append(imports, g.imports.ImportLines()...) + imports = append(imports, fmt.Sprintf("%s_%s \"%s\"", group, version, typedClientPath)) + } + return +} + +func (g *genClientset) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + // TODO: We actually don't need any type information to generate the clientset, + // perhaps we can adapt the go2ild framework to this kind of usage. + sw := generator.NewSnippetWriter(w, c, "$", "$") + const pkgUnversioned = "k8s.io/kubernetes/pkg/client/unversioned" + + type arg struct { + Group string + PackageName string + } + + allGroups := []arg{} + for _, gv := range g.groupVersions { + group := normalizeGroup(gv.Group) + version := normalizeVersion(gv.Version) + allGroups = append(allGroups, arg{namer.IC(group), group + "_" + version}) + } + + m := map[string]interface{}{ + "allGroups": allGroups, + "Config": c.Universe.Type(types.Name{Package: pkgUnversioned, Name: "Config"}), + "DefaultKubernetesUserAgent": c.Universe.Function(types.Name{Package: pkgUnversioned, Name: "DefaultKubernetesUserAgent"}), + "RESTClient": c.Universe.Type(types.Name{Package: pkgUnversioned, Name: "RESTClient"}), + } + sw.Do(clientsetInterfaceTemplate, m) + sw.Do(clientsetTemplate, m) + for _, g := range allGroups { + sw.Do(clientsetInterfaceImplTemplate, g) + } + sw.Do(newClientsetForConfigTemplate, m) + sw.Do(newClientsetForConfigOrDieTemplate, m) + sw.Do(newClientsetForRESTClientTemplate, m) + + return sw.Error() +} + +var clientsetInterfaceTemplate = ` +type Interface interface { + $range .allGroups$$.Group$() $.PackageName$.$.Group$Client + $end$ +} +` + +var clientsetTemplate = ` +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + $range .allGroups$*$.PackageName$.$.Group$Client + $end$ +} +` + +var clientsetInterfaceImplTemplate = ` +// $.Group$ retrieves the $.Group$Client +func (c *Clientset) $.Group$() *$.PackageName$.$.Group$Client { + return c.$.Group$Client +} +` + +var newClientsetForConfigTemplate = ` +// NewForConfig creates a new Clientset for the given config. +func NewForConfig(c *$.Config|raw$) (*Clientset, error) { + var clientset Clientset + var err error +$range .allGroups$ clientset.$.Group$Client, err =$.PackageName$.NewForConfig(c) + if err!=nil { + return nil, err + } +$end$ + return &clientset, nil +} +` + +var newClientsetForConfigOrDieTemplate = ` +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *$.Config|raw$) *Clientset { + var clientset Clientset +$range .allGroups$ clientset.$.Group$Client =$.PackageName$.NewForConfigOrDie(c) +$end$ + return &clientset +} +` + +var newClientsetForRESTClientTemplate = ` +// New creates a new Clientset for the given RESTClient. +func New(c *$.RESTClient|raw$) *Clientset { + var clientset Clientset +$range .allGroups$ clientset.$.Group$Client =$.PackageName$.New(c) +$end$ + + return &clientset +} +` diff --git a/cmd/libs/go2idl/client-gen/generators/generator-for-group.go b/cmd/libs/go2idl/client-gen/generators/generator-for-group.go index a425c6631a..8b181752ee 100644 --- a/cmd/libs/go2idl/client-gen/generators/generator-for-group.go +++ b/cmd/libs/go2idl/client-gen/generators/generator-for-group.go @@ -34,6 +34,8 @@ type genGroup struct { imports *generator.ImportTracker } +var _ generator.Generator = &genGroup{} + // We only want to call GenerateType() once per group. func (g *genGroup) Filter(c *generator.Context, t *types.Type) bool { return t == g.types[0] diff --git a/cmd/libs/go2idl/client-gen/generators/generator-for-type.go b/cmd/libs/go2idl/client-gen/generators/generator-for-type.go index e83ffabbe8..ad77004404 100644 --- a/cmd/libs/go2idl/client-gen/generators/generator-for-type.go +++ b/cmd/libs/go2idl/client-gen/generators/generator-for-type.go @@ -34,6 +34,8 @@ type genClientForType struct { imports *generator.ImportTracker } +var _ generator.Generator = &genClientForType{} + // Filter ignores all but one type because we're making a single file per type. func (g *genClientForType) Filter(c *generator.Context, t *types.Type) bool { return t == g.typeToMatch } diff --git a/cmd/libs/go2idl/client-gen/main.go b/cmd/libs/go2idl/client-gen/main.go index 34b5c7a28a..130f53ee85 100644 --- a/cmd/libs/go2idl/client-gen/main.go +++ b/cmd/libs/go2idl/client-gen/main.go @@ -18,45 +18,99 @@ limitations under the License. package main import ( + "fmt" + "path/filepath" + "k8s.io/kubernetes/cmd/libs/go2idl/args" "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/generators" + "k8s.io/kubernetes/pkg/api/unversioned" "github.com/golang/glog" flag "github.com/spf13/pflag" ) -var test = flag.BoolP("test", "t", false, "set this flag to generate the client code for the testdata") +var ( + test = flag.BoolP("test", "t", false, "set this flag to generate the client code for the testdata") + inputVersions = flag.StringSlice("input", []string{"api/", "extensions/"}, "group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format \"group1/version1,group2/version2...\". Default to \"api/,extensions\"") + clientsetName = flag.StringP("clientset-name", "n", "release_1_1", "the name of the generated clientset package.") + clientsetPath = flag.String("clientset-path", "k8s.io/kubernetes/pkg/client/clientset_generated/", "the generated clientset will be output to /. Default to \"k8s.io/kubernetes/pkg/client/clientset_generated/\"") + clientsetOnly = flag.Bool("clientset-only", false, "when set, client-gen only generates the clientset shell, without generating the individual typed clients") +) + +func versionToPath(group string, version string) (path string) { + const base = "k8s.io/kubernetes/pkg" + // special case for the legacy group + if group == "api" { + path = filepath.Join(base, "api", version) + } else { + path = filepath.Join(base, "apis", group, version) + } + return +} + +func parseInputVersions() ([]string, []unversioned.GroupVersion, error) { + var visitedGroups = make(map[string]struct{}) + var groupVersions []unversioned.GroupVersion + var paths []string + for _, gvString := range *inputVersions { + gv, err := unversioned.ParseGroupVersion(gvString) + if err != nil { + return nil, nil, err + } + + if _, found := visitedGroups[gv.Group]; found { + return nil, nil, fmt.Errorf("group %q appeared more than once in the input. At most one version is allowed for each group.", gv.Group) + } + visitedGroups[gv.Group] = struct{}{} + groupVersions = append(groupVersions, gv) + paths = append(paths, versionToPath(gv.Group, gv.Version)) + } + return paths, groupVersions, nil +} func main() { arguments := args.Default() flag.Parse() + dependencies := []string{ + "k8s.io/kubernetes/pkg/fields", + "k8s.io/kubernetes/pkg/labels", + "k8s.io/kubernetes/pkg/watch", + "k8s.io/kubernetes/pkg/client/unversioned", + "k8s.io/kubernetes/pkg/api/latest", + } + if *test { - // Override defaults. These are Kubernetes specific input and output - // locations. - arguments.InputDirs = []string{ + arguments.InputDirs = append(dependencies, []string{ "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/testdata/apis/testgroup", - "k8s.io/kubernetes/pkg/fields", - "k8s.io/kubernetes/pkg/labels", - "k8s.io/kubernetes/pkg/watch", - "k8s.io/kubernetes/pkg/client/unversioned", - "k8s.io/kubernetes/pkg/api/latest", - } + }...) // We may change the output path later. arguments.OutputPackagePath = "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/testoutput" - } else { - // Override defaults. These are Kubernetes specific input and output - // locations. - arguments.InputDirs = []string{ - "k8s.io/kubernetes/pkg/api", - "k8s.io/kubernetes/pkg/apis/extensions", - "k8s.io/kubernetes/pkg/fields", - "k8s.io/kubernetes/pkg/labels", - "k8s.io/kubernetes/pkg/watch", - "k8s.io/kubernetes/pkg/client/unversioned", - "k8s.io/kubernetes/pkg/api/latest", + arguments.CustomArgs = generators.ClientGenArgs{ + []unversioned.GroupVersion{{"testgroup", ""}}, + "test_release_1_1", + "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/testoutput/clientset_generated/", + false, } + } else { + inputPath, groupVersions, err := parseInputVersions() + if err != nil { + glog.Fatalf("Error: %v", err) + } + glog.Info("going to generate clientset from these input paths: %v", inputPath) + arguments.InputDirs = append(inputPath, dependencies...) + // TODO: we need to make OutPackagePath a map[string]string. For example, + // we need clientset and the individual typed clients be output to different + // output path. + // We may change the output path later. arguments.OutputPackagePath = "k8s.io/kubernetes/pkg/client/typed/generated" + + arguments.CustomArgs = generators.ClientGenArgs{ + groupVersions, + *clientsetName, + *clientsetPath, + *clientsetOnly, + } } if err := arguments.Execute( diff --git a/cmd/libs/go2idl/client-gen/testoutput/clientset_generated/test_release_1_1/clientset.go b/cmd/libs/go2idl/client-gen/testoutput/clientset_generated/test_release_1_1/clientset.go new file mode 100644 index 0000000000..5b4019df46 --- /dev/null +++ b/cmd/libs/go2idl/client-gen/testoutput/clientset_generated/test_release_1_1/clientset.go @@ -0,0 +1,66 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 test_release_1_1 + +import ( + testgroup_unversioned "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/testoutput/testgroup/unversioned" + unversioned "k8s.io/kubernetes/pkg/client/unversioned" +) + +type Interface interface { + Testgroup() testgroup_unversioned.TestgroupClient +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *testgroup_unversioned.TestgroupClient +} + +// Testgroup retrieves the TestgroupClient +func (c *Clientset) Testgroup() *testgroup_unversioned.TestgroupClient { + return c.TestgroupClient +} + +// NewForConfig creates a new Clientset for the given config. +func NewForConfig(c *unversioned.Config) (*Clientset, error) { + var clientset Clientset + var err error + clientset.TestgroupClient, err = testgroup_unversioned.NewForConfig(c) + if err != nil { + return nil, err + } + + return &clientset, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *unversioned.Config) *Clientset { + var clientset Clientset + clientset.TestgroupClient = testgroup_unversioned.NewForConfigOrDie(c) + + return &clientset +} + +// New creates a new Clientset for the given RESTClient. +func New(c *unversioned.RESTClient) *Clientset { + var clientset Clientset + clientset.TestgroupClient = testgroup_unversioned.New(c) + + return &clientset +} diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index e0753e2bda..3f23c22ad8 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -354,3 +354,6 @@ watch-only whitelist-override-label windows-line-endings www-prefix +clientset-name +clientset-only +clientset-path diff --git a/pkg/client/clientset_generated/release_1_1/clientset.go b/pkg/client/clientset_generated/release_1_1/clientset.go new file mode 100644 index 0000000000..022a13ac31 --- /dev/null +++ b/pkg/client/clientset_generated/release_1_1/clientset.go @@ -0,0 +1,80 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 release_1_1 + +import ( + extensions_unversioned "k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned" + legacy_unversioned "k8s.io/kubernetes/pkg/client/typed/generated/legacy/unversioned" + unversioned "k8s.io/kubernetes/pkg/client/unversioned" +) + +type Interface interface { + Legacy() legacy_unversioned.LegacyClient + Extensions() extensions_unversioned.ExtensionsClient +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *legacy_unversioned.LegacyClient + *extensions_unversioned.ExtensionsClient +} + +// Legacy retrieves the LegacyClient +func (c *Clientset) Legacy() *legacy_unversioned.LegacyClient { + return c.LegacyClient +} + +// Extensions retrieves the ExtensionsClient +func (c *Clientset) Extensions() *extensions_unversioned.ExtensionsClient { + return c.ExtensionsClient +} + +// NewForConfig creates a new Clientset for the given config. +func NewForConfig(c *unversioned.Config) (*Clientset, error) { + var clientset Clientset + var err error + clientset.LegacyClient, err = legacy_unversioned.NewForConfig(c) + if err != nil { + return nil, err + } + clientset.ExtensionsClient, err = extensions_unversioned.NewForConfig(c) + if err != nil { + return nil, err + } + + return &clientset, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *unversioned.Config) *Clientset { + var clientset Clientset + clientset.LegacyClient = legacy_unversioned.NewForConfigOrDie(c) + clientset.ExtensionsClient = extensions_unversioned.NewForConfigOrDie(c) + + return &clientset +} + +// New creates a new Clientset for the given RESTClient. +func New(c *unversioned.RESTClient) *Clientset { + var clientset Clientset + clientset.LegacyClient = legacy_unversioned.New(c) + clientset.ExtensionsClient = extensions_unversioned.New(c) + + return &clientset +}