Repository: fluxcd/flagger (go)

Issue:
helm-controller drift-detection collides with flagger's default selectorLabels
### Describe the bug

with helm-controller drift-detection enabled, helm-controller may continually modify the Service object managed by flagger's Canary depending on what the helm chart's manifest is. This causes the canary deployment to never leave the initalizing state.


### To Reproduce

<!--
Steps to reproduce the behaviour
-->
I have a chart based on the helm default template created with `helm create <chart>` where template/service.yaml will contain the following under .spec.selector in Service:
```
app.kubernetes.io/name: {{ include "foobar.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
```
When Canary is activated, flagger will drop the app.kubernetes.io/instance selector label as implied in https://docs.flagger.app/faq#label-selectors, and adds the `kustomize.toolkit.fluxcd.io/reconcile: disabled` annotation into .metadata.annotations.

But with helm-controllers drift-detection enabled, it will modify the Service object to revert the selectorLabels back to the contents of the helm manifest. from the helm-controller debug logs:
```Service/dev-es-api/dev-es-api-es-api-producer diff:
.spec.selector.app.kubernetes.io/instance
+dev-es-api

.spec.selector.app.kubernetes.io/name
-es-api-producer-primary
+es-api-producer
```
This modification in turn causes the Canary process to start over and the primary pods stay running but the "canary" pods (without the -primary suffix) see a new configuration every 2 minutes.

### Expected behavior

flagger and helm-controller drift-detection coexist. I suggest the workaround of the  `helm.toolkit.fluxcd.io/driftDetection: disabled` annotation also may be the easier fix in flagger by setting that annotation at the same time as when `kustomize.toolkit.fluxcd.io/reconcile: disabled` is added to the Service object.

### Additional context

- Flagger version: 1.29.0
- Kubernetes version:   1.23
- Service Mesh provider: linkerd
- Ingress provider: nginx


Pull request description:
Disable Flux helm drift detection for managed resources
Disable helm-controller drift detection for managed resources by adding the `helm.toolkit.fluxcd.io/driftDetection: disabled` annotation to the resource. (ref: https://fluxcd.io/flux/components/helm/helmreleases/#excluding-resources-from-drift-detection)

Fixes #1407 

Patch:
diff --git a/pkg/router/util.go b/pkg/router/util.go
index 03267de3..ad0f6902 100644
--- a/pkg/router/util.go
+++ b/pkg/router/util.go
@@ -7,6 +7,7 @@ import (
 const (
 	toolkitMarker         = "toolkit.fluxcd.io"
 	toolkitReconcileKey   = "kustomize.toolkit.fluxcd.io/reconcile"
+	helmDriftDetectionKey = "helm.toolkit.fluxcd.io/driftDetection"
 	toolkitReconcileValue = "disabled"
 )
 
@@ -30,5 +31,6 @@ func includeLabelsByPrefix(labels map[string]string, includeLabelPrefixes []stri
 func filterMetadata(meta map[string]string) map[string]string {
 	// prevent Flux from overriding Flagger managed objects
 	meta[toolkitReconcileKey] = toolkitReconcileValue
+	meta[helmDriftDetectionKey] = toolkitReconcileValue
 	return meta
 }


Test patch:
diff --git a/pkg/router/kubernetes_default_test.go b/pkg/router/kubernetes_default_test.go
index 9d4648f6..c0f96995 100644
--- a/pkg/router/kubernetes_default_test.go
+++ b/pkg/router/kubernetes_default_test.go
@@ -393,7 +393,7 @@ func TestServiceRouter_InitializeMetadata(t *testing.T) {
 
 	primarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
 	require.NoError(t, err)
-	assert.Equal(t, 1, len(primarySvc.Annotations))
+	assert.Equal(t, 2, len(primarySvc.Annotations))
 	assert.Equal(t, "podinfo-primary", primarySvc.Labels["app"])
 }
 
@@ -425,12 +425,12 @@ func TestServiceRouter_ReconcileMetadata(t *testing.T) {
 
 	canarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo-canary", metav1.GetOptions{})
 	require.NoError(t, err)
-	assert.Equal(t, 1, len(canarySvc.Annotations))
+	assert.Equal(t, 2, len(canarySvc.Annotations))
 	assert.Equal(t, "podinfo-canary", canarySvc.Labels["app"])
 
 	primarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
 	require.NoError(t, err)
-	assert.Equal(t, 1, len(primarySvc.Annotations))
+	assert.Equal(t, 2, len(primarySvc.Annotations))
 	assert.Equal(t, "podinfo-primary", primarySvc.Labels["app"])
 
 	mocks.canary.Spec.Service.Apex = &flaggerv1.CustomMetadata{
diff --git a/test/kubernetes/test-deployment.sh b/test/kubernetes/test-deployment.sh
index f9e4407c..393813ba 100755
--- a/test/kubernetes/test-deployment.sh
+++ b/test/kubernetes/test-deployment.sh
@@ -93,6 +93,13 @@ if [ -z "$passed" ]; then
   kubectl -n test get svc/podinfo-svc -oyaml
   exit 1
 fi
+passed=$(kubectl -n test get svc/podinfo-svc -oyaml 2>&1 | { grep 'helm.toolkit.fluxcd.io/driftDetection' || true; })
+if [ -z "$passed" ]; then
+  echo -e '\u2716 helm drift detection annotation test failed'
+  kubectl -n test get svc/podinfo-svc -oyaml
+  exit 1
+fi
+
 passed=$(kubectl -n test get deploy/podinfo-primary -oyaml 2>&1 | { grep test-label-prefix || true; })
 if [ -z "$passed" ]; then
   echo -e '\u2716 primary copy labels by prefix test failed'