Skip to content

Commit

Permalink
Implement strongly typed class reconcilers (#1103)
Browse files Browse the repository at this point in the history
* Implement strongly typed class reconcilers

* document class filters.

* matt is always right
  • Loading branch information
n3wscott committed Feb 19, 2020
1 parent 1ad2b11 commit 5d8a01d
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 20 deletions.
2 changes: 1 addition & 1 deletion apis/test/pub/v1alpha1/bar_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
46 changes: 36 additions & 10 deletions codegen/cmd/injection-gen/generators/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
13 changes: 10 additions & 3 deletions codegen/cmd/injection-gen/generators/reconciler_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type reconcilerControllerGenerator struct {
clientPkg string
schemePkg string
informerPackagePath string

reconcilerClass string
hasReconcilerClass bool
}

var _ generator.Generator = (*reconcilerControllerGenerator)(nil)
Expand All @@ -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",
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type reconcilerControllerStubGenerator struct {

reconcilerPkg string
informerPackagePath string
reconcilerClass string
hasReconcilerClass bool
}

var _ generator.Generator = (*reconcilerControllerStubGenerator)(nil)
Expand All @@ -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",
Expand Down Expand Up @@ -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.")
Expand Down
28 changes: 24 additions & 4 deletions codegen/cmd/injection-gen/generators/reconciler_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type reconcilerReconcilerGenerator struct {
listerName string
listerPkg string

reconcilerClass string
hasReconcilerClass bool

groupGoName string
groupVersion clientgentypes.GroupVersion
}
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
29 changes: 29 additions & 0 deletions injection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>` key annotation. Additionally the
value of the `<key>` 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
Expand Down

0 comments on commit 5d8a01d

Please sign in to comment.