Skip to content

Commit ef0cfb0

Browse files
authored
feat: watch and reconcile updates to the admin clusterrole (#913)
* feat: watch and reconcile updates to the admin clusterrole Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com> * remove duplicate import Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com> --------- Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
1 parent 2df7e8e commit ef0cfb0

File tree

3 files changed

+165
-2
lines changed

3 files changed

+165
-2
lines changed

cmd/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ func main() {
221221
os.Exit(1)
222222
}
223223

224+
argocdprovisioner.Register(openshift.ReconcilerHook, openshift.BuilderHook)
225+
224226
if err = (&argocdprovisioner.ReconcileArgoCD{
225227
Client: mgr.GetClient(),
226228
Scheme: mgr.GetScheme(),
@@ -268,8 +270,6 @@ func main() {
268270
os.Exit(1)
269271
}
270272

271-
argocdprovisioner.Register(openshift.ReconcilerHook)
272-
273273
setupLog.Info("starting manager")
274274
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
275275
setupLog.Error(err, "problem running manager")

controllers/argocd/openshift/openshift.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ import (
1616
rbacv1 "k8s.io/api/rbac/v1"
1717
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1818
"k8s.io/client-go/kubernetes"
19+
"sigs.k8s.io/controller-runtime/pkg/builder"
20+
"sigs.k8s.io/controller-runtime/pkg/client"
1921
"sigs.k8s.io/controller-runtime/pkg/client/config"
22+
"sigs.k8s.io/controller-runtime/pkg/handler"
2023
logf "sigs.k8s.io/controller-runtime/pkg/log"
24+
"sigs.k8s.io/controller-runtime/pkg/predicate"
25+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2126
)
2227

2328
var log = logf.Log.WithName("openshift_controller_argocd")
@@ -101,6 +106,26 @@ func ReconcilerHook(cr *argoapp.ArgoCD, v interface{}, hint string) error {
101106
return nil
102107
}
103108

109+
// BuilderHook updates the Argo CD controller builder to watch for changes to the "admin" ClusterRole
110+
func BuilderHook(_ *argoapp.ArgoCD, v interface{}, _ string) error {
111+
logv := log.WithValues("module", "builder-hook")
112+
113+
bldr, ok := v.(*argocd.BuilderHook)
114+
if !ok {
115+
return nil
116+
}
117+
118+
logv.Info("updating the Argo CD controller to watch for changes to the admin ClusterRole")
119+
120+
clusterResourceHandler := handler.EnqueueRequestsFromMapFunc(adminClusterRoleMapper(bldr.Client))
121+
bldr.Builder.Watches(&rbacv1.ClusterRole{}, clusterResourceHandler,
122+
builder.WithPredicates(predicate.NewPredicateFuncs(func(o client.Object) bool {
123+
return o.GetName() == "admin"
124+
})))
125+
126+
return nil
127+
}
128+
104129
func getPolicyRuleForApplicationController() []rbacv1.PolicyRule {
105130
return []rbacv1.PolicyRule{
106131
{
@@ -387,3 +412,33 @@ func initK8sClient() (*kubernetes.Clientset, error) {
387412

388413
return kClient, nil
389414
}
415+
416+
// adminClusterRoleMapper maps changes to the "admin" ClusterRole to all Argo CD instances in the cluster
417+
func adminClusterRoleMapper(k8sClient client.Client) handler.MapFunc {
418+
return func(ctx context.Context, o client.Object) []reconcile.Request {
419+
var result = []reconcile.Request{}
420+
421+
// Only process the "admin" ClusterRole
422+
if o.GetName() != "admin" {
423+
return result
424+
}
425+
426+
// Get all Argo CD instances in all namespaces
427+
argocds := &argoapp.ArgoCDList{}
428+
if err := k8sClient.List(ctx, argocds, &client.ListOptions{}); err != nil {
429+
log.Error(err, "failed to list Argo CD instances for admin ClusterRole mapping")
430+
return result
431+
}
432+
433+
// Create reconcile requests for all Argo CD instances
434+
for _, argocd := range argocds.Items {
435+
namespacedName := client.ObjectKey{
436+
Name: argocd.Name,
437+
Namespace: argocd.Namespace,
438+
}
439+
result = append(result, reconcile.Request{NamespacedName: namespacedName})
440+
}
441+
442+
return result
443+
}
444+
}

controllers/argocd/openshift/openshift_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package openshift
22

33
import (
4+
"context"
5+
"sort"
46
"testing"
57

68
corev1 "k8s.io/api/core/v1"
79
rbacv1 "k8s.io/api/rbac/v1"
810
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/client-go/kubernetes/scheme"
912

13+
argoapp "github.com/argoproj-labs/argocd-operator/api/v1beta1"
1014
"github.com/stretchr/testify/assert"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
17+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1118
)
1219

1320
func TestReconcileArgoCD_reconcileApplicableClusterRole(t *testing.T) {
@@ -207,3 +214,104 @@ func TestReconcileArgoCD_reconcileSecrets(t *testing.T) {
207214
assert.NoError(t, ReconcilerHook(a, testSecret, ""))
208215
assert.Equal(t, string(testSecret.Data["namespaces"]), "someRandomNamespace")
209216
}
217+
218+
func TestAdminClusterRoleMapper(t *testing.T) {
219+
s := scheme.Scheme
220+
s.AddKnownTypes(argoapp.GroupVersion, &argoapp.ArgoCD{}, &argoapp.ArgoCDList{})
221+
222+
t.Run("non-admin object returns empty result", func(t *testing.T) {
223+
fakeClient := fake.NewClientBuilder().WithScheme(s).Build()
224+
225+
mapFunc := adminClusterRoleMapper(fakeClient)
226+
227+
nonAdminClusterRole := &rbacv1.ClusterRole{
228+
ObjectMeta: metav1.ObjectMeta{
229+
Name: "not-admin",
230+
},
231+
}
232+
233+
result := mapFunc(context.TODO(), nonAdminClusterRole)
234+
235+
assert.Empty(t, result)
236+
})
237+
238+
t.Run("admin object with no Argo CD instances returns empty result", func(t *testing.T) {
239+
fakeClient := fake.NewClientBuilder().WithScheme(s).Build()
240+
241+
mapFunc := adminClusterRoleMapper(fakeClient)
242+
243+
// Create admin cluster role
244+
adminClusterRole := &rbacv1.ClusterRole{
245+
ObjectMeta: metav1.ObjectMeta{
246+
Name: "admin",
247+
},
248+
}
249+
250+
result := mapFunc(context.TODO(), adminClusterRole)
251+
252+
assert.Empty(t, result)
253+
})
254+
255+
t.Run("admin object with Argo CD instances returns reconcile requests", func(t *testing.T) {
256+
// Create test Argo CD instances
257+
argocd1 := &argoapp.ArgoCD{
258+
ObjectMeta: metav1.ObjectMeta{
259+
Name: "argocd-1",
260+
Namespace: "namespace-1",
261+
},
262+
}
263+
264+
argocd2 := &argoapp.ArgoCD{
265+
ObjectMeta: metav1.ObjectMeta{
266+
Name: "argocd-2",
267+
Namespace: "namespace-2",
268+
},
269+
}
270+
271+
// Create fake client with Argo CD instances
272+
fakeClient := fake.NewClientBuilder().
273+
WithScheme(s).
274+
WithObjects(argocd1, argocd2).
275+
Build()
276+
277+
mapFunc := adminClusterRoleMapper(fakeClient)
278+
279+
// Create admin cluster role
280+
adminClusterRole := &rbacv1.ClusterRole{
281+
ObjectMeta: metav1.ObjectMeta{
282+
Name: "admin",
283+
},
284+
}
285+
286+
result := mapFunc(context.TODO(), adminClusterRole)
287+
288+
// Should return reconcile requests for both Argo CD instances
289+
assert.Len(t, result, 2)
290+
291+
// Check that the reconcile requests contain the correct namespaced names
292+
expectedRequests := []reconcile.Request{
293+
{
294+
NamespacedName: client.ObjectKey{
295+
Name: "argocd-1",
296+
Namespace: "namespace-1",
297+
},
298+
},
299+
{
300+
NamespacedName: client.ObjectKey{
301+
Name: "argocd-2",
302+
Namespace: "namespace-2",
303+
},
304+
},
305+
}
306+
307+
// Sort both slices to ensure consistent comparison
308+
sort.Slice(result, func(i, j int) bool {
309+
return result[i].NamespacedName.Name < result[j].NamespacedName.Name
310+
})
311+
sort.Slice(expectedRequests, func(i, j int) bool {
312+
return expectedRequests[i].NamespacedName.Name < expectedRequests[j].NamespacedName.Name
313+
})
314+
315+
assert.Equal(t, expectedRequests, result)
316+
})
317+
}

0 commit comments

Comments
 (0)