From e22f31c5eaaef9b5900f8d4014aa2fdfe7f2c19c Mon Sep 17 00:00:00 2001 From: Andy Goldstein Date: Fri, 13 May 2022 14:04:46 -0400 Subject: [PATCH 1/2] wip Signed-off-by: Andy Goldstein --- main.go | 13 +++- pkg/generators/clientgen/gen.go | 111 ++++++++++++++++++++++++++++---- pkg/internal/parser.go | 59 ++++++++++++----- 3 files changed, 151 insertions(+), 32 deletions(-) diff --git a/main.go b/main.go index 103a28a89..635151844 100644 --- a/main.go +++ b/main.go @@ -17,11 +17,13 @@ limitations under the License. package main import ( + goflags "flag" "fmt" "os" "strings" "github.com/spf13/cobra" + "k8s.io/klog/v2" "sigs.k8s.io/controller-tools/pkg/genall" "sigs.k8s.io/controller-tools/pkg/markers" @@ -39,9 +41,10 @@ var ( func main() { f := &flag.Flags{} cmd := &cobra.Command{ - Use: "code-gen", - Short: "Generate cluster-aware kcp wrappers around clients, listers and informers.", - Long: "Generate cluster-aware kcp wrappers around clients, listers and informers.", + Use: "code-gen", + SilenceUsage: true, + Short: "Generate cluster-aware kcp wrappers around clients, listers and informers.", + Long: "Generate cluster-aware kcp wrappers around clients, listers and informers.", Example: `Generate cluster-aware kcp clients from existing code scaffolded by k8.io/code-gen. For example: # To generate client wrappers: @@ -94,6 +97,10 @@ func main() { }, } + fs := goflags.NewFlagSet("klog", goflags.PanicOnError) + klog.InitFlags(fs) + cmd.Flags().AddGoFlagSet(fs) + f.AddTo(cmd.Flags()) if err := cmd.Execute(); err != nil { diff --git a/pkg/generators/clientgen/gen.go b/pkg/generators/clientgen/gen.go index 590799a08..c486ed01d 100644 --- a/pkg/generators/clientgen/gen.go +++ b/pkg/generators/clientgen/gen.go @@ -40,12 +40,15 @@ import ( ) var ( - // RuleDefinition is a marker for defining rules - ruleDefinition = markers.Must(markers.MakeDefinition("genclient", markers.DescribesType, placeholder{})) - // nonNamespacedMarker checks if resource is namespaced or clusterscoped - nonNamespacedMarker = markers.Must(markers.MakeDefinition("genclient:nonNamespaced", markers.DescribesType, placeholder{})) - // noStatusMarker checks if status is to scaffolded - noStatusMarker = markers.Must(markers.MakeDefinition("+genclient:noStatus", markers.DescribesType, placeholder{})) + genclientMarker = markers.Must(markers.MakeDefinition("genclient", markers.DescribesType, genclient{})) + + nonNamespacedMarker = markers.Must(markers.MakeDefinition("genclient:nonNamespaced", markers.DescribesType, struct{}{})) + + noStatusMarker = markers.Must(markers.MakeDefinition("genclient:noStatus", markers.DescribesType, struct{}{})) + + noVerbsMarker = markers.Must(markers.MakeDefinition("genclient:noVerbs", markers.DescribesType, struct{}{})) + + readOnlyMarker = markers.Must(markers.MakeDefinition("genclient:readOnly", markers.DescribesType, struct{}{})) ) const ( @@ -59,9 +62,19 @@ const ( extensionGo = ".go" ) -// Assigning marker's output to a placeholder struct, to verify to -// typecast the result and make sure if it exists for the type. -type placeholder struct{} +type genclient struct { + Method *string + Verb *[]string + Subresource *string + Input *string + Result *string + + // ReadOnly bool + + OnlyVerbs *[]string + + SkipVerbs *[]string +} type Generator struct { // inputDir is the path where types are defined. @@ -97,7 +110,14 @@ type pkgPaths struct { func (g Generator) RegisterMarker() (*markers.Registry, error) { reg := &markers.Registry{} - if err := markers.RegisterAll(reg, ruleDefinition, nonNamespacedMarker, noStatusMarker); err != nil { + if err := markers.RegisterAll( + reg, + genclientMarker, + nonNamespacedMarker, + noStatusMarker, + noVerbsMarker, + readOnlyMarker, + ); err != nil { return nil, fmt.Errorf("error registering markers") } return reg, nil @@ -376,7 +396,74 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { return } - a, err := internal.NewAPI(root, info, string(version.Version), gv.PackageName, !isClusterScoped(info), hasStatusSubresource(info), &outContent) + /* + This is the current list of unique genclient markers as of 1.23 + + // +genclient + // +genclient:method=ApplyScale,verb=apply,subresource=scale,input=Scale,result=Scale + // +genclient:method=ApplyScale,verb=apply,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale + // +genclient:method=CreateScale,verb=create,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale + // +genclient:method=CreateToken,verb=create,subresource=token,input=k8s.io/api/authentication/v1.TokenRequest,result=k8s.io/api/authentication/v1.TokenRequest + // +genclient:method=GetScale,verb=get,subresource=scale,result=Scale + // +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/api/autoscaling/v1.Scale + // +genclient:method=UpdateApproval,verb=update,subresource=approval,input=k8s.io/api/certificates/v1.CertificateSigningRequest,result=k8s.io/api/certificates/v1.CertificateSigningRequest + // +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers + // +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale + // +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale + // +genclient:noStatus + // +genclient:nonNamespaced + // +genclient:noVerbs + // +genclient:onlyVerbs=create + // +genclient:onlyVerbs=create,delete + // +genclient:readonly + // +genclient:skipVerbs=deleteCollection + // +genclient:skipVerbs=get,update + */ + + var additionalMethods []internal.AdditionalMethod + var skipVerbs []string + var onlyVerbs []string + + genclientMarkers := info.Markers[genclientMarker.Name] + for _, m := range genclientMarkers { + gm := m.(genclient) + + if gm.Method != nil { + additionalMethods = append(additionalMethods, internal.AdditionalMethod{ + Method: gm.Method, + Verb: gm.Verb, + Subresource: gm.Subresource, + Input: gm.Input, + Result: gm.Result, + }) + } + + if gm.SkipVerbs != nil { + skipVerbs = *gm.SkipVerbs + } + + if gm.OnlyVerbs != nil { + onlyVerbs = *gm.OnlyVerbs + } + } + + namespaceScoped := info.Markers.Get(nonNamespacedMarker.Name) == nil + noVerbs := info.Markers.Get(noVerbsMarker.Name) != nil + hasStatus := info.Markers.Get(noStatusMarker.Name) == nil + + a, err := internal.NewAPI( + root, + info, + gv.PackageName, + string(version.Version), + namespaceScoped, + additionalMethods, + skipVerbs, + onlyVerbs, + noVerbs, + hasStatus, + &outContent, + ) if err != nil { root.AddError(err) return @@ -429,7 +516,7 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { // isEnabledForMethod verifies if the genclient marker is enabled for // this type or not. func isEnabledForMethod(info *markers.TypeInfo) bool { - enabled := info.Markers.Get(ruleDefinition.Name) + enabled := info.Markers.Get(genclientMarker.Name) return enabled != nil } diff --git a/pkg/internal/parser.go b/pkg/internal/parser.go index fc4e70e8e..14efbec48 100644 --- a/pkg/internal/parser.go +++ b/pkg/internal/parser.go @@ -58,16 +58,6 @@ type interfaceWrapper struct { writer *io.Writer } -// api contains info about each type -type api struct { - Name string - Version string - PkgName string - writer io.Writer - IsNamespaced bool - HasStatus bool -} - // packages stores the info used to scaffold wrapped interfaces content type packages struct { Name string @@ -152,19 +142,54 @@ func (p *packages) WriteContent() error { return templ.Execute(p.writer, p) } -func NewAPI(root *loader.Package, info *markers.TypeInfo, version, group string, isNamespaced bool, hasStatus bool, w io.Writer) (*api, error) { +// api contains info about each type +type api struct { + Name string + PkgName string + Version string + IsNamespaced bool + AdditionalMethods []AdditionalMethod + SkipVerbs []string + OnlyVerbs []string + NoVerbs bool + HasStatus bool + writer io.Writer +} + +type AdditionalMethod struct { + Method *string + Verb *[]string + Subresource *string + Input *string + Result *string +} + +func NewAPI( + root *loader.Package, + info *markers.TypeInfo, + group, version string, + namespaceScoped bool, + additionalMethods []AdditionalMethod, + skipVerbs, onlyVerbs []string, + noVerbs, hasStatus bool, + w io.Writer, +) (*api, error) { typeInfo := root.TypesInfo.TypeOf(info.RawSpec.Name) if typeInfo == types.Typ[types.Invalid] { return nil, fmt.Errorf("unknown type: %s", info.Name) } api := &api{ - Name: info.RawSpec.Name.Name, - Version: version, - PkgName: group, - writer: w, - IsNamespaced: isNamespaced, - HasStatus: hasStatus, + Name: info.RawSpec.Name.Name, + PkgName: group, + Version: version, + IsNamespaced: namespaceScoped, + AdditionalMethods: additionalMethods, + SkipVerbs: skipVerbs, + OnlyVerbs: onlyVerbs, + NoVerbs: noVerbs, + HasStatus: hasStatus, + writer: w, } return api, nil } From c64c58b6832b719961d31bfd3ad68d4831470400 Mon Sep 17 00:00:00 2001 From: varshaprasad96 Date: Tue, 17 May 2022 15:55:12 -0700 Subject: [PATCH 2/2] This commit adds support to all the `//+genclient` markers as specified by upstream k8s code-generator. Signed-off-by: varshaprasad96 --- examples/pkg/apis/example/v1/types.go | 1 + .../typed/example/v1/examplev1.go | 109 ++++---- go.mod | 2 +- main.go | 4 +- pkg/flag/flags.go | 5 +- pkg/generators/clientgen/gen.go | 196 +++++++++---- pkg/internal/parser.go | 257 ++++++++++++++++-- pkg/internal/templates.go | 128 +++++---- pkg/util/util.go | 48 ++++ 9 files changed, 569 insertions(+), 181 deletions(-) diff --git a/examples/pkg/apis/example/v1/types.go b/examples/pkg/apis/example/v1/types.go index 726bb0013..d3651de31 100644 --- a/examples/pkg/apis/example/v1/types.go +++ b/examples/pkg/apis/example/v1/types.go @@ -19,6 +19,7 @@ package v1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // +genclient +// +genclient:noStatus // TestType is a top-level type. A client is created for it. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type TestType struct { diff --git a/examples/pkg/clusterclient/typed/example/v1/examplev1.go b/examples/pkg/clusterclient/typed/example/v1/examplev1.go index 5af20373c..d0c1ab4ef 100644 --- a/examples/pkg/clusterclient/typed/example/v1/examplev1.go +++ b/examples/pkg/clusterclient/typed/example/v1/examplev1.go @@ -24,10 +24,9 @@ package v1 import ( "context" "fmt" + kcp "github.com/kcp-dev/apimachinery/pkg/client" exampleapiv1 "github.com/kcp-dev/code-generator/examples/pkg/apis/example/v1" examplev1 "github.com/kcp-dev/code-generator/examples/pkg/generated/clientset/versioned/typed/example/v1" - - kcp "github.com/kcp-dev/apimachinery/pkg/client" "github.com/kcp-dev/logicalcluster" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -78,76 +77,76 @@ func (w *wrappedClusterTestType) checkCluster(ctx context.Context) (context.Cont return ctx, nil } -// Create implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) Create(ctx context.Context, clusterTestType *exampleapiv1.ClusterTestType, opts metav1.CreateOptions) (*exampleapiv1.ClusterTestType, error) { +// Get implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) Get(ctx context.Context, name string, opts metav1.GetOptions) (*exampleapiv1.ClusterTestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Create(ctx, clusterTestType, opts) + return w.delegate.Get(ctx, name, opts) } -// Update implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) Update(ctx context.Context, clusterTestType *exampleapiv1.ClusterTestType, opts metav1.UpdateOptions) (*exampleapiv1.ClusterTestType, error) { +// List implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) List(ctx context.Context, opts metav1.ListOptions) (*exampleapiv1.ClusterTestTypeList, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Update(ctx, clusterTestType, opts) + return w.delegate.List(ctx, opts) } -// UpdateStatus implements ClusterTestTypeInterface. It was generated because the type contains a Status member. -func (w *wrappedClusterTestType) UpdateStatus(ctx context.Context, clusterTestType *exampleapiv1.ClusterTestType, opts metav1.UpdateOptions) (*exampleapiv1.ClusterTestType, error) { +// Watch implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.UpdateStatus(ctx, clusterTestType, opts) + return w.delegate.Watch(ctx, opts) } -// Update implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { +// Create implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) Create(ctx context.Context, clusterTestType *exampleapiv1.ClusterTestType, opts metav1.CreateOptions) (*exampleapiv1.ClusterTestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { - return err + return nil, err } - return w.delegate.Delete(ctx, name, opts) + return w.delegate.Create(ctx, clusterTestType, opts) } -// DeleteCollection implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listopts metav1.ListOptions) error { +// Update implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) Update(ctx context.Context, clusterTestType *exampleapiv1.ClusterTestType, opts metav1.UpdateOptions) (*exampleapiv1.ClusterTestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { - return err + return nil, err } - return w.delegate.DeleteCollection(ctx, opts, listopts) + return w.delegate.Update(ctx, clusterTestType, opts) } -// Get implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) Get(ctx context.Context, name string, opts metav1.GetOptions) (*exampleapiv1.ClusterTestType, error) { +// UpdateStatus implements ClusterTestTypeInterface. It was generated because the type contains a Status member. +func (w *wrappedClusterTestType) UpdateStatus(ctx context.Context, clusterTestType *exampleapiv1.ClusterTestType, opts metav1.UpdateOptions) (*exampleapiv1.ClusterTestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Get(ctx, name, opts) + return w.delegate.UpdateStatus(ctx, clusterTestType, opts) } -// List implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) List(ctx context.Context, opts metav1.ListOptions) (*exampleapiv1.ClusterTestTypeList, error) { +// Delete implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { ctx, err := w.checkCluster(ctx) if err != nil { - return nil, err + return err } - return w.delegate.List(ctx, opts) + return w.delegate.Delete(ctx, name, opts) } -// Watch implements ClusterTestTypeInterface. -func (w *wrappedClusterTestType) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { +// DeleteCollection implements ClusterTestTypeInterface. +func (w *wrappedClusterTestType) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listopts metav1.ListOptions) error { ctx, err := w.checkCluster(ctx) if err != nil { - return nil, err + return err } - return w.delegate.Watch(ctx, opts) + return w.delegate.DeleteCollection(ctx, opts, listopts) } // Patch implements ClusterTestTypeInterface. @@ -185,67 +184,67 @@ func (w *wrappedTestType) checkCluster(ctx context.Context) (context.Context, er return ctx, nil } -// Create implements TestTypeInterface. -func (w *wrappedTestType) Create(ctx context.Context, testType *exampleapiv1.TestType, opts metav1.CreateOptions) (*exampleapiv1.TestType, error) { +// Get implements TestTypeInterface. +func (w *wrappedTestType) Get(ctx context.Context, name string, opts metav1.GetOptions) (*exampleapiv1.TestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Create(ctx, testType, opts) + return w.delegate.Get(ctx, name, opts) } -// Update implements TestTypeInterface. -func (w *wrappedTestType) Update(ctx context.Context, testType *exampleapiv1.TestType, opts metav1.UpdateOptions) (*exampleapiv1.TestType, error) { +// List implements TestTypeInterface. +func (w *wrappedTestType) List(ctx context.Context, opts metav1.ListOptions) (*exampleapiv1.TestTypeList, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Update(ctx, testType, opts) + return w.delegate.List(ctx, opts) } -// Update implements TestTypeInterface. -func (w *wrappedTestType) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { +// Watch implements TestTypeInterface. +func (w *wrappedTestType) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { ctx, err := w.checkCluster(ctx) if err != nil { - return err + return nil, err } - return w.delegate.Delete(ctx, name, opts) + return w.delegate.Watch(ctx, opts) } -// DeleteCollection implements TestTypeInterface. -func (w *wrappedTestType) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listopts metav1.ListOptions) error { +// Create implements TestTypeInterface. +func (w *wrappedTestType) Create(ctx context.Context, testType *exampleapiv1.TestType, opts metav1.CreateOptions) (*exampleapiv1.TestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { - return err + return nil, err } - return w.delegate.DeleteCollection(ctx, opts, listopts) + return w.delegate.Create(ctx, testType, opts) } -// Get implements TestTypeInterface. -func (w *wrappedTestType) Get(ctx context.Context, name string, opts metav1.GetOptions) (*exampleapiv1.TestType, error) { +// Update implements TestTypeInterface. +func (w *wrappedTestType) Update(ctx context.Context, testType *exampleapiv1.TestType, opts metav1.UpdateOptions) (*exampleapiv1.TestType, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Get(ctx, name, opts) + return w.delegate.Update(ctx, testType, opts) } -// List implements TestTypeInterface. -func (w *wrappedTestType) List(ctx context.Context, opts metav1.ListOptions) (*exampleapiv1.TestTypeList, error) { +// Delete implements TestTypeInterface. +func (w *wrappedTestType) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { ctx, err := w.checkCluster(ctx) if err != nil { - return nil, err + return err } - return w.delegate.List(ctx, opts) + return w.delegate.Delete(ctx, name, opts) } -// Watch implements TestTypeInterface. -func (w *wrappedTestType) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { +// DeleteCollection implements TestTypeInterface. +func (w *wrappedTestType) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listopts metav1.ListOptions) error { ctx, err := w.checkCluster(ctx) if err != nil { - return nil, err + return err } - return w.delegate.Watch(ctx, opts) + return w.delegate.DeleteCollection(ctx, opts, listopts) } // Patch implements TestTypeInterface. diff --git a/go.mod b/go.mod index 937724749..5b13f8fb2 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( k8s.io/apimachinery v0.23.5 k8s.io/client-go v0.23.5 k8s.io/code-generator v0.23.0 + k8s.io/klog/v2 v2.60.1 sigs.k8s.io/controller-tools v0.8.0 ) @@ -49,7 +50,6 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/api v0.23.5 // indirect k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect - k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect diff --git a/main.go b/main.go index 635151844..fd3efd7db 100644 --- a/main.go +++ b/main.go @@ -43,8 +43,8 @@ func main() { cmd := &cobra.Command{ Use: "code-gen", SilenceUsage: true, - Short: "Generate cluster-aware kcp wrappers around clients, listers and informers.", - Long: "Generate cluster-aware kcp wrappers around clients, listers and informers.", + Short: "Generate cluster-aware kcp wrappers around clients, listers, and informers.", + Long: "Generate cluster-aware kcp wrappers around clients, listers, and informers.", Example: `Generate cluster-aware kcp clients from existing code scaffolded by k8.io/code-gen. For example: # To generate client wrappers: diff --git a/pkg/flag/flags.go b/pkg/flag/flags.go index eb25f072c..9f0e29f3e 100644 --- a/pkg/flag/flags.go +++ b/pkg/flag/flags.go @@ -28,6 +28,9 @@ type Flags struct { InputDir string // ClientsetAPIPath is the path to where client sets are scaffolded by codegen. ClientsetAPIPath string + // optional package of apply configurations, generated by applyconfiguration-gen, that are required to generate Apply functions for each type in the clientset. + // By default Apply functions are not generated. If this is provided, then wrappers for Apply functions will be generated. + ApplyConfigurationPackage string // List of group versions for which the wrappers are to be generated. GroupVersions []string // Path to the headerfile. @@ -41,7 +44,7 @@ func (f *Flags) AddTo(flagset *pflag.FlagSet) { flagset.StringVar(&f.InputDir, "input-dir", "", "Input directory where types are defined. It is assumed that 'types.go' is present inside /pkg/apis.") flagset.StringVar(&f.OutputDir, "output-dir", "output", "Output directory where wrapped clients will be generated. The wrappers will be present in '/generated' path.") flagset.StringVar(&f.ClientsetAPIPath, "clientset-api-path", "/apis", "package path where clients are generated.") - + flagset.StringVar(&f.ApplyConfigurationPackage, "apply-configuration-package", "", "optional package of apply configurations, generated by applyconfiguration-gen, that are required to generate Apply functions for each type in the clientset. If this is provided, then wrappers for Apply functions will be generated.") flagset.StringArrayVar(&f.GroupVersions, "group-versions", []string{}, "specify group versions for the clients.") flagset.StringVar(&f.GoHeaderFilePath, "go-header-file", "", "path to headerfile for the generated text.") flagset.StringVar(&f.ClientsetName, "clientset-name", "clientset", "the name of the generated clientset package.") diff --git a/pkg/generators/clientgen/gen.go b/pkg/generators/clientgen/gen.go index c486ed01d..3de7e5cd4 100644 --- a/pkg/generators/clientgen/gen.go +++ b/pkg/generators/clientgen/gen.go @@ -37,18 +37,23 @@ import ( "github.com/kcp-dev/code-generator/pkg/flag" "github.com/kcp-dev/code-generator/pkg/internal" "github.com/kcp-dev/code-generator/pkg/util" + genutil "k8s.io/code-generator/cmd/client-gen/generators/util" ) var ( genclientMarker = markers.Must(markers.MakeDefinition("genclient", markers.DescribesType, genclient{})) + // In controller-tool's terms marker's are defined in the following format: :=. These + // markers are not a part of genclient, since they do not accept any values. nonNamespacedMarker = markers.Must(markers.MakeDefinition("genclient:nonNamespaced", markers.DescribesType, struct{}{})) - - noStatusMarker = markers.Must(markers.MakeDefinition("genclient:noStatus", markers.DescribesType, struct{}{})) - - noVerbsMarker = markers.Must(markers.MakeDefinition("genclient:noVerbs", markers.DescribesType, struct{}{})) - - readOnlyMarker = markers.Must(markers.MakeDefinition("genclient:readOnly", markers.DescribesType, struct{}{})) + noStatusMarker = markers.Must(markers.MakeDefinition("genclient:noStatus", markers.DescribesType, struct{}{})) + noVerbsMarker = markers.Must(markers.MakeDefinition("genclient:noVerbs", markers.DescribesType, struct{}{})) + readOnlyMarker = markers.Must(markers.MakeDefinition("genclient:readonly", markers.DescribesType, struct{}{})) + + // These markers, are not a part of "+genclient", and are defined separately because they accept a list which is comma separated. In + // controller-tools, comma indicates another argument, as multiple arguments need to provided with a semi-colon separator. + skipVerbsMarker = markers.Must(markers.MakeDefinition("genclient:skipVerbs", markers.DescribesType, markers.RawArguments(""))) + onlyVerbsMarker = markers.Must(markers.MakeDefinition("genclient:onlyVerbs", markers.DescribesType, markers.RawArguments(""))) ) const ( @@ -64,16 +69,10 @@ const ( type genclient struct { Method *string - Verb *[]string + Verb *string Subresource *string Input *string Result *string - - // ReadOnly bool - - OnlyVerbs *[]string - - SkipVerbs *[]string } type Generator struct { @@ -87,6 +86,8 @@ type Generator struct { outputDir string // path to where generated clientsets are found. clientSetAPIPath string + // path to optional apply-config-gen packages. + applyConfigGenPkg string // clientsetName is the name of the generated clientset package. clientsetName string // GroupVersions for whom the clients are to be generated. @@ -117,6 +118,8 @@ func (g Generator) RegisterMarker() (*markers.Registry, error) { noStatusMarker, noVerbsMarker, readOnlyMarker, + skipVerbsMarker, + onlyVerbsMarker, ); err != nil { return nil, fmt.Errorf("error registering markers") } @@ -201,6 +204,9 @@ func (g *Generator) setDefaults(f flag.Flags) (err error) { if f.ClientsetName != "" { g.clientsetName = f.ClientsetName } + if f.ApplyConfigurationPackage != "" { + g.applyConfigGenPkg = f.ApplyConfigurationPackage + } g.headerText, err = getHeaderText(f.GoHeaderFilePath) if err != nil { return err @@ -377,16 +383,14 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { // this is to accomodate multiple types defined in single group byType := make(map[string][]byte) + importList := make([]string, 0) + var outCommonContent bytes.Buffer pkgmg := internal.NewPackages(root, path, g.clientSetAPIPath, string(version.Version), gv.PackageName, &outCommonContent) if err := g.writeHeader(&outCommonContent); err != nil { root.AddError(err) } - err = pkgmg.WriteContent() - if err != nil { - root.AddError(err) - } if eachTypeErr := markers.EachType(ctx.Collector, root, func(info *markers.TypeInfo) { var outContent bytes.Buffer @@ -425,39 +429,96 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { var onlyVerbs []string genclientMarkers := info.Markers[genclientMarker.Name] + + namespaceScoped := info.Markers.Get(nonNamespacedMarker.Name) == nil + noVerbs := info.Markers.Get(noVerbsMarker.Name) != nil + hasStatus := hasStatusSubresource(info) + readOnly := info.Markers.Get(readOnlyMarker.Name) != nil + + // Extract values from skip verbs marker. + sVerbs := info.Markers.Get(skipVerbsMarker.Name) + if sVerbs != nil { + val, ok := sVerbs.(markers.RawArguments) + if !ok { + root.AddError(fmt.Errorf("marker defined in wrong format %q", skipVerbsMarker.Name)) + } + skipVerbs = strings.Split(string(val), ",") + } + + // Extract values from only verbs marker. + oVerbs := info.Markers.Get(onlyVerbsMarker.Name) + if oVerbs != nil { + val, ok := oVerbs.(markers.RawArguments) + if !ok { + root.AddError(fmt.Errorf("marker defined in wrong format %q", onlyVerbsMarker.Name)) + } + onlyVerbs = append(onlyVerbs, strings.Split(string(val), ",")...) + } + + if readOnly { + onlyVerbs = append(onlyVerbs, genutil.ReadonlyVerbs...) + } + for _, m := range genclientMarkers { - gm := m.(genclient) + gm, ok := m.(genclient) + if !ok { + root.AddError(fmt.Errorf("marker defined in wrong format %q", genclientMarker.Name)) + } if gm.Method != nil { - additionalMethods = append(additionalMethods, internal.AdditionalMethod{ + additionalMethod := internal.AdditionalMethod{ Method: gm.Method, Verb: gm.Verb, Subresource: gm.Subresource, Input: gm.Input, Result: gm.Result, - }) - } + } - if gm.SkipVerbs != nil { - skipVerbs = *gm.SkipVerbs + if err := verifyAdditionalMethod(additionalMethod); err != nil { + root.AddError(err) + } + additionalMethods = append(additionalMethods, additionalMethod) } - if gm.OnlyVerbs != nil { - onlyVerbs = *gm.OnlyVerbs + // parse onlyVerbs marker + if len(onlyVerbs) > 0 { + onlyVerbsSet := make(map[string]bool) + for _, o := range onlyVerbs { + onlyVerbsSet[o] = true + } + + // Combine the verbs in only Verbs and skip Verbs list. + skippedverbsList := []string{} + for _, m := range genutil.SupportedVerbs { + skip := true + if _, ok := onlyVerbsSet[m]; ok { + skip = false + } + + // Check for conflits if a verb is present in onlyVerb and also in a SkipVerb marker. + for _, v := range skippedverbsList { + if v == m { + root.AddError(fmt.Errorf("verb %q used both in genclient:skipVerbs and genclient:onlyVerbs", v)) + } + } + + if skip { + skippedverbsList = append(skippedverbsList, m) + } + } + skipVerbs = skippedverbsList } } - namespaceScoped := info.Markers.Get(nonNamespacedMarker.Name) == nil - noVerbs := info.Markers.Get(noVerbsMarker.Name) != nil - hasStatus := info.Markers.Get(noStatusMarker.Name) == nil - a, err := internal.NewAPI( root, info, gv.PackageName, string(version.Version), + g.applyConfigGenPkg, namespaceScoped, additionalMethods, + &importList, skipVerbs, onlyVerbs, noVerbs, @@ -487,6 +548,11 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { return nil } + err = pkgmg.WriteContent(&importList) + if err != nil { + root.AddError(err) + } + var outContent bytes.Buffer outContent.Write(outCommonContent.Bytes()) err = writeMethods(&outContent, byType) @@ -513,6 +579,43 @@ func (g *Generator) generateSubInterfaces(ctx *genall.GenerationContext) error { return nil } +func verifyAdditionalMethod(m internal.AdditionalMethod) error { + if m.Verb == nil { + return fmt.Errorf("verb type must be specified (use '// +genclient:method=%s,verb=create')", *m.Method) + } + if m.Result != nil { + supported := false + for _, v := range util.ResultTypeSupportedVerbs { + if *m.Verb == v { + supported = true + break + } + } + if !supported { + return fmt.Errorf("%s: result type is not supported for %q verbs (supported verbs: %#v)", *m.Method, *m.Verb, util.ResultTypeSupportedVerbs) + } + } + + if m.Input != nil { + supported := false + for _, v := range util.InputTypeSupportedVerbs { + if *m.Verb == v { + supported = true + break + } + } + if !supported { + return fmt.Errorf("%s: input type is not supported for %q verbs (supported verbs: %#v)", *m.Method, *m.Verb, util.InputTypeSupportedVerbs) + } + } + for _, t := range util.UnsupportedExtensionVerbs { + if *m.Verb == t { + return fmt.Errorf("verb %q is not supported by extension generator", *m.Verb) + } + } + return nil +} + // isEnabledForMethod verifies if the genclient marker is enabled for // this type or not. func isEnabledForMethod(info *markers.TypeInfo) bool { @@ -520,11 +623,20 @@ func isEnabledForMethod(info *markers.TypeInfo) bool { return enabled != nil } -// isClusterScoped verifies if the genclient marker for this -// type is namespaced or clusterscoped. -func isClusterScoped(info *markers.TypeInfo) bool { - enabled := info.Markers.Get(nonNamespacedMarker.Name) - return enabled != nil +func writeMethods(out io.Writer, byType map[string][]byte) error { + sortedNames := make([]string, 0, len(byType)) + for name := range byType { + sortedNames = append(sortedNames, name) + } + sort.Strings(sortedNames) + + for _, name := range sortedNames { + _, err := out.Write(byType[name]) + if err != nil { + return err + } + } + return nil } // hasStatusSubresource verifies if updateStatus verb is to be scaffolded. @@ -544,19 +656,3 @@ func hasStatusSubresource(info *markers.TypeInfo) bool { } return hasStatusField } - -func writeMethods(out io.Writer, byType map[string][]byte) error { - sortedNames := make([]string, 0, len(byType)) - for name := range byType { - sortedNames = append(sortedNames, name) - } - sort.Strings(sortedNames) - - for _, name := range sortedNames { - _, err := out.Write(byType[name]) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/internal/parser.go b/pkg/internal/parser.go index 14efbec48..432e8d11c 100644 --- a/pkg/internal/parser.go +++ b/pkg/internal/parser.go @@ -24,6 +24,9 @@ import ( "strings" "text/template" + codegenutil "k8s.io/code-generator/cmd/client-gen/generators/util" + + "github.com/kcp-dev/code-generator/pkg/util" gentype "k8s.io/code-generator/cmd/client-gen/types" "sigs.k8s.io/controller-tools/pkg/loader" "sigs.k8s.io/controller-tools/pkg/markers" @@ -38,6 +41,10 @@ var funcMap = template.FuncMap{ "lowerFirst": func(s string) string { return strings.ToLower(string(s[0])) + s[1:] }, + "default": util.DefaultValue, + "typepkg": func(a api) string { + return fmt.Sprintf("*%sapi%s.%s", a.PkgName, a.Version, a.Name) + }, } // interfaceWrapper is used to wrap each of the @@ -64,6 +71,7 @@ type packages struct { APIPath string ClientPath string Version string + Imports []string writer io.Writer } @@ -134,7 +142,13 @@ func sanitize(groupName string) string { return groupName } -func (p *packages) WriteContent() error { +// TODO: Clean this up. Its not required to convert the input to a struct +// and then to a map before passing it to template. Using of map is better, +// as it allows to add more variables dynamically. +func (p *packages) WriteContent(importList *[]string) error { + if importList != nil { + p.Imports = append(p.Imports, p.appendGenericImports(*importList)...) + } templ, err := template.New("client").Funcs(funcMap).Parse(commonTempl) if err != nil { return err @@ -144,21 +158,27 @@ func (p *packages) WriteContent() error { // api contains info about each type type api struct { - Name string - PkgName string - Version string - IsNamespaced bool - AdditionalMethods []AdditionalMethod - SkipVerbs []string - OnlyVerbs []string - NoVerbs bool - HasStatus bool - writer io.Writer + Name string + PkgName string + Version string + IsNamespaced bool + AdditionalMethods []AdditionalMethod + SkipVerbs []string + NoVerbs bool + HasStatus bool + ApplyConfigurationPackage string + writer io.Writer + importList *[]string + + InputType string + InputName string + ResultType string + Method string } type AdditionalMethod struct { Method *string - Verb *[]string + Verb *string Subresource *string Input *string Result *string @@ -167,9 +187,10 @@ type AdditionalMethod struct { func NewAPI( root *loader.Package, info *markers.TypeInfo, - group, version string, + group, version, applyconfigurationpkg string, namespaceScoped bool, additionalMethods []AdditionalMethod, + importList *[]string, skipVerbs, onlyVerbs []string, noVerbs, hasStatus bool, w io.Writer, @@ -180,24 +201,210 @@ func NewAPI( } api := &api{ - Name: info.RawSpec.Name.Name, - PkgName: group, - Version: version, - IsNamespaced: namespaceScoped, - AdditionalMethods: additionalMethods, - SkipVerbs: skipVerbs, - OnlyVerbs: onlyVerbs, - NoVerbs: noVerbs, - HasStatus: hasStatus, - writer: w, + Name: info.RawSpec.Name.Name, + PkgName: group, + Version: version, + ApplyConfigurationPackage: applyconfigurationpkg, + IsNamespaced: namespaceScoped, + AdditionalMethods: additionalMethods, + importList: importList, + SkipVerbs: skipVerbs, + NoVerbs: noVerbs, + HasStatus: hasStatus, + writer: w, } return api, nil } +// TODO: add templating logic for additional methods func (a *api) WriteContent() error { - templ, err := template.New("wrapper").Funcs(funcMap).Parse(wrapperMethodsTempl) + generateApply := len(a.ApplyConfigurationPackage) > 0 + // Add common template followed by specific methods which are required. + if err := templateExecute(wrapperMethodsTempl, *a); err != nil { + return err + } + + if a.NoVerbs { + return nil + } + + if a.hasVerb("get") { + if err := templateExecute(getTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("list") { + if err := templateExecute(listTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("watch") { + if err := templateExecute(watchTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("create") { + if err := templateExecute(createTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("update") { + if err := templateExecute(updateTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("updateStatus") && a.HasStatus { + if err := templateExecute(updateStatusTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("delete") { + if err := templateExecute(deleteTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("deleteCollection") { + if err := templateExecute(deleteCollectionTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("patch") { + if err := templateExecute(patchTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("apply") && generateApply { + *a.importList = append(*a.importList, util.ImportFormat(fmt.Sprintf("%sapply%s", a.PkgName, a.Version), fmt.Sprintf("%s/%s/%s", a.ApplyConfigurationPackage, a.PkgName, a.Version))) + if err := templateExecute(applyTemplate, *a); err != nil { + return err + } + } + + if a.hasVerb("applyStatus") && generateApply && a.HasStatus { + if err := templateExecute(applyStatusTemplate, *a); err != nil { + return err + } + } + + for _, extension := range a.AdditionalMethods { + if extension.Result != nil { + name, pkg := getPkgType(*extension.Result) + if len(pkg) > 0 { + *a.importList = append(*a.importList, util.ImportFormat(fmt.Sprintf("%sapi", strings.ToLower(name)), pkg)) + a.ResultType = fmt.Sprintf("*%sapi.%s", strings.ToLower(name), name) + } else { + a.ResultType = fmt.Sprintf("*%sapi%s.%s", a.PkgName, a.Version, name) + } + + } + + if extension.Input != nil { + name, pkg := getPkgType(*extension.Result) + if len(pkg) > 0 { + *a.importList = append(*a.importList, util.ImportFormat(fmt.Sprintf("%sapi", strings.ToLower(name)), pkg)) + a.InputType = fmt.Sprintf("*%sapi.%s", strings.ToLower(name), name) + if *extension.Verb == "apply" { + _, gvString := codegenutil.ParsePathGroupVersion(pkg) + *a.importList = append(*a.importList, util.ImportFormat(fmt.Sprintf("%sapplyconfig", strings.ToLower(name)), fmt.Sprintf("%s/%s", a.ApplyConfigurationPackage, gvString))) + a.InputType = fmt.Sprintf("*%sapplyconfig.%sApplyConfiguration", strings.ToLower(name), name) + } + } else { + a.InputType = fmt.Sprintf("*%sapi%s.%s", a.PkgName, a.Version, name) + if *extension.Verb == "apply" { + a.InputType = fmt.Sprintf("*%sapply%s.%sApplyConfiguration", a.PkgName, a.Version, name) + } + } + a.InputName = strings.ToLower(name) + } + + if *extension.Verb == "get" { + a.Method = *extension.Method + if err := templateExecute(getTemplate, *a); err != nil { + return err + } + } + + if *extension.Verb == "create" { + a.Method = *extension.Method + adjTemplate := adjustTemplate(createTemplate, "create") + if err := templateExecute(adjTemplate, *a); err != nil { + return err + } + } + + if *extension.Verb == "update" { + a.Method = *extension.Method + adjTemplate := adjustTemplate(updateTemplate, "update") + if err := templateExecute(adjTemplate, *a); err != nil { + return err + } + } + + if *extension.Verb == "apply" { + a.Method = *extension.Method + adjTemplate := adjustTemplate(applyTemplate, "apply") + if err := templateExecute(adjTemplate, *a); err != nil { + return err + } + } + } + return nil +} + +// Execute template and append the contents to the writer. +func templateExecute(templateName string, data api) error { + templ, err := template.New("wrapper").Funcs(funcMap).Parse(templateName) if err != nil { return err } - return templ.Execute(a.writer, a) + return templ.Execute(data.writer, data) +} + +// hasVerb returns true if the verb is to be scaffolded, +// if it is a part of "skipVerbs" then it returns false. +func (a *api) hasVerb(verb string) bool { + if len(a.SkipVerbs) == 0 { + return true + } + for _, s := range a.SkipVerbs { + if verb == s { + return false + } + } + return true +} + +// getPkgType returns the result override package path and the type. +func getPkgType(input string) (string, string) { + parts := strings.Split(input, ".") + return parts[len(parts)-1], strings.Join(parts[0:len(parts)-1], ".") +} + +// appendImports adds the regular imports needed for scaffolding. +func (p *packages) appendGenericImports(importList []string) []string { + return append(importList, `"context"`, `"fmt"`, `"k8s.io/apimachinery/pkg/types"`, `"k8s.io/client-go/rest"`, + `"github.com/kcp-dev/logicalcluster"`, util.ImportFormat("metav1", "k8s.io/apimachinery/pkg/apis/meta/v1"), `"k8s.io/apimachinery/pkg/watch"`, util.ImportFormat("kcp", "github.com/kcp-dev/apimachinery/pkg/client"), + util.ImportFormat(fmt.Sprintf("%sapi%s", p.Name, p.Version), p.APIPath), + util.ImportFormat(fmt.Sprintf("%s%s", p.Name, p.Version), fmt.Sprintf("%s/typed/%s/%s", p.ClientPath, p.Name, p.Version))) +} + +func adjustTemplate(template, verb string) string { + index := strings.Index(template, "ctx context.Context,") + len("ctx context.Context,") + newTemplate := string(template[:index]) + " name string," + string(template[index:]) + return adjReturnValueInTemplate(newTemplate, verb) +} + +func adjReturnValueInTemplate(template, verb string) string { + s := fmt.Sprintf("w.delegate.{{value .Method \"%s\"}}(ctx,", util.UpperFirst(verb)) + index := strings.Index(template, s) + len(s) + return string(template[:index]) + " name, " + string(template[index:]) } diff --git a/pkg/internal/templates.go b/pkg/internal/templates.go index d9f46b50a..9c3d70841 100644 --- a/pkg/internal/templates.go +++ b/pkg/internal/templates.go @@ -108,17 +108,8 @@ const commonTempl = ` package {{.Version}} import ( - "context" - "fmt" - {{.Name}}api{{.Version}} "{{.APIPath}}" - {{.Name}}{{.Version}} "{{.ClientPath}}/typed/{{.Name}}/{{.Version}}" - - kcp "github.com/kcp-dev/apimachinery/pkg/client" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/rest" - "github.com/kcp-dev/logicalcluster" - "k8s.io/apimachinery/pkg/watch" + {{range $element := .Imports}} + {{$element}} {{end}} ) // Wrapped{{upperFirst .Name}}{{upperFirst .Version}} wraps the client interface with a @@ -166,37 +157,77 @@ func (w *wrapped{{.Name}}) checkCluster(ctx context.Context) (context.Context, e } return ctx, nil } +` +const patchTemplate = ` +// Patch implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *{{.PkgName}}api{{.Version}}.{{.Name}}, err error) { + ctx, err = w.checkCluster(ctx) + if err != nil { + return nil, err + } + return w.delegate.Patch(ctx, name, pt, data, opts, subresources...) +} +` +const applyTemplate = ` +{{$result := typepkg . }} +{{$privateName := lowerFirst .Name}} +{{$applyConfig := (printf "*%sapply%s.%sApplyConfiguration" .PkgName .Version .Name)}} +// {{default .Method "Apply"}} implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) {{default .Method "Apply"}}(ctx context.Context, {{default .InputName $privateName}} {{default .InputType $applyConfig}}, opts metav1.ApplyOptions) (result {{default .ResultType $result}}, err error) { + ctx, err = w.checkCluster(ctx) + if err != nil { + return nil, err + } + return w.delegate.{{default .Method "Apply"}}(ctx, {{default .InputName $privateName}}, opts) +} +` -// Create implements {{.Name}}Interface. -func (w *wrapped{{.Name}}) Create(ctx context.Context, {{lowerFirst .Name}} *{{.PkgName}}api{{.Version}}.{{.Name}}, opts metav1.CreateOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}, error) { - ctx, err := w.checkCluster(ctx) +const applyStatusTemplate = ` +// ApplyStatus implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) ApplyStatus(ctx context.Context, {{lowerFirst .Name}} *{{.PkgName}}apply{{.Version}}.{{.Name}}ApplyConfiguration, opts metav1.ApplyOptions) (result *{{.PkgName}}api{{.Version}}.{{.Name}}, err error) { + ctx, err = w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Create(ctx, {{lowerFirst .Name}}, opts) + return w.delegate.ApplyStatus(ctx, {{lowerFirst .Name}}, opts) } +` -// Update implements {{.Name}}Interface. -func (w *wrapped{{.Name}}) Update(ctx context.Context, {{lowerFirst .Name}} *{{.PkgName}}api{{.Version}}.{{.Name}}, opts metav1.UpdateOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}, error) { +const watchTemplate = ` +// Watch implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Update(ctx, {{lowerFirst .Name}}, opts) + return w.delegate.Watch(ctx, opts) } +` -{{if .HasStatus}} - // UpdateStatus implements {{.Name}}Interface. It was generated because the type contains a Status member. - func (w *wrapped{{.Name}}) UpdateStatus(ctx context.Context, {{lowerFirst .Name}} *{{.PkgName}}api{{.Version}}.{{.Name}}, opts metav1.UpdateOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}, error) { - ctx, err := w.checkCluster(ctx) - if err != nil { - return nil, err - } - return w.delegate.UpdateStatus(ctx, {{lowerFirst .Name}}, opts) - } - {{end}} +const listTemplate = ` +// List implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) List(ctx context.Context, opts metav1.ListOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}List, error) { + ctx, err := w.checkCluster(ctx) + if err != nil { + return nil, err + } + return w.delegate.List(ctx, opts) +} +` -// Update implements {{.Name}}Interface. +const getTemplate = ` +{{$result := typepkg . }} +// {{default .Method "Get"}} implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) {{default .Method "Get"}}(ctx context.Context, name string, opts metav1.GetOptions) ({{default .ResultType $result}}, error) { + ctx, err := w.checkCluster(ctx) + if err != nil { + return nil, err + } + return w.delegate.{{default .Method "Get"}}(ctx, name, opts) +} +` +const deleteTemplate = ` +// Delete implements {{.Name}}Interface. func (w *wrapped{{.Name}}) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { ctx, err := w.checkCluster(ctx) if err != nil { @@ -204,7 +235,9 @@ func (w *wrapped{{.Name}}) Delete(ctx context.Context, name string, opts metav1. } return w.delegate.Delete(ctx, name, opts) } +` +const deleteCollectionTemplate = ` // DeleteCollection implements {{.Name}}Interface. func (w *wrapped{{.Name}}) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listopts metav1.ListOptions) error { ctx, err := w.checkCluster(ctx) @@ -213,40 +246,41 @@ func (w *wrapped{{.Name}}) DeleteCollection(ctx context.Context, opts metav1.Del } return w.delegate.DeleteCollection(ctx, opts, listopts) } +` -// Get implements {{.Name}}Interface. -func (w *wrapped{{.Name}}) Get(ctx context.Context, name string, opts metav1.GetOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}, error) { +const createTemplate = ` +{{$result := typepkg . }} +{{$privateName := lowerFirst .Name}} +// {{default .Method "Create"}} implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) {{default .Method "Create"}}(ctx context.Context, {{default .InputName $privateName}} {{default .InputType $result}}, opts metav1.CreateOptions) ({{default .ResultType $result}}, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Get(ctx, name, opts) + return w.delegate.{{default .Method "Create"}}(ctx, {{default .InputName $privateName}}, opts) } +` -// List implements {{.Name}}Interface. -func (w *wrapped{{.Name}}) List(ctx context.Context, opts metav1.ListOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}List, error) { +const updateTemplate = ` +{{$result := typepkg . }} +{{$privateName := lowerFirst .Name}} +// {{default .Method "Update"}} implements {{.Name}}Interface. +func (w *wrapped{{.Name}}) {{default .Method "Update"}}(ctx context.Context, {{default .InputName $privateName}} {{default .InputType $result}}, opts metav1.UpdateOptions) ({{default .ResultType $result}}, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.List(ctx, opts) + return w.delegate.{{default .Method "Update"}}(ctx, {{default .InputName $privateName}}, opts) } +` -// Watch implements {{.Name}}Interface. -func (w *wrapped{{.Name}}) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { +const updateStatusTemplate = ` +// UpdateStatus implements {{.Name}}Interface. It was generated because the type contains a Status member. +func (w *wrapped{{.Name}}) UpdateStatus(ctx context.Context, {{lowerFirst .Name}} *{{.PkgName}}api{{.Version}}.{{.Name}}, opts metav1.UpdateOptions) (*{{.PkgName}}api{{.Version}}.{{.Name}}, error) { ctx, err := w.checkCluster(ctx) if err != nil { return nil, err } - return w.delegate.Watch(ctx, opts) -} - -// Patch implements {{.Name}}Interface. -func (w *wrapped{{.Name}}) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *{{.PkgName}}api{{.Version}}.{{.Name}}, err error) { - ctx, err = w.checkCluster(ctx) - if err != nil { - return nil, err - } - return w.delegate.Patch(ctx, name, pt, data, opts, subresources...) + return w.delegate.UpdateStatus(ctx, {{lowerFirst .Name}}, opts) } ` diff --git a/pkg/util/util.go b/pkg/util/util.go index 83d26ea50..29543d633 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -26,6 +26,43 @@ import ( "golang.org/x/mod/modfile" ) +// ResultTypeSupportedVerbs is a list of verb types that supports overriding the +// resulting type. +var ResultTypeSupportedVerbs = []string{ + "create", + "update", + "get", + "list", + "patch", + "apply", +} + +// UnsupportedExtensionVerbs is a list of verbs we don't support generating +// extension client functions for. +var UnsupportedExtensionVerbs = []string{ + "updateStatus", + "deleteCollection", + "watch", + "delete", +} + +// InputTypeSupportedVerbs is a list of verb types that supports overriding the +// input argument type. +var InputTypeSupportedVerbs = []string{ + "create", + "update", + "apply", +} + +// DefaultValue just returns the first non-empty string among +// two inputs. +var DefaultValue = func(a, b string) string { + if len(a) == 0 { + return b + } + return a +} + // CurrentPackage returns the go package of the current directory, or "" if it cannot // be derived from the GOPATH. // This logic is taken from k8.io/code-generator, but has a change of letting user pass the @@ -99,3 +136,14 @@ func GetCleanRealtivePath(basePath, outputPath string) string { return filepath.Join(basePath, filepath.Clean(outputPath)) } + +// ImportFormat returns the pkgName and path formatted to be scaffolded +// for inputs. +func ImportFormat(tag, path string) string { + return fmt.Sprintf("%s %q", tag, path) +} + +// upperFirst sets the first alphabet to upperCase +func UpperFirst(s string) string { + return strings.ToUpper(string(s[0])) + s[1:] +}