Skip to content

Commit a23975e

Browse files
committed
Watch handler namespace to revert external modifications
In addition to the periodic resync, watch the handler namespace and map its events to NMState reconcile requests so externally modified namespace metadata (e.g. annotations) is restored immediately. The manager cache is restricted to the handler namespace for Namespace objects to avoid caching every namespace in the cluster. Assisted-By: Claude Fable 5 Signed-off-by: Mat Kowalski <mko@redhat.com>
1 parent 9923a61 commit a23975e

3 files changed

Lines changed: 52 additions & 0 deletions

File tree

cmd/operator/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import (
2525
"time"
2626

2727
corev1 "k8s.io/api/core/v1"
28+
"k8s.io/apimachinery/pkg/fields"
2829
"k8s.io/apimachinery/pkg/runtime"
2930
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3031
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3132
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
3233
ctrl "sigs.k8s.io/controller-runtime"
34+
"sigs.k8s.io/controller-runtime/pkg/cache"
3335
"sigs.k8s.io/controller-runtime/pkg/client"
3436
"sigs.k8s.io/controller-runtime/pkg/healthz"
3537
"sigs.k8s.io/controller-runtime/pkg/log/zap"
@@ -88,6 +90,16 @@ func main() {
8890
BindAddress: "0", // disable metrics
8991
},
9092
HealthProbeBindAddress: ":8081",
93+
Cache: cache.Options{
94+
// The operator watches the handler namespace to revert external
95+
// modifications to its metadata. Restrict the cache to just that
96+
// namespace so we do not cache/watch every namespace in the cluster.
97+
ByObject: map[client.Object]cache.ByObject{
98+
&corev1.Namespace{}: {
99+
Field: fields.OneTermEqualSelector("metadata.name", os.Getenv("HANDLER_NAMESPACE")),
100+
},
101+
},
102+
},
91103
}
92104

93105
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrlOptions)

controllers/operator/nmstate_controller.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"k8s.io/apimachinery/pkg/types"
3535
ctrl "sigs.k8s.io/controller-runtime"
3636
"sigs.k8s.io/controller-runtime/pkg/client"
37+
"sigs.k8s.io/controller-runtime/pkg/handler"
3738

3839
appsv1 "k8s.io/api/apps/v1"
3940
corev1 "k8s.io/api/core/v1"
@@ -152,13 +153,39 @@ func (r *NMStateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
152153
}
153154

154155
func (r *NMStateReconciler) SetupWithManager(mgr ctrl.Manager) error {
156+
// Watch the handler namespace so that externally modified metadata
157+
// (e.g. annotations) is restored without waiting for the periodic
158+
// resync. The manager cache is restricted to the handler namespace
159+
// for Namespace objects, but we filter here too in case that ever
160+
// changes.
161+
handlerNamespaceFunc := r.nmstateRequestsFromHandlerNamespace
155162
return ctrl.NewControllerManagedBy(mgr).
156163
For(&nmstatev1.NMState{}).
157164
Owns(&appsv1.Deployment{}).
158165
Owns(&appsv1.DaemonSet{}).
166+
Watches(&corev1.Namespace{}, handler.EnqueueRequestsFromMapFunc(handlerNamespaceFunc)).
159167
Complete(r)
160168
}
161169

170+
// nmstateRequestsFromHandlerNamespace maps events on the handler namespace to
171+
// reconcile requests for the deployed NMState CRs so that externally modified
172+
// namespace metadata (e.g. annotations) is restored.
173+
func (r *NMStateReconciler) nmstateRequestsFromHandlerNamespace(ctx context.Context, obj client.Object) []ctrl.Request {
174+
if obj.GetName() != os.Getenv("HANDLER_NAMESPACE") {
175+
return nil
176+
}
177+
nmstateList := &nmstatev1.NMStateList{}
178+
if err := r.List(ctx, nmstateList); err != nil {
179+
r.Log.Error(err, "failed listing NMState CRs to enqueue from namespace event")
180+
return nil
181+
}
182+
requests := []ctrl.Request{}
183+
for i := range nmstateList.Items {
184+
requests = append(requests, ctrl.Request{NamespacedName: types.NamespacedName{Name: nmstateList.Items[i].Name}})
185+
}
186+
return requests
187+
}
188+
162189
func (r *NMStateReconciler) applyManifests(instance *nmstatev1.NMState, ctx context.Context) error {
163190
if err := r.applyCRDs(ctx, instance); err != nil {
164191
return errors.Wrap(err, "failed applying CRDs")

controllers/operator/nmstate_controller_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,19 @@ var _ = Describe("NMState controller reconcile", func() {
224224
Expect(result).To(Equal(ctrl.Result{RequeueAfter: ResyncPeriod}))
225225
})
226226
})
227+
Context("when handler namespace events are mapped to reconcile requests", func() {
228+
newNamespace := func(name string) *corev1.Namespace {
229+
return &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}
230+
}
231+
It("should enqueue a request for the NMState CR on handler namespace events", func() {
232+
requests := reconciler.nmstateRequestsFromHandlerNamespace(context.Background(), newNamespace(handlerNamespace))
233+
Expect(requests).To(ConsistOf(ctrl.Request{NamespacedName: types.NamespacedName{Name: existingNMStateName}}))
234+
})
235+
It("should not enqueue requests for other namespaces", func() {
236+
requests := reconciler.nmstateRequestsFromHandlerNamespace(context.Background(), newNamespace("some-other-namespace"))
237+
Expect(requests).To(BeEmpty())
238+
})
239+
})
227240
Context("when one of manifest directory is empty", func() {
228241
var (
229242
request ctrl.Request

0 commit comments

Comments
 (0)