Repository: argoproj-labs/applicationset (go)

Issue:
ApplicationSet conflicts with ArgoCD-Notification which cause notification spam
**ArgoCD-Notification** sets annotation to track already sent notifications:
```
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    notified.notifications.argoproj.io: >-
      {"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}
```
**ApplicationSet** clears external annotation on sync which leads to notification spam in the loop. 
Should be related: https://github.com/argoproj-labs/applicationset/issues/154



Pull request description:
Preserve ArgoCD Notification state
While #186 would probably be a long-term enhancement, this at least fixes #180 in the near term

Patch:
diff --git a/README.md b/README.md
index b91d7ba..c8a64b5 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,16 @@
-# Argo CD ApplicationSet CRD
+# Argo CD ApplicationSet Controller 
 
-The Argo CD ApplicationSet CRD and controller provides a formal solution to replace the
-[app-of-apps](https://argoproj.github.io/argo-cd/operator-manual/cluster-bootstrapping/) pattern
-with the ultimate goal of introducing ApplicationSet as a first class supported object in 
-Argo CD Core.
+The ApplicationSet controller is a Kubernetes controller that adds support for a new custom `ApplicationSet` CustomResourceDefinition (CRD). This controller/CRD enables both automation and greater flexibility when managing Argo CD Applications across a large number of clusters and within monorepos, plus it makes self-service usage possible on multitenant Kubernetes clusters.
 
-This project will initially be maintained separately from Argo CD, in order to allow quick iteration
-of the spec and implementation, without tying it to Argo CD releases. No promises of backwards
-compatibility are made, at least until merging into Argo CD proper.
+The ApplicationSet controller provides the ability:
+- To deploy Argo CD Applications to multiple Kubernetes clusters at once
+- To deploy multiple Argo CD applications from a single monorepo
+- Allows unprivileged cluster users (those without access to the Argo CD namespace) to deploy Argo CD applications without the need to involve cluster administrators in enabling the destination clusters/namespaces
+- Best of all, all these features are controlled by only a single instance of an ApplicationSet custom resource, which means no more juggling of multiple Argo CD Application resources to target those multiple clusters/repos!
+
+Unlike with an Argo CD Application resource, which deploys resources from a single Git repository to a single destination cluster/namespace, ApplicationSet uses templated automation to create, modify, and manage multiple Argo CD applications at once. 
+
+If you are loving Argo CD and want to use ApplicationSet's automation and templating to take your usage to the next level, the ApplicationSet controller a shot!
 
 ## Example Spec:
 
@@ -22,28 +25,47 @@ metadata:
   name: guestbook
 spec:
   generators:
-  - clusters: {}
-  template:
+  - clusters: {} # This is a generator, specifically, a cluster generator.
+  template: 
+    # This is a template Argo CD Application, but with support for parameter substitution.
     metadata:
       name: '{{name}}-guestbook'
     spec:
+      project: "default"
       source:
-        repoURL: https://github.com/infra-team/cluster-deployments.git
+        repoURL: https://github.com/argoproj/argocd-example-apps/
         targetRevision: HEAD
-        chart: guestbook
+        path: guestbook
       destination:
         server: '{{server}}'
         namespace: guestbook
 ```
 
-See the documentation for an explanation of fields and additional examples.
+The Cluster generator generates parameters, which are substituted into `{{parameter name}}` values within the `template:` section of the `ApplicationSet` resource. In this example, the cluster generates `name` and `server` parameters (containing the name and API URL for the target cluster), which are then substituted into the template's `{{name}}` and `{{server}}` values, respectively.
+
+The parameter generation via multiple sources (cluster, list, git repos), and the use of those values within Argo CD Application templates, is a powerful combination. Learn more about [generators and template](https://argocd-applicationset.readthedocs.io/en/stable/), the [Cluster generator and various other ApplicationSet generators](https://argocd-applicationset.readthedocs.io/en/stable/Generators/), and more, from the ApplicationSet documentation.
+
 
 ## Documentation
 
-Read [the documentation](https://argocd-applicationset.readthedocs.io/) for more information on how to setup and run the ApplicationSet controller, and to learn more about features and usage.
+Take a look at our introductory blog post, [Introducing the ApplicationSet Controller for Argo CD](https://blog.argoproj.io/introducing-the-applicationset-controller-for-argo-cd-982e28b62dc5).
+
+Check out [the complete documentation](https://argocd-applicationset.readthedocs.io/) for a complete introduction, how to setup and run the ApplicationSet controller, how it interacts with Argo CD, generators, templates, use cases, and more.
 
-The original design doc is available here: https://docs.google.com/document/d/1juWGr20FQaJmuuTIS8mBFmWWDU422M_FQMuhp5c1jt4/edit?usp=sharing
+## Community
+
+The ApplicationSet controller is a community-driven project. You can reach the Argo CD ApplicationSet community and developers via the following channels:
+- Q & A : [Github Discussions](https://github.com/argoproj-labs/applicationset/discussions)
+- Chat : [The #argo-cd-appset Slack channel](https://argoproj.github.io/community/join-slack)
+
+We'd love to have you join us!
 
 ## Development
 
-Learn more in the documentation on how to [setup a development environment, and build the controller](https://argocd-applicationset.readthedocs.io/en/latest/Development/).
\ No newline at end of file
+Learn more about how to [setup a development environment, build the ApplicationSet controller, and run the unit/E2E tests](https://argocd-applicationset.readthedocs.io/en/latest/Development/).
+
+Our end goal is to provide a formal solution to replace the [app-of-apps](https://argoproj.github.io/argo-cd/operator-manual/cluster-bootstrapping/) pattern. You can learn more about the founding principles of the ApplicationSet controller from [the original design doc](https://docs.google.com/document/d/1juWGr20FQaJmuuTIS8mBFmWWDU422M_FQMuhp5c1jt4/edit?usp=sharing).
+
+This project will initially be maintained separately from Argo CD, in order to allow quick iteration
+of the spec and implementation, without tying it to Argo CD releases. No promises of backwards
+compatibility are made, at least until merging into Argo CD proper.
diff --git a/pkg/controllers/applicationset_controller.go b/pkg/controllers/applicationset_controller.go
index 7c659f4..d1064da 100644
--- a/pkg/controllers/applicationset_controller.go
+++ b/pkg/controllers/applicationset_controller.go
@@ -53,6 +53,13 @@ import (
 	"github.com/imdario/mergo"
 )
 
+const (
+	// Rather than importing the whole argocd-notifications controller, just copying the const here
+	//   https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/subscriptions.go#L12
+	//   https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/state.go#L17
+	NotifiedAnnotationKey = "notified.notifications.argoproj.io"
+)
+
 // ApplicationSetReconciler reconciles a ApplicationSet object
 type ApplicationSetReconciler struct {
 	client.Client
@@ -438,7 +445,16 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
 		action, err := utils.CreateOrUpdate(ctx, r.Client, found, func() error {
 			// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
 			found.Spec = generatedApp.Spec
+
+			// Preserve argo cd notifications state (https://github.com/argoproj-labs/applicationset/issues/180)
+			if state, exists := found.ObjectMeta.Annotations[NotifiedAnnotationKey]; exists {
+				if generatedApp.Annotations == nil {
+					generatedApp.Annotations = map[string]string{}
+				}
+				generatedApp.Annotations[NotifiedAnnotationKey] = state
+			}
 			found.ObjectMeta.Annotations = generatedApp.Annotations
+
 			found.ObjectMeta.Finalizers = generatedApp.Finalizers
 			found.ObjectMeta.Labels = generatedApp.Labels
 			return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)


Test patch:
diff --git a/pkg/controllers/applicationset_controller_test.go b/pkg/controllers/applicationset_controller_test.go
index 3c4c78c..723435a 100644
--- a/pkg/controllers/applicationset_controller_test.go
+++ b/pkg/controllers/applicationset_controller_test.go
@@ -736,6 +736,72 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "Ensure that argocd notifications state annotation is preserved from an existing app",
+			appSet: argoprojiov1alpha1.ApplicationSet{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "name",
+					Namespace: "namespace",
+				},
+				Spec: argoprojiov1alpha1.ApplicationSetSpec{
+					Template: argoprojiov1alpha1.ApplicationSetTemplate{
+						Spec: argov1alpha1.ApplicationSpec{
+							Project: "project",
+						},
+					},
+				},
+			},
+			existingApps: []argov1alpha1.Application{
+				{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Application",
+						APIVersion: "argoproj.io/v1alpha1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:            "app1",
+						Namespace:       "namespace",
+						ResourceVersion: "2",
+						Labels:          map[string]string{"label-key": "label-value"},
+						Annotations: map[string]string{
+							"annot-key":           "annot-value",
+							NotifiedAnnotationKey: `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`,
+						},
+					},
+					Spec: argov1alpha1.ApplicationSpec{
+						Project: "project",
+					},
+				},
+			},
+			desiredApps: []argov1alpha1.Application{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "app1",
+					},
+					Spec: argov1alpha1.ApplicationSpec{
+						Project: "project",
+					},
+				},
+			},
+			expected: []argov1alpha1.Application{
+				{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Application",
+						APIVersion: "argoproj.io/v1alpha1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:            "app1",
+						Namespace:       "namespace",
+						ResourceVersion: "3",
+						Annotations: map[string]string{
+							NotifiedAnnotationKey: `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`,
+						},
+					},
+					Spec: argov1alpha1.ApplicationSpec{
+						Project: "project",
+					},
+				},
+			},
+		},
 	} {
 
 		t.Run(c.name, func(t *testing.T) {