Repository: argoproj-labs/argocd-operator
Pull request #638 (2022-05-06 14:23:11)

Create notifications config resources
**What type of PR is this?**
> Uncomment only one ` /kind` line, and delete the rest.
> For example, `> /kind bug` would simply become: `/kind bug`

> /kind bug
> /kind chore
> /kind cleanup
> /kind failing-test
 /kind enhancement
> /kind documentation
> /kind code-refactoring


**What does this PR do / why we need it**:

**NOTE**: This PR builds on top of https://github.com/argoproj-labs/argocd-operator/pull/635 and should only be merged after that one has been merged. This PR will be updated with the same review changes from https://github.com/argoproj-labs/argocd-operator/pull/635 after it has been merged. Review comments need not be repeated
 
This is 2/2 PRs for adding support for spinning up the notifications-controller within argocd-operator. This PR enables the operator to create/delete the `argocd-notifications-cm` configmap and `argcocd-notifications-secret` based on whether notifications has been enabled or not. It also adds a set of default template and triggers configurations to the cm out of the box, based on what is present in https://github.com/argoproj/argo-cd/blob/master/notifications_catalog/install.yaml

**Have you updated the necessary documentation?**

* [x] Documentation update is required by this PR.
* [ ] Documentation has been updated.

**Which issue(s) this PR fixes**:
https://github.com/argoproj-labs/argocd-operator/issues/655

**How to test changes / Special notes to the reviewer**:
1. Start the operator locally and spin up an empty Argo CD instance
2. Notice that out of the box there are no configmaps or secrets created for notifications
3. Set `.spec.notifications.enabled` to true
4. Verify that `argocd-notifications-cm` and `argocd-notifications-secret` are created 
5. Verify that `argocd-notifications-cm` contains a set of default templates and triggers out of the box
6. Disable notifications by setting `.spec.notifications.enabled` to false
7. Verify that the cm and secret are properly cleaned up


ishitasequeira: @jaideepr97 , this PR seems to contain changes from your earlier PR #635. Is this intentional? (Especially as this PR is raised on top of the other PR)
jaideepr97: @ishitasequeira yes
This is built on top of the other one 
Split them up since the stories were also split up that way, And it can only be tested all together 
This should go in only after the first one does 
ishitasequeira: I understand, but do we need same changes in both the PRs? Or are you planning on updating this PR once the other one goes in?
jaideepr97: Thanks for the feedback @ishitasequeira !

Patch:
diff --git a/controllers/argocd/notifications.go b/controllers/argocd/notifications.go
index e2583779..08cfbdb1 100644
--- a/controllers/argocd/notifications.go
+++ b/controllers/argocd/notifications.go
@@ -38,6 +38,16 @@ func (r *ReconcileArgoCD) reconcileNotificationsController(cr *argoprojv1a1.Argo
 		return err
 	}
 
+	log.Info("reconciling notifications configmap")
+	if err := r.reconcileNotificationsConfigMap(cr); err != nil {
+		return err
+	}
+
+	log.Info("reconciling notifications secret")
+	if err := r.reconcileNotificationsSecret(cr); err != nil {
+		return err
+	}
+
 	log.Info("reconciling notifications deployment")
 	if err := r.reconcileNotificationsDeployment(cr, sa); err != nil {
 		return err
@@ -72,19 +82,29 @@ func (r *ReconcileArgoCD) deleteNotificationsResources(cr *argoprojv1a1.ArgoCD)
 		return err
 	}
 
+	log.Info("reconciling notifications secret")
+	if err := r.reconcileNotificationsSecret(cr); err != nil {
+		return err
+	}
+
+	log.Info("reconciling notifications configmap")
+	if err := r.reconcileNotificationsConfigMap(cr); err != nil {
+		return err
+	}
+
 	log.Info("reconciling notifications role binding")
 	if err := r.reconcileNotificationsRoleBinding(cr, role, sa); err != nil {
 		return err
 	}
 
-	log.Info("reconciling notifications serviceaccount")
-	_, err := r.reconcileNotificationsServiceAccount(cr)
+	log.Info("reconciling notifications role")
+	_, err := r.reconcileNotificationsRole(cr)
 	if err != nil {
 		return err
 	}
 
-	log.Info("reconciling notifications role")
-	_, err = r.reconcileNotificationsRole(cr)
+	log.Info("reconciling notifications serviceaccount")
+	_, err = r.reconcileNotificationsServiceAccount(cr)
 	if err != nil {
 		return err
 	}
@@ -208,7 +228,7 @@ func (r *ReconcileArgoCD) reconcileNotificationsRoleBinding(cr *argoprojv1a1.Arg
 			return err
 		}
 
-		log.Info(fmt.Sprintf("Creating role %s", desiredRoleBinding.Name))
+		log.Info(fmt.Sprintf("Creating roleBinding %s", desiredRoleBinding.Name))
 		return r.Client.Create(context.TODO(), desiredRoleBinding)
 	}
 
@@ -393,6 +413,97 @@ func (r *ReconcileArgoCD) reconcileNotificationsDeployment(cr *argoprojv1a1.Argo
 
 }
 
+// reconcileNotificationsConfigMap only creates/deletes the argocd-notifications-cm based on whether notifications is enabled/disabled in the CR
+// It does not reconcile/overwrite any fields or information in the configmap itself
+func (r *ReconcileArgoCD) reconcileNotificationsConfigMap(cr *argoprojv1a1.ArgoCD) error {
+
+	desiredConfigMap := newConfigMapWithName("argocd-notifications-cm", cr)
+	desiredConfigMap.Data = getDefaultNotificationsConfig()
+
+	cmExists := true
+	existingConfigMap := &corev1.ConfigMap{}
+	if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredConfigMap.Name, existingConfigMap); err != nil {
+		if !errors.IsNotFound(err) {
+			return fmt.Errorf("failed to get the configmap associated with %s : %s", desiredConfigMap.Name, err)
+		}
+		cmExists = false
+	}
+
+	if cmExists {
+		// CM exists but shouldn't, so it should be deleted
+		if !cr.Spec.Notifications.Enabled {
+			log.Info(fmt.Sprintf("Deleting configmap %s as notifications is disabled", existingConfigMap.Name))
+			return r.Client.Delete(context.TODO(), existingConfigMap)
+		}
+
+		// CM exists and should, nothing to do here
+		return nil
+	}
+
+	// CM doesn't exist and shouldn't, nothing to do here
+	if !cr.Spec.Notifications.Enabled {
+		return nil
+	}
+
+	// CM doesn't exist but should, so it should be created
+	if err := controllerutil.SetControllerReference(cr, desiredConfigMap, r.Scheme); err != nil {
+		return err
+	}
+
+	log.Info(fmt.Sprintf("Creating configmap %s", desiredConfigMap.Name))
+	err := r.Client.Create(context.TODO(), desiredConfigMap)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// reconcileNotificationsSecret only creates/deletes the argocd-notifications-secret based on whether notifications is enabled/disabled in the CR
+// It does not reconcile/overwrite any fields or information in the secret itself
+func (r *ReconcileArgoCD) reconcileNotificationsSecret(cr *argoprojv1a1.ArgoCD) error {
+
+	desiredSecret := argoutil.NewSecretWithName(cr, "argocd-notifications-secret")
+
+	secretExists := true
+	existingSecret := &corev1.Secret{}
+	if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredSecret.Name, existingSecret); err != nil {
+		if !errors.IsNotFound(err) {
+			return fmt.Errorf("failed to get the secret associated with %s : %s", desiredSecret.Name, err)
+		}
+		secretExists = false
+	}
+
+	if secretExists {
+		// secret exists but shouldn't, so it should be deleted
+		if !cr.Spec.Notifications.Enabled {
+			log.Info(fmt.Sprintf("Deleting secret %s as notifications is disabled", existingSecret.Name))
+			return r.Client.Delete(context.TODO(), existingSecret)
+		}
+
+		// secret exists and should, nothing to do here
+		return nil
+	}
+
+	// secret doesn't exist and shouldn't, nothing to do here
+	if !cr.Spec.Notifications.Enabled {
+		return nil
+	}
+
+	// secret doesn't exist but should, so it should be created
+	if err := controllerutil.SetControllerReference(cr, desiredSecret, r.Scheme); err != nil {
+		return err
+	}
+
+	log.Info(fmt.Sprintf("Creating secret %s", desiredSecret.Name))
+	err := r.Client.Create(context.TODO(), desiredSecret)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func getNotificationsCommand() []string {
 
 	cmd := make([]string, 0)
@@ -412,14 +523,3 @@ func getNotificationsResources(cr *argoprojv1a1.ArgoCD) corev1.ResourceRequireme
 
 	return resources
 }
-
-// getArgoCDNotificationsControllerReplicas will return the size value for the argocd-notifications-controller replica count if it
-// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or
-// replicas value is < 0.
-func getArgoCDNotificationsControllerReplicas(cr *argoprojv1a1.ArgoCD) *int32 {
-	if cr.Spec.Notifications.Replicas != nil && *cr.Spec.Notifications.Replicas >= 0 {
-		return cr.Spec.Notifications.Replicas
-	}
-
-	return nil
-}
diff --git a/controllers/argocd/notifications_util.go b/controllers/argocd/notifications_util.go
new file mode 100644
index 00000000..fc78a7e9
--- /dev/null
+++ b/controllers/argocd/notifications_util.go
@@ -0,0 +1,555 @@
+package argocd
+
+import argoprojv1a1 "github.com/argoproj-labs/argocd-operator/api/v1alpha1"
+
+// getDefaultNotificationsConfig returns a map that contains default triggers and template configurations for argocd-notifications-cm
+func getDefaultNotificationsConfig() map[string]string {
+
+	notificationsConfig := make(map[string]string)
+
+	// configure default notifications templates
+
+	notificationsConfig["template.app-created"] = `email:
+  subject: Application {{.app.metadata.name}} has been created.
+message: Application {{.app.metadata.name}} has been created.
+teams:
+  title: Application {{.app.metadata.name}} has been created.`
+
+	notificationsConfig["template.app-deleted"] = `email:
+  subject: Application {{.app.metadata.name}} has been deleted.
+message: Application {{.app.metadata.name}} has been deleted.
+teams:
+  title: Application {{.app.metadata.name}} has been deleted.`
+
+	notificationsConfig["template.app-deployed"] = `email:
+  subject: New version of an application {{.app.metadata.name}} is up and running.
+message: |
+  {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} is now running new version of deployments manifests.
+slack:
+  attachments: |
+    [{
+      "title": "{{ .app.metadata.name}}",
+      "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
+      "color": "#18be52",
+      "fields": [
+      {
+        "title": "Sync Status",
+        "value": "{{.app.status.sync.status}}",
+        "short": true
+      },
+      {
+        "title": "Repository",
+        "value": "{{.app.spec.source.repoURL}}",
+        "short": true
+      },
+      {
+        "title": "Revision",
+        "value": "{{.app.status.sync.revision}}",
+        "short": true
+      }
+      {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "title": "{{$c.type}}",
+        "value": "{{$c.message}}",
+        "short": true
+      }
+      {{end}}
+      ]
+    }]
+  deliveryPolicy: Post
+  groupingKey: ""
+  notifyBroadcast: false
+teams:
+  facts: |
+    [{
+      "name": "Sync Status",
+      "value": "{{.app.status.sync.status}}"
+    },
+    {
+      "name": "Repository",
+      "value": "{{.app.spec.source.repoURL}}"
+    },
+    {
+      "name": "Revision",
+      "value": "{{.app.status.sync.revision}}"
+    }
+    {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "name": "{{$c.type}}",
+        "value": "{{$c.message}}"
+      }
+    {{end}}
+    ]
+  potentialAction: |-
+    [{
+      "@type":"OpenUri",
+      "name":"Operation Application",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
+      }]
+    },
+    {
+      "@type":"OpenUri",
+      "name":"Open Repository",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
+      }]
+    }]
+  themeColor: '#000080'
+  title: New version of an application {{.app.metadata.name}} is up and running.`
+
+	notificationsConfig["template.app-health-degraded"] = `email:
+  subject: Application {{.app.metadata.name}} has degraded.
+message: |
+  {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} has degraded.
+  Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
+slack:
+  attachments: |
+    [{
+      "title": "{{ .app.metadata.name}}",
+      "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
+      "color": "#f4c030",
+      "fields": [
+      {
+        "title": "Health Status",
+        "value": "{{.app.status.health.status}}",
+        "short": true
+      },
+      {
+        "title": "Repository",
+        "value": "{{.app.spec.source.repoURL}}",
+        "short": true
+      }
+      {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "title": "{{$c.type}}",
+        "value": "{{$c.message}}",
+        "short": true
+      }
+      {{end}}
+      ]
+    }]
+  deliveryPolicy: Post
+  groupingKey: ""
+  notifyBroadcast: false
+teams:
+  facts: |
+    [{
+      "name": "Health Status",
+      "value": "{{.app.status.health.status}}"
+    },
+    {
+      "name": "Repository",
+      "value": "{{.app.spec.source.repoURL}}"
+    }
+    {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "name": "{{$c.type}}",
+        "value": "{{$c.message}}"
+      }
+    {{end}}
+    ]
+  potentialAction: |
+    [{
+      "@type":"OpenUri",
+      "name":"Open Application",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
+      }]
+    },
+    {
+      "@type":"OpenUri",
+      "name":"Open Repository",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
+      }]
+    }]
+  themeColor: '#FF0000'
+  title: Application {{.app.metadata.name}} has degraded.`
+
+	notificationsConfig["template.app-sync-failed"] = `email:
+  subject: Failed to sync application {{.app.metadata.name}}.
+message: |
+  {{if eq .serviceType "slack"}}:exclamation:{{end}}  The sync operation of application {{.app.metadata.name}} has failed at {{.app.status.operationState.finishedAt}} with the following error: {{.app.status.operationState.message}}
+  Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
+slack:
+  attachments: |
+    [{
+      "title": "{{ .app.metadata.name}}",
+      "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
+      "color": "#E96D76",
+      "fields": [
+      {
+        "title": "Sync Status",
+        "value": "{{.app.status.sync.status}}",
+        "short": true
+      },
+      {
+        "title": "Repository",
+        "value": "{{.app.spec.source.repoURL}}",
+        "short": true
+      }
+      {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "title": "{{$c.type}}",
+        "value": "{{$c.message}}",
+        "short": true
+      }
+      {{end}}
+      ]
+    }]
+  deliveryPolicy: Post
+  groupingKey: ""
+  notifyBroadcast: false
+teams:
+  facts: |
+    [{
+      "name": "Sync Status",
+      "value": "{{.app.status.sync.status}}"
+    },
+    {
+      "name": "Failed at",
+      "value": "{{.app.status.operationState.finishedAt}}"
+    },
+    {
+      "name": "Repository",
+      "value": "{{.app.spec.source.repoURL}}"
+    }
+    {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "name": "{{$c.type}}",
+        "value": "{{$c.message}}"
+      }
+    {{end}}
+    ]
+  potentialAction: |-
+    [{
+      "@type":"OpenUri",
+      "name":"Open Operation",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
+      }]
+    },
+    {
+      "@type":"OpenUri",
+      "name":"Open Repository",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
+      }]
+    }]
+  themeColor: '#FF0000'
+  title: Failed to sync application {{.app.metadata.name}}.`
+
+	notificationsConfig["template.app-sync-running"] = `email:
+  subject: Start syncing application {{.app.metadata.name}}.
+message: |
+  The sync operation of application {{.app.metadata.name}} has started at {{.app.status.operationState.startedAt}}.
+  Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
+slack:
+  attachments: |
+    [{
+      "title": "{{ .app.metadata.name}}",
+      "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
+      "color": "#0DADEA",
+      "fields": [
+      {
+        "title": "Sync Status",
+        "value": "{{.app.status.sync.status}}",
+        "short": true
+      },
+      {
+        "title": "Repository",
+        "value": "{{.app.spec.source.repoURL}}",
+        "short": true
+      }
+      {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "title": "{{$c.type}}",
+        "value": "{{$c.message}}",
+        "short": true
+      }
+      {{end}}
+      ]
+    }]
+  deliveryPolicy: Post
+  groupingKey: ""
+  notifyBroadcast: false
+teams:
+  facts: |
+    [{
+      "name": "Sync Status",
+      "value": "{{.app.status.sync.status}}"
+    },
+    {
+      "name": "Started at",
+      "value": "{{.app.status.operationState.startedAt}}"
+    },
+    {
+      "name": "Repository",
+      "value": "{{.app.spec.source.repoURL}}"
+    }
+    {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "name": "{{$c.type}}",
+        "value": "{{$c.message}}"
+      }
+    {{end}}
+    ]
+  potentialAction: |-
+    [{
+      "@type":"OpenUri",
+      "name":"Open Operation",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
+      }]
+    },
+    {
+      "@type":"OpenUri",
+      "name":"Open Repository",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
+      }]
+    }]
+  title: Start syncing application {{.app.metadata.name}}.`
+
+	notificationsConfig["template.app-sync-status-unknown"] = `email:
+  subject: Application {{.app.metadata.name}} sync status is 'Unknown'
+message: |
+  {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} sync is 'Unknown'.
+  Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
+  {{if ne .serviceType "slack"}}
+  {{range $c := .app.status.conditions}}
+      * {{$c.message}}
+  {{end}}
+  {{end}}
+slack:
+  attachments: |
+    [{
+      "title": "{{ .app.metadata.name}}",
+      "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
+      "color": "#E96D76",
+      "fields": [
+      {
+        "title": "Sync Status",
+        "value": "{{.app.status.sync.status}}",
+        "short": true
+      },
+      {
+        "title": "Repository",
+        "value": "{{.app.spec.source.repoURL}}",
+        "short": true
+      }
+      {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "title": "{{$c.type}}",
+        "value": "{{$c.message}}",
+        "short": true
+      }
+      {{end}}
+      ]
+    }]
+  deliveryPolicy: Post
+  groupingKey: ""
+  notifyBroadcast: false
+teams:
+  facts: |
+    [{
+      "name": "Sync Status",
+      "value": "{{.app.status.sync.status}}"
+    },
+    {
+      "name": "Repository",
+      "value": "{{.app.spec.source.repoURL}}"
+    }
+    {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "name": "{{$c.type}}",
+        "value": "{{$c.message}}"
+      }
+    {{end}}
+    ]
+  potentialAction: |-
+    [{
+      "@type":"OpenUri",
+      "name":"Open Application",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}"
+      }]
+    },
+    {
+      "@type":"OpenUri",
+      "name":"Open Repository",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
+      }]
+    }]
+  title: Application {{.app.metadata.name}} sync status is 'Unknown'`
+
+	notificationsConfig["template.app-sync-succeeded"] = `email:
+  subject: Application {{.app.metadata.name}} has been successfully synced.
+message: |
+  {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} has been successfully synced at {{.app.status.operationState.finishedAt}}.
+  Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true .
+slack:
+  attachments: |
+    [{
+      "title": "{{ .app.metadata.name}}",
+      "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
+      "color": "#18be52",
+      "fields": [
+      {
+        "title": "Sync Status",
+        "value": "{{.app.status.sync.status}}",
+        "short": true
+      },
+      {
+        "title": "Repository",
+        "value": "{{.app.spec.source.repoURL}}",
+        "short": true
+      }
+      {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "title": "{{$c.type}}",
+        "value": "{{$c.message}}",
+        "short": true
+      }
+      {{end}}
+      ]
+    }]
+  deliveryPolicy: Post
+  groupingKey: ""
+  notifyBroadcast: false
+teams:
+  facts: |
+    [{
+      "name": "Sync Status",
+      "value": "{{.app.status.sync.status}}"
+    },
+    {
+      "name": "Synced at",
+      "value": "{{.app.status.operationState.finishedAt}}"
+    },
+    {
+      "name": "Repository",
+      "value": "{{.app.spec.source.repoURL}}"
+    }
+    {{range $index, $c := .app.status.conditions}}
+      {{if not $index}},{{end}}
+      {{if $index}},{{end}}
+      {
+        "name": "{{$c.type}}",
+        "value": "{{$c.message}}"
+      }
+    {{end}}
+    ]
+  potentialAction: |-
+    [{
+      "@type":"OpenUri",
+      "name":"Operation Details",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
+      }]
+    },
+    {
+      "@type":"OpenUri",
+      "name":"Open Repository",
+      "targets":[{
+        "os":"default",
+        "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}"
+      }]
+    }]
+  themeColor: '#000080'
+  title: Application {{.app.metadata.name}} has been successfully synced`
+
+	// configure default notifications triggers
+
+	notificationsConfig["trigger.on-created"] = `- description: Application is created.
+  oncePer: app.metadata.name
+  send:
+  - app-created
+  when: "true"`
+
+	notificationsConfig["trigger.on-deleted"] = `- description: Application is deleted.
+  oncePer: app.metadata.name
+  send:
+  - app-deleted
+  when: app.metadata.deletionTimestamp != nil`
+
+	notificationsConfig["trigger.on-deployed"] = `- description: Application is synced and healthy. Triggered once per commit.
+  oncePer: app.status.operationState.syncResult.revision
+  send:
+  - app-deployed
+  when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status
+      == 'Healthy'`
+
+	notificationsConfig["trigger.on-health-degraded"] = `- description: Application has degraded
+  send:
+  - app-health-degraded
+  when: app.status.health.status == 'Degraded'`
+
+	notificationsConfig["trigger.on-sync-failed"] = `- description: Application syncing has failed
+  send:
+  - app-sync-failed
+  when: app.status.operationState.phase in ['Error', 'Failed']`
+
+	notificationsConfig["trigger.on-sync-running"] = `- description: Application is being synced
+  send:
+  - app-sync-running
+  when: app.status.operationState.phase in ['Running']`
+
+	notificationsConfig["trigger.on-sync-status-unknown"] = `- description: Application status is 'Unknown'
+  end:
+  - app-sync-status-unknown
+  when: app.status.sync.status == 'Unknown'`
+
+	notificationsConfig["trigger.on-sync-succeeded"] = `-description: Application syncing has succeeded
+  send:
+  - app-sync-succeeded
+  when: app.status.operationState.phase in ['Succeeded']`
+
+	return notificationsConfig
+}
+
+// getArgoCDNotificationsControllerReplicas will return the size value for the argocd-notifications-controller replica count if it
+// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or
+// replicas value is < 0.
+func getArgoCDNotificationsControllerReplicas(cr *argoprojv1a1.ArgoCD) *int32 {
+	if cr.Spec.Notifications.Replicas != nil && *cr.Spec.Notifications.Replicas >= 0 {
+		return cr.Spec.Notifications.Replicas
+	}
+
+	return nil
+}


Test patch:
diff --git a/controllers/argocd/argocd_controller_test.go b/controllers/argocd/argocd_controller_test.go
index afdcb3af..f031a127 100644
--- a/controllers/argocd/argocd_controller_test.go
+++ b/controllers/argocd/argocd_controller_test.go
@@ -106,23 +106,23 @@ func TestReconcileArgoCD_Reconcile(t *testing.T) {
 func TestReconcileArgoCD_Reconcile_RemoveManagedByLabelOnArgocdDeletion(t *testing.T) {
 	restoreEnv(t)
 	logf.SetLogger(ZapLogger(true))
-	
+
 	tests := []struct {
-		testName									string
-		nsName										string
-		isRemoveManagedByLabelOnArgoCDDeletionSet	bool
-		}{
-			{
-				testName: 									"Without REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set",
-				nsName:										"newNamespaceTest1",
-				isRemoveManagedByLabelOnArgoCDDeletionSet: 	false,
-			},
-			{
-				testName: 									"With REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set",
-				nsName:										"newNamespaceTest2",
-				isRemoveManagedByLabelOnArgoCDDeletionSet: 	true,
-			},
-		}
+		testName                                  string
+		nsName                                    string
+		isRemoveManagedByLabelOnArgoCDDeletionSet bool
+	}{
+		{
+			testName: "Without REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set",
+			nsName:   "newNamespaceTest1",
+			isRemoveManagedByLabelOnArgoCDDeletionSet: false,
+		},
+		{
+			testName: "With REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set",
+			nsName:   "newNamespaceTest2",
+			isRemoveManagedByLabelOnArgoCDDeletionSet: true,
+		},
+	}
 
 	for _, test := range tests {
 		t.Run(test.testName, func(t *testing.T) {
@@ -135,10 +135,10 @@ func TestReconcileArgoCD_Reconcile_RemoveManagedByLabelOnArgocdDeletion(t *testi
 			err := r.Client.Create(context.TODO(), nsArgocd)
 			assert.NoError(t, err)
 
-			if (test.isRemoveManagedByLabelOnArgoCDDeletionSet) {
+			if test.isRemoveManagedByLabelOnArgoCDDeletionSet {
 				os.Setenv("REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION", "true")
 			}
-		
+
 			ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
 				Name: test.nsName,
 				Labels: map[string]string{
@@ -147,19 +147,19 @@ func TestReconcileArgoCD_Reconcile_RemoveManagedByLabelOnArgocdDeletion(t *testi
 			}
 			err = r.Client.Create(context.TODO(), ns)
 			assert.NoError(t, err)
-			
+
 			req := reconcile.Request{
 				NamespacedName: types.NamespacedName{
 					Name:      a.Name,
 					Namespace: a.Namespace,
 				},
 			}
-		
+
 			_, err = r.Reconcile(context.TODO(), req)
 			assert.NoError(t, err)
-		
+
 			assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: ns.Name}, ns))
-			if (test.isRemoveManagedByLabelOnArgoCDDeletionSet) {
+			if test.isRemoveManagedByLabelOnArgoCDDeletionSet {
 				// Check if the managed-by label gets removed from the new namespace
 				if _, ok := ns.Labels[common.ArgoCDManagedByLabel]; ok {
 					t.Errorf("Expected the label[%v] to be removed from the namespace[%v]", common.ArgoCDManagedByLabel, ns.Name)
diff --git a/controllers/argocd/notifications_test.go b/controllers/argocd/notifications_test.go
index 73b5f90b..143a7add 100644
--- a/controllers/argocd/notifications_test.go
+++ b/controllers/argocd/notifications_test.go
@@ -221,3 +221,55 @@ func TestReconcileNotifications_CreateDeployments(t *testing.T) {
 	}, deployment)
 	assert.True(t, errors.IsNotFound(err))
 }
+
+func TestReconcileNotifications_CreateSecret(t *testing.T) {
+	logf.SetLogger(ZapLogger(true))
+	a := makeTestArgoCD(func(a *argoprojv1alpha1.ArgoCD) {
+		a.Spec.Notifications.Enabled = true
+	})
+
+	r := makeTestReconciler(t, a)
+
+	err := r.reconcileNotificationsSecret(a)
+	assert.NoError(t, err)
+
+	testSecret := &corev1.Secret{}
+	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      "argocd-notifications-secret",
+		Namespace: a.Namespace,
+	}, testSecret))
+
+	a.Spec.Notifications.Enabled = false
+	err = r.reconcileNotificationsSecret(a)
+	assert.NoError(t, err)
+	secret := &corev1.Secret{}
+	err = r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-notifications-secret", Namespace: a.Namespace}, secret)
+	assertNotFound(t, err)
+}
+
+func TestReconcileNotifications_CreateConfigMap(t *testing.T) {
+	logf.SetLogger(ZapLogger(true))
+	a := makeTestArgoCD(func(a *argoprojv1alpha1.ArgoCD) {
+		a.Spec.Notifications.Enabled = true
+	})
+
+	r := makeTestReconciler(t, a)
+
+	err := r.reconcileNotificationsConfigMap(a)
+	assert.NoError(t, err)
+
+	testCm := &corev1.ConfigMap{}
+	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
+		Name:      "argocd-notifications-cm",
+		Namespace: a.Namespace,
+	}, testCm))
+
+	assert.True(t, len(testCm.Data) > 0)
+
+	a.Spec.Notifications.Enabled = false
+	err = r.reconcileNotificationsConfigMap(a)
+	assert.NoError(t, err)
+	testCm = &corev1.ConfigMap{}
+	err = r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-notifications-cm", Namespace: a.Namespace}, testCm)
+	assertNotFound(t, err)
+}
diff --git a/controllers/argocd/status_test.go b/controllers/argocd/status_test.go
index fb5c15db..94d15370 100644
--- a/controllers/argocd/status_test.go
+++ b/controllers/argocd/status_test.go
@@ -164,7 +164,6 @@ func TestReconcileArgoCD_reconcileStatusHost(t *testing.T) {
 }
 
 func TestReconcileArgoCD_reconcileStatusNotificationsController(t *testing.T) {
-
 	logf.SetLogger(ZapLogger(true))
 	a := makeTestArgoCD()
 	r := makeTestReconciler(t, a)