From e74c6ca460266768ef5279421863966c05fd2362 Mon Sep 17 00:00:00 2001 From: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:33:16 -0400 Subject: [PATCH] fix(notifications): Allow notifications controller to notify on all namespaces (cherry-pick 2.7) (#15856) * fix(notifications): Allow notifications controller to notify on all namespaces (#15702) * Allow notifications controller to notify on all namespaces This adds functionality to the notifications controller to be notified of and send notifications for applications in any namespace. The namespaces to watch are controlled by the same --application-namespaces and ARGOCD_APPLICATION_NAMESPACES variables as in the application controller. Signed-off-by: Nikolas Skoufis * Add SEEK to users.md Signed-off-by: Nikolas Skoufis * Remove unused fields Signed-off-by: Nikolas Skoufis * Revert changes to Procfile Signed-off-by: Nik Skoufis * Fix unit tests Signed-off-by: Nikolas Skoufis * - add argocd namespaces environment variable to notifications controller Signed-off-by: Stewart Thomson * - add example cluster role rbac Signed-off-by: Stewart Thomson * - only look for projects in the controller's namespace (argocd by default) Signed-off-by: Stewart Thomson * - update base manifest Signed-off-by: Stewart Thomson * - skip app processing in notification controller Signed-off-by: Stewart Thomson * added unit test and updated doc Signed-off-by: May Zhang * added unit test and updated doc Signed-off-by: May Zhang * updated examples/k8s-rbac/argocd-server-applications/kustomization.yaml's resources Signed-off-by: May Zhang --------- Signed-off-by: Nikolas Skoufis Signed-off-by: Nik Skoufis Signed-off-by: Stewart Thomson Signed-off-by: May Zhang Co-authored-by: Nikolas Skoufis Co-authored-by: Nik Skoufis Co-authored-by: Stewart Thomson undo unnecessary manifest changes Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> undo unnecessary manifest changes Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * revert unnecessary changes Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> --------- Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Co-authored-by: May Zhang --- .../commands/controller.go | 4 +- docs/operator-manual/app-any-namespace.md | 2 + ...fications-controller-rbac-clusterrole.yaml | 19 +++++++++ ...ns-controller-rbac-clusterrolebinding.yaml | 16 +++++++ ...d-notifications-controller-deployment.yaml | 7 ++++ manifests/ha/install.yaml | 7 ++++ manifests/ha/namespace-install.yaml | 7 ++++ manifests/install.yaml | 7 ++++ manifests/namespace-install.yaml | 7 ++++ .../controller/controller.go | 42 +++++++++++++++---- .../controller/controller_test.go | 32 ++++++++++++++ 11 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrole.yaml create mode 100644 examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrolebinding.yaml create mode 100644 notification_controller/controller/controller_test.go diff --git a/cmd/argocd-notification/commands/controller.go b/cmd/argocd-notification/commands/controller.go index f9494ea966313..e07340233bd39 100644 --- a/cmd/argocd-notification/commands/controller.go +++ b/cmd/argocd-notification/commands/controller.go @@ -55,6 +55,7 @@ func NewCommand() *cobra.Command { argocdRepoServerStrictTLS bool configMapName string secretName string + applicationNamespaces []string ) var command = cobra.Command{ Use: "controller", @@ -138,7 +139,7 @@ func NewCommand() *cobra.Command { log.Infof("serving metrics on port %d", metricsPort) log.Infof("loading configuration %d", metricsPort) - ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, appLabelSelector, registry, secretName, configMapName) + ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, applicationNamespaces, appLabelSelector, registry, secretName, configMapName) err = ctrl.Init(ctx) if err != nil { return err @@ -161,5 +162,6 @@ func NewCommand() *cobra.Command { command.Flags().BoolVar(&argocdRepoServerStrictTLS, "argocd-repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server") command.Flags().StringVar(&configMapName, "config-map-name", "argocd-notifications-cm", "Set notifications ConfigMap name") command.Flags().StringVar(&secretName, "secret-name", "argocd-notifications-secret", "Set notifications Secret name") + command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces that this controller should send notifications for") return &command } diff --git a/docs/operator-manual/app-any-namespace.md b/docs/operator-manual/app-any-namespace.md index ab4b0ca299d7b..447553adc16cb 100644 --- a/docs/operator-manual/app-any-namespace.md +++ b/docs/operator-manual/app-any-namespace.md @@ -71,6 +71,8 @@ We supply a `ClusterRole` and `ClusterRoleBinding` suitable for this purpose in kubectl apply -f examples/k8s-rbac/argocd-server-applications/ ``` +`argocd-notifications-controller-rbac-clusterrole.yaml` and `argocd-notifications-controller-rbac-clusterrolebinding.yaml` are used to support notifications controller to notify apps in all namespaces. + !!! note At some later point in time, we may make this cluster role part of the default installation manifests. diff --git a/examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrole.yaml b/examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrole.yaml new file mode 100644 index 0000000000000..05f92abb11717 --- /dev/null +++ b/examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrole.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-notifications-controller-cluster-apps + app.kubernetes.io/part-of: argocd + app.kubernetes.io/component: notifications-controller + name: argocd-notifications-controller-cluster-apps +rules: +- apiGroups: + - "argoproj.io" + resources: + - "applications" + verbs: + - get + - list + - watch + - update + - patch \ No newline at end of file diff --git a/examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrolebinding.yaml b/examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrolebinding.yaml new file mode 100644 index 0000000000000..c28ab688c0716 --- /dev/null +++ b/examples/k8s-rbac/argocd-server-applications/argocd-notifications-controller-rbac-clusterrolebinding.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: argocd-notifications-controller-cluster-apps + app.kubernetes.io/part-of: argocd + app.kubernetes.io/component: notifications-controller + name: argocd-notifications-controller-cluster-apps +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-notifications-controller-cluster-apps +subjects: +- kind: ServiceAccount + name: argocd-notifications-controller + namespace: argocd diff --git a/manifests/base/notification/argocd-notifications-controller-deployment.yaml b/manifests/base/notification/argocd-notifications-controller-deployment.yaml index d49e565e2acd1..40e1ce52e46a3 100644 --- a/manifests/base/notification/argocd-notifications-controller-deployment.yaml +++ b/manifests/base/notification/argocd-notifications-controller-deployment.yaml @@ -35,6 +35,13 @@ spec: containers: - args: - /usr/local/bin/argocd-notifications + env: + - name: ARGOCD_APPLICATION_NAMESPACES + valueFrom: + configMapKeyRef: + key: application.namespaces + name: argocd-cmd-params-cm + optional: true workingDir: /app livenessProbe: tcpSocket: diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml index 4d741ea667a7a..84cdaf7e6b2eb 100644 --- a/manifests/ha/install.yaml +++ b/manifests/ha/install.yaml @@ -18094,6 +18094,13 @@ spec: containers: - args: - /usr/local/bin/argocd-notifications + env: + - name: ARGOCD_APPLICATION_NAMESPACES + valueFrom: + configMapKeyRef: + key: application.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:v2.7.14 imagePullPolicy: Always livenessProbe: diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml index 16f0a9277dc15..8a93a84f49228 100644 --- a/manifests/ha/namespace-install.yaml +++ b/manifests/ha/namespace-install.yaml @@ -1754,6 +1754,13 @@ spec: containers: - args: - /usr/local/bin/argocd-notifications + env: + - name: ARGOCD_APPLICATION_NAMESPACES + valueFrom: + configMapKeyRef: + key: application.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:v2.7.14 imagePullPolicy: Always livenessProbe: diff --git a/manifests/install.yaml b/manifests/install.yaml index 2eb00d87a3d7e..37c46f637414d 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -17211,6 +17211,13 @@ spec: containers: - args: - /usr/local/bin/argocd-notifications + env: + - name: ARGOCD_APPLICATION_NAMESPACES + valueFrom: + configMapKeyRef: + key: application.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:v2.7.14 imagePullPolicy: Always livenessProbe: diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 9665aa4a771fc..6af51c780724b 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -871,6 +871,13 @@ spec: containers: - args: - /usr/local/bin/argocd-notifications + env: + - name: ARGOCD_APPLICATION_NAMESPACES + valueFrom: + configMapKeyRef: + key: application.namespaces + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:v2.7.14 imagePullPolicy: Always livenessProbe: diff --git a/notification_controller/controller/controller.go b/notification_controller/controller/controller.go index e975b5c2ded03..1ad2ab361ab93 100644 --- a/notification_controller/controller/controller.go +++ b/notification_controller/controller/controller.go @@ -6,6 +6,8 @@ import ( "fmt" "time" + "github.com/argoproj/argo-cd/v2/util/glob" + "github.com/argoproj/argo-cd/v2/util/notification/k8s" service "github.com/argoproj/argo-cd/v2/util/notification/argocd" @@ -53,14 +55,15 @@ func NewController( client dynamic.Interface, argocdService service.Service, namespace string, + applicationNamespaces []string, appLabelSelector string, registry *controller.MetricsRegistry, secretName string, configMapName string, ) *notificationController { appClient := client.Resource(applications) - appInformer := newInformer(appClient.Namespace(namespace), appLabelSelector) - appProjInformer := newInformer(newAppProjClient(client, namespace), "") + appInformer := newInformer(appClient, namespace, applicationNamespaces, appLabelSelector) + appProjInformer := newInformer(newAppProjClient(client, namespace), namespace, []string{namespace}, "") secretInformer := k8s.NewSecretInformer(k8sClient, namespace, secretName) configMapInformer := k8s.NewConfigMapInformer(k8sClient, namespace, configMapName) apiFactory := api.NewFactory(settings.GetFactorySettings(argocdService, secretName, configMapName), namespace, secretInformer, configMapInformer) @@ -77,6 +80,9 @@ func NewController( if !ok { return false, "" } + if checkAppNotInAdditionalNamespaces(app, namespace, applicationNamespaces) { + return true, "app is not in one of the application-namespaces, nor the notification controller namespace" + } return !isAppSyncStatusRefreshed(app, log.WithField("app", obj.GetName())), "sync status out of date" }), controller.WithMetricsRegistry(registry), @@ -84,6 +90,11 @@ func NewController( return res } +// Check if app is not in the namespace where the controller is in, and also app is not in one of the applicationNamespaces +func checkAppNotInAdditionalNamespaces(app *unstructured.Unstructured, namespace string, applicationNamespaces []string) bool { + return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), false) +} + func (c *notificationController) alterDestinations(obj v1.Object, destinations services.Destinations, cfg api.Config) services.Destinations { app, ok := (obj).(*unstructured.Unstructured) if !ok { @@ -97,21 +108,38 @@ func (c *notificationController) alterDestinations(obj v1.Object, destinations s return destinations } -func newInformer(resClient dynamic.ResourceInterface, selector string) cache.SharedIndexInformer { +func newInformer(resClient dynamic.ResourceInterface, controllerNamespace string, applicationNamespaces []string, selector string) cache.SharedIndexInformer { informer := cache.NewSharedIndexInformer( &cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (object runtime.Object, err error) { + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + // We are only interested in apps that exist in namespaces the + // user wants to be enabled. options.LabelSelector = selector - return resClient.List(context.Background(), options) + appList, err := resClient.List(context.TODO(), options) + if err != nil { + return nil, fmt.Errorf("failed to list applications: %w", err) + } + newItems := []unstructured.Unstructured{} + for _, res := range appList.Items { + if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), false) { + newItems = append(newItems, res) + } + } + appList.Items = newItems + return appList, nil }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { options.LabelSelector = selector - return resClient.Watch(context.Background(), options) + return resClient.Watch(context.TODO(), options) }, }, &unstructured.Unstructured{}, resyncPeriod, - cache.Indexers{}, + cache.Indexers{ + cache.NamespaceIndex: func(obj interface{}) ([]string, error) { + return cache.MetaNamespaceIndexFunc(obj) + }, + }, ) return informer } diff --git a/notification_controller/controller/controller_test.go b/notification_controller/controller/controller_test.go new file mode 100644 index 0000000000000..48cb08c51e9ab --- /dev/null +++ b/notification_controller/controller/controller_test.go @@ -0,0 +1,32 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestCheckAppNotInAdditionalNamespaces(t *testing.T) { + app := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "spec": map[string]interface{}{}, + }, + } + namespace := "argocd" + var applicationNamespaces []string + applicationNamespaces = append(applicationNamespaces, "namespace1") + applicationNamespaces = append(applicationNamespaces, "namespace2") + + // app is in same namespace as controller's namespace + app.SetNamespace(namespace) + assert.False(t, checkAppNotInAdditionalNamespaces(app, namespace, applicationNamespaces)) + + // app is not in the namespace as controller's namespace, but it is in one of the applicationNamespaces + app.SetNamespace("namespace2") + assert.False(t, checkAppNotInAdditionalNamespaces(app, "", applicationNamespaces)) + + // app is not in the namespace as controller's namespace, and it is not in any of the applicationNamespaces + app.SetNamespace("namespace3") + assert.True(t, checkAppNotInAdditionalNamespaces(app, "", applicationNamespaces)) +}