diff --git a/apis/test/pub/v1alpha1/bar_types.go b/apis/test/pub/v1alpha1/bar_types.go index 40221487b6..2eefcd87a7 100644 --- a/apis/test/pub/v1alpha1/bar_types.go +++ b/apis/test/pub/v1alpha1/bar_types.go @@ -27,7 +27,7 @@ import ( ) // +genclient -// +genreconciler +// +genreconciler:class=example.com/filter.class // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Bar is for testing. diff --git a/codegen/cmd/injection-gen/generators/packages.go b/codegen/cmd/injection-gen/generators/packages.go index 55a890e8e6..f65bc2ba7f 100644 --- a/codegen/cmd/injection-gen/generators/packages.go +++ b/codegen/cmd/injection-gen/generators/packages.go @@ -165,13 +165,31 @@ func MustParseClientGenTags(lines []string) Tags { } values := types.ExtractCommentTags("+", lines) - // log.Printf("GOT values %v", values) + _, ret.GenerateDuck = values["genduck"] - _, ret.GenerateReconciler = values["genreconciler"] + + _, genRec := values["genreconciler"] + _, genRecClass := values["genreconciler:class"] + // Generate Reconciler code if genreconciler OR genreconciler:class exist. + if genRec || genRecClass { + ret.GenerateReconciler = true + } return ret } +func extractReconcilerClassTag(t *types.Type) (string, bool) { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + values := types.ExtractCommentTags("+", comments)["genreconciler:class"] + for _, v := range values { + if len(v) == 0 { + continue + } + return v, true + } + return "", false +} + // isInternal returns true if the tags for a member do not contain a json tag func isInternal(m types.Member) bool { return !strings.Contains(m.Tags, "json") @@ -387,6 +405,8 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp // Fix for golang iterator bug. t := t + reconcilerClass, hasReconcilerClass := extractReconcilerClassTag(t) + packagePath := filepath.Join(packagePath, strings.ToLower(t.Name.Name)) clientPackagePath := filepath.Join(basePackage, "client") @@ -412,6 +432,8 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp clientPkg: clientPackagePath, informerPackagePath: informerPackagePath, schemePkg: filepath.Join(customArgs.VersionedClientSetPackage, "scheme"), + reconcilerClass: reconcilerClass, + hasReconcilerClass: hasReconcilerClass, }) return generators @@ -438,6 +460,8 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp outputPackage: filepath.Join(packagePath, "stub"), imports: generator.NewImportTracker(), informerPackagePath: informerPackagePath, + reconcilerClass: reconcilerClass, + hasReconcilerClass: hasReconcilerClass, }) return generators @@ -459,14 +483,16 @@ func reconcilerPackages(basePackage string, groupPkgName string, gv clientgentyp DefaultGen: generator.DefaultGen{ OptionalName: "reconciler", }, - typeToGenerate: t, - outputPackage: packagePath, - imports: generator.NewImportTracker(), - clientsetPkg: customArgs.VersionedClientSetPackage, - listerName: t.Name.Name + "Lister", - listerPkg: listerPackagePath, - groupGoName: groupGoName, - groupVersion: gv, + typeToGenerate: t, + outputPackage: packagePath, + imports: generator.NewImportTracker(), + clientsetPkg: customArgs.VersionedClientSetPackage, + listerName: t.Name.Name + "Lister", + listerPkg: listerPackagePath, + groupGoName: groupGoName, + groupVersion: gv, + reconcilerClass: reconcilerClass, + hasReconcilerClass: hasReconcilerClass, }) return generators diff --git a/codegen/cmd/injection-gen/generators/reconciler_controller.go b/codegen/cmd/injection-gen/generators/reconciler_controller.go index 125d090fbf..05db6d6d75 100644 --- a/codegen/cmd/injection-gen/generators/reconciler_controller.go +++ b/codegen/cmd/injection-gen/generators/reconciler_controller.go @@ -37,6 +37,9 @@ type reconcilerControllerGenerator struct { clientPkg string schemePkg string informerPackagePath string + + reconcilerClass string + hasReconcilerClass bool } var _ generator.Generator = (*reconcilerControllerGenerator)(nil) @@ -63,8 +66,10 @@ func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *ty klog.V(5).Infof("processing type %v", t) m := map[string]interface{}{ - "type": t, - "group": g.groupName, + "type": t, + "group": g.groupName, + "class": g.reconcilerClass, + "hasClass": g.hasReconcilerClass, "controllerImpl": c.Universe.Type(types.Name{ Package: "knative.dev/pkg/controller", Name: "Impl", @@ -149,13 +154,14 @@ const ( defaultControllerAgentName = "{{.type|lowercaseSingular}}-controller" defaultFinalizerName = "{{.type|allLowercasePlural}}.{{.group}}" defaultQueueName = "{{.type|allLowercasePlural}}" + {{if .hasClass}}classAnnotationKey = "{{ .class }}"{{end}} ) // NewImpl returns a {{.controllerImpl|raw}} that handles queuing and feeding work from // the queue through an implementation of {{.controllerReconciler|raw}}, delegating to // the provided Interface and optional Finalizer methods. OptionsFn is used to return // {{.controllerOptions|raw}} to be used but the internal reconciler. -func NewImpl(ctx {{.contextContext|raw}}, r Interface, optionsFns ...{{.controllerOptionsFn|raw}}) *{{.controllerImpl|raw}} { +func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValue string{{end}}, optionsFns ...{{.controllerOptionsFn|raw}}) *{{.controllerImpl|raw}} { logger := {{.loggingFromContext|raw}}(ctx) // Check the options function input. It should be 0 or 1. @@ -189,6 +195,7 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface, optionsFns ...{{.controll Lister: {{.type|lowercaseSingular}}Informer.Lister(), Recorder: recorder, reconciler: r, + {{if .hasClass}}classValue: classValue,{{end}} } impl := {{.controllerNewImpl|raw}}(rec, logger, defaultQueueName) diff --git a/codegen/cmd/injection-gen/generators/reconciler_controller_stub.go b/codegen/cmd/injection-gen/generators/reconciler_controller_stub.go index e7a1428b4f..594a688126 100644 --- a/codegen/cmd/injection-gen/generators/reconciler_controller_stub.go +++ b/codegen/cmd/injection-gen/generators/reconciler_controller_stub.go @@ -35,6 +35,8 @@ type reconcilerControllerStubGenerator struct { reconcilerPkg string informerPackagePath string + reconcilerClass string + hasReconcilerClass bool } var _ generator.Generator = (*reconcilerControllerStubGenerator)(nil) @@ -61,7 +63,9 @@ func (g *reconcilerControllerStubGenerator) GenerateType(c *generator.Context, t klog.V(5).Infof("processing type %v", t) m := map[string]interface{}{ - "type": t, + "type": t, + "class": g.reconcilerClass, + "hasClass": g.hasReconcilerClass, "informerGet": c.Universe.Function(types.Name{ Package: g.informerPackagePath, Name: "Get", @@ -103,9 +107,10 @@ func NewController( {{.type|lowercaseSingular}}Informer := {{.informerGet|raw}}(ctx) // TODO: setup additional informers here. + {{if .hasClass}}// TODO: pass in the expected value for the class annotation filter.{{end}} r := &Reconciler{} - impl := {{.reconcilerNewImpl|raw}}(ctx, r) + impl := {{.reconcilerNewImpl|raw}}(ctx, r{{if .hasClass}}, "default"{{end}}) logger.Info("Setting up event handlers.") diff --git a/codegen/cmd/injection-gen/generators/reconciler_reconciler.go b/codegen/cmd/injection-gen/generators/reconciler_reconciler.go index 7330f71c57..9bf3b7cfc5 100644 --- a/codegen/cmd/injection-gen/generators/reconciler_reconciler.go +++ b/codegen/cmd/injection-gen/generators/reconciler_reconciler.go @@ -37,6 +37,9 @@ type reconcilerReconcilerGenerator struct { listerName string listerPkg string + reconcilerClass string + hasReconcilerClass bool + groupGoName string groupVersion clientgentypes.GroupVersion } @@ -65,9 +68,11 @@ func (g *reconcilerReconcilerGenerator) GenerateType(c *generator.Context, t *ty klog.V(5).Infof("processing type %v", t) m := map[string]interface{}{ - "type": t, - "group": namer.IC(g.groupGoName), - "version": namer.IC(g.groupVersion.Version.String()), + "type": t, + "group": namer.IC(g.groupGoName), + "version": namer.IC(g.groupVersion.Version.String()), + "class": g.reconcilerClass, + "hasClass": g.hasReconcilerClass, "controllerImpl": c.Universe.Type(types.Name{ Package: "knative.dev/pkg/controller", Name: "Impl", @@ -191,6 +196,11 @@ type reconcilerImpl struct { // reconciler is the implementation of the business logic of the resource. reconciler Interface + + {{if .hasClass}} + // classValue is the resource annotation[{{ .class }}] instance value this reconciler instance filters on. + classValue string + {{end}} } // Check that our Reconciler implements controller.Reconciler @@ -199,7 +209,7 @@ var _ controller.Reconciler = (*reconcilerImpl)(nil) ` var reconcilerNewReconciler = ` -func NewReconciler(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw}}, client {{.clientsetInterface|raw}}, lister {{.resourceLister|raw}}, recorder {{.recordEventRecorder|raw}}, r Interface, options ...{{.controllerOptions|raw}} ) {{.controllerReconciler|raw}} { +func NewReconciler(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw}}, client {{.clientsetInterface|raw}}, lister {{.resourceLister|raw}}, recorder {{.recordEventRecorder|raw}}, r Interface{{if .hasClass}}, classValue string{{end}}, options ...{{.controllerOptions|raw}} ) {{.controllerReconciler|raw}} { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) @@ -210,6 +220,7 @@ func NewReconciler(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw} Lister: lister, Recorder: recorder, reconciler: r, + {{if .hasClass}}classValue: classValue,{{end}} } for _, opts := range options { @@ -251,6 +262,15 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro } else if err != nil { return err } + {{if .hasClass}} + if classValue, found := original.GetAnnotations()[classAnnotationKey]; !found || classValue != r.classValue { + logger.Debugw("Skip reconciling resource, class annotation value does not match reconciler instance value.", + zap.String("classKey", classAnnotationKey), + zap.String("issue", classValue+"!="+r.classValue)) + return nil + } + {{end}} + // Don't modify the informers copy. resource := original.DeepCopy() diff --git a/injection/README.md b/injection/README.md index fb57eafa27..77b0877b3c 100644 --- a/injection/README.md +++ b/injection/README.md @@ -423,6 +423,35 @@ var _ addressableservicereconciler.Interface = (*Reconciler)(nil) var _ addressableservicereconciler.Finalizer = (*Reconciler)(nil) ``` +#### Annotation based class filters + +Sometimes a reconciler only wants to reconcile a class of resource identified by +a special annotation on the Custom Resource. + +This behavior can be enabled in the generators by adding the annotation class +key to the type struct: + +```go +// +genreconciler:class=example.com/filter.class +``` + +The `genreconciler` generator code will now have the addition of +`classValue string` to `NewImpl` and `NewReconciler` (for tests): + +```go +NewImpl(ctx context.Context, r Interface, classValue string, optionsFns ...controller.OptionsFn) *controller.Impl +``` + +```go +NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister pubv1alpha1.BarLister, recorder record.EventRecorder, r Interface, classValue string, options ...controller.Options) controller.Reconciler +``` + +`ReconcileKind` and `FinalizeKind` will NOT be called for resources that DO NOT +have the provided `+genreconciler:class=` key annotation. Additionally the +value of the `` annotation on a resource must match the value provided to +`NewImpl` (or `NewReconcile`) for `ReconcileKind` or `FinalizeKind` to be called +for that resource. + #### Stubs To get started, or to use as reference. It is intended to be copied out of the