Skip to content

Commit 8081eb5

Browse files
justinsbatoato88
andcommitted
Add support for hooks to the reconciler
Hooks are an extension of the sink concept that we already use for watches, but support more interception points and we can have multiple hooks. Co-authored-by: atoato88 <akihito-inou@nec.com>
1 parent c38e4e7 commit 8081eb5

File tree

4 files changed

+118
-18
lines changed

4 files changed

+118
-18
lines changed

pkg/patterns/declarative/hooks.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package declarative
18+
19+
import (
20+
"context"
21+
22+
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/applier"
23+
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/manifest"
24+
)
25+
26+
// Hook is the base interface implemented by a hook
27+
type Hook interface {
28+
}
29+
30+
// ApplyOperation contains the details of an Apply operation
31+
type ApplyOperation struct {
32+
// Subject is the object we are reconciling
33+
Subject DeclarativeObject
34+
35+
// Objects is the set of objects we are applying
36+
Objects *manifest.Objects
37+
38+
// ApplierOptions is the set of options passed to the applier
39+
ApplierOptions *applier.ApplierOptions
40+
}
41+
42+
// AfterApply is implemented by hooks that want to be called after every apply operation
43+
type AfterApply interface {
44+
AfterApply(ctx context.Context, op *ApplyOperation) error
45+
}
46+
47+
// BeforeApply is implemented by hooks that want to be called before every apply operation
48+
type BeforeApply interface {
49+
BeforeApply(ctx context.Context, op *ApplyOperation) error
50+
}

pkg/patterns/declarative/options.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ type reconcilerParams struct {
6161
ownerFn OwnerSelector
6262
labelMaker LabelMaker
6363
status Status
64+
65+
// hooks allow for interception of events during the reconciliation lifecycle
66+
hooks []Hook
6467
}
6568

6669
type ManifestController interface {
@@ -241,3 +244,11 @@ func WithReconcileMetrics(metricsDuration int, ot *ObjectTracker) ReconcilerOpti
241244
return p
242245
}
243246
}
247+
248+
// WithHook allows for us to intercept and inject behaviours at various points in the lifecycle
249+
func WithHook(hook Hook) ReconcilerOption {
250+
return func(p reconcilerParams) reconcilerParams {
251+
p.hooks = append(p.hooks, hook)
252+
return p
253+
}
254+
}

pkg/patterns/declarative/reconciler.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,22 @@ func (r *Reconciler) reconcileExists(ctx context.Context, name types.NamespacedN
304304
CascadingStrategy: r.options.cascadingStrategy,
305305
}
306306

307+
applyOperation := &ApplyOperation{
308+
Subject: instance,
309+
Objects: objects,
310+
ApplierOptions: &applierOpt,
311+
}
312+
307313
applier := r.options.applier
314+
for _, hook := range r.options.hooks {
315+
if beforeApply, ok := hook.(BeforeApply); ok {
316+
if err := beforeApply.BeforeApply(ctx, applyOperation); err != nil {
317+
log.Error(err, "calling BeforeApply hook")
318+
return objects, fmt.Errorf("error calling BeforeApply hook: %v", err)
319+
}
320+
}
321+
}
322+
308323
if err := applier.Apply(ctx, applierOpt); err != nil {
309324
log.Error(err, "applying manifest")
310325
return objects, fmt.Errorf("error applying manifest: %v", err)
@@ -316,6 +331,16 @@ func (r *Reconciler) reconcileExists(ctx context.Context, name types.NamespacedN
316331
return objects, err
317332
}
318333
}
334+
335+
for _, hook := range r.options.hooks {
336+
if afterApply, ok := hook.(AfterApply); ok {
337+
if err := afterApply.AfterApply(ctx, applyOperation); err != nil {
338+
log.Error(err, "calling AfterApply hook")
339+
return objects, fmt.Errorf("error calling AfterApply hook: %w", err)
340+
}
341+
}
342+
}
343+
319344
return objects, nil
320345
}
321346

@@ -613,10 +638,17 @@ func (r *Reconciler) IsKustomizeOptionUsed() bool {
613638
}
614639

615640
// SetSink provides a Sink that will be notified for all deployments
641+
//
642+
// Deprecated: prefer WithHook
616643
func (r *Reconciler) SetSink(sink Sink) {
617644
r.options.sink = sink
618645
}
619646

647+
// AddHook provides a Hook that will be notified of significant events
648+
func (r *Reconciler) AddHook(hook Hook) {
649+
r.options.hooks = append(r.options.hooks, hook)
650+
}
651+
620652
func parseListKind(infos *manifest.Objects) (*manifest.Objects, error) {
621653
var out []*manifest.Object
622654

pkg/patterns/declarative/watch.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ import (
3333
"sigs.k8s.io/controller-runtime/pkg/handler"
3434
"sigs.k8s.io/controller-runtime/pkg/log"
3535
"sigs.k8s.io/controller-runtime/pkg/source"
36-
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/manifest"
3736
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/watch"
3837
)
3938

40-
type eventsSource interface {
41-
SetSink(sink Sink)
39+
// hookableReconciler is implemented by a reconciler that we can hook
40+
type hookableReconciler interface {
41+
AddHook(hook Hook)
4242
}
4343

4444
type DynamicWatch interface {
@@ -62,7 +62,7 @@ type WatchChildrenOptions struct {
6262
Controller controller.Controller
6363

6464
// Reconciler lets us hook into the post-apply lifecycle event.
65-
Reconciler eventsSource
65+
Reconciler hookableReconciler
6666

6767
// ScopeWatchesToNamespace controls whether watches are per-namespace.
6868
// This allows for more narrowly scoped RBAC permissions, at the cost of more watches.
@@ -71,7 +71,7 @@ type WatchChildrenOptions struct {
7171

7272
// WatchAll creates a Watch on ctrl for all objects reconciled by recnl.
7373
// Deprecated: prefer WatchChildren (and consider setting ScopeWatchesToNamespace)
74-
func WatchAll(config *rest.Config, ctrl controller.Controller, reconciler eventsSource, labelMaker LabelMaker) (chan struct{}, error) {
74+
func WatchAll(config *rest.Config, ctrl controller.Controller, reconciler hookableReconciler, labelMaker LabelMaker) (chan struct{}, error) {
7575
options := WatchChildrenOptions{
7676
RESTConfig: config,
7777
Controller: ctrl,
@@ -126,47 +126,54 @@ func WatchChildren(options WatchChildrenOptions) (chan struct{}, error) {
126126
return nil, fmt.Errorf("setting up dynamic watch on the controller: %w", err)
127127
}
128128

129-
options.Reconciler.SetSink(&watchAll{
130-
dw: dw,
131-
options: options,
132-
registered: make(map[string]struct{})})
129+
afterApplyHook := &clusterWatch{
130+
dw: dw,
131+
scopeWatchesToNamespace: options.ScopeWatchesToNamespace,
132+
labelMaker: options.LabelMaker,
133+
registered: make(map[string]struct{}),
134+
}
135+
136+
options.Reconciler.AddHook(afterApplyHook)
133137

134138
return stopCh, nil
135139
}
136140

137-
type watchAll struct {
141+
// clusterWatch watches the objects in one cluster
142+
type clusterWatch struct {
138143
dw DynamicWatch
139144

140-
options WatchChildrenOptions
145+
scopeWatchesToNamespace bool
146+
labelMaker LabelMaker
141147

142148
mutex sync.Mutex
143-
// registered tracks what we are currently watching, avoid duplicate watches.
149+
150+
// registered tracks the objects we are currently watching, avoid duplicate watches.
144151
registered map[string]struct{}
145152
}
146153

147-
// Notify is called by the controller when the object changes. We establish any new watches.
148-
func (w *watchAll) Notify(ctx context.Context, dest DeclarativeObject, objs *manifest.Objects) error {
154+
// AfterApply is called by the controller after an apply. We establish any new watches.
155+
func (w *clusterWatch) AfterApply(ctx context.Context, op *ApplyOperation) error {
149156
log := log.FromContext(ctx)
150157

151-
labelSelector, err := labels.ValidatedSelectorFromSet(w.options.LabelMaker(ctx, dest))
158+
labelSelector, err := labels.ValidatedSelectorFromSet(w.labelMaker(ctx, op.Subject))
152159
if err != nil {
153160
return fmt.Errorf("failed to build label selector: %w", err)
154161
}
155162

156-
notify := metav1.ObjectMeta{Name: dest.GetName(), Namespace: dest.GetNamespace()}
163+
notify := metav1.ObjectMeta{Name: op.Subject.GetName(), Namespace: op.Subject.GetNamespace()}
157164
filter := metav1.ListOptions{LabelSelector: labelSelector.String()}
158165

159166
// Protect against concurrent invocation
160167
w.mutex.Lock()
161168
defer w.mutex.Unlock()
162169

163-
for _, obj := range objs.Items {
170+
for _, obj := range op.Objects.Items {
164171
gvk := obj.GroupVersionKind()
165172

166173
key := fmt.Sprintf("gvk=%s:%s:%s;labels=%s", gvk.Group, gvk.Version, gvk.Kind, filter.LabelSelector)
167174

168175
filterNamespace := ""
169-
if w.options.ScopeWatchesToNamespace && obj.GetNamespace() != "" {
176+
if w.scopeWatchesToNamespace && obj.GetNamespace() != "" {
170177
filterNamespace = obj.GetNamespace()
171178
key += ";namespace=" + filterNamespace
172179
}

0 commit comments

Comments
 (0)