diff --git a/controller/cache/cache.go b/controller/cache/cache.go index 4149cc09c1a62..231f506b5a5fb 100644 --- a/controller/cache/cache.go +++ b/controller/cache/cache.go @@ -176,6 +176,7 @@ func NewLiveStateCache( type cacheSettings struct { clusterSettings clustercache.Settings appInstanceLabelKey string + trackingMethod appv1.TrackingMethod } type liveStateCache struct { @@ -210,7 +211,7 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) { ResourceHealthOverride: lua.ResourceHealthOverrides(resourceOverrides), ResourcesFilter: resourcesFilter, } - return &cacheSettings{clusterSettings, appInstanceLabelKey}, nil + return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr)}, nil } func asResourceNode(r *clustercache.Resource) appv1.ResourceNode { @@ -387,7 +388,6 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server) } - trackingMethod := argo.GetTrackingMethod(c.settingsMgr) clusterCacheOpts := []clustercache.UpdateSettingsFunc{ clustercache.SetListSemaphore(semaphore.NewWeighted(clusterCacheListSemaphoreSize)), clustercache.SetListPageSize(clusterCacheListPageSize), @@ -400,9 +400,12 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e clustercache.SetPopulateResourceInfoHandler(func(un *unstructured.Unstructured, isRoot bool) (interface{}, bool) { res := &ResourceInfo{} populateNodeInfo(un, res) + c.lock.RLock() + cacheSettings := c.cacheSettings + c.lock.RUnlock() res.Health, _ = health.GetResourceHealth(un, cacheSettings.clusterSettings.ResourceHealthOverride) - appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, trackingMethod) + appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod) if isRoot && appName != "" { res.AppName = appName } diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go index 116e0c9b55f97..815f486573619 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -38,6 +38,7 @@ import ( projectFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/project" repoFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/repos" "github.com/argoproj/argo-cd/v2/test/e2e/testdata" + "github.com/argoproj/argo-cd/v2/util/argo" . "github.com/argoproj/argo-cd/v2/util/argo" . "github.com/argoproj/argo-cd/v2/util/errors" "github.com/argoproj/argo-cd/v2/util/io" @@ -2063,3 +2064,189 @@ func TestDisableManifestGeneration(t *testing.T) { assert.Equal(t, app.Status.SourceType, ApplicationSourceTypeDirectory) }) } + +func TestSwitchTrackingMethod(t *testing.T) { + ctx := Given(t) + + ctx. + SetTrackingMethod(string(argo.TrackingMethodAnnotation)). + Path("deployment"). + When(). + CreateApp(). + Sync(). + Refresh(RefreshTypeNormal). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Add resource with tracking annotation. This should put the + // application OutOfSync. + FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-configmap", + Annotations: map[string]string{ + common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()), + }, + }, + }, metav1.CreateOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Delete resource to bring application back in sync + FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + SetTrackingMethod(string(argo.TrackingMethodLabel)). + Sync(). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Add a resource with a tracking annotation. This should not + // affect the application, because we now use the tracking method + // "label". + FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-configmap", + Annotations: map[string]string{ + common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()), + }, + }, + }, metav1.CreateOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Add a resource with the tracking label. The app should become + // OutOfSync. + FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extra-configmap", + Labels: map[string]string{ + common.LabelKeyAppInstance: Name(), + }, + }, + }, metav1.CreateOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Delete resource to bring application back in sync + FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "extra-configmap", metav1.DeleteOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)) +} + +func TestSwitchTrackingLabel(t *testing.T) { + ctx := Given(t) + + ctx. + Path("deployment"). + When(). + CreateApp(). + Sync(). + Refresh(RefreshTypeNormal). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Add extra resource that carries the default tracking label + // We expect the app to go out of sync. + FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-configmap", + Labels: map[string]string{ + common.LabelKeyAppInstance: Name(), + }, + }, + }, metav1.CreateOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Delete resource to bring application back in sync + FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + // Change tracking label + SetTrackingLabel("argocd.tracking"). + Sync(). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Create resource with the new tracking label, the application + // is expected to go out of sync + FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-configmap", + Labels: map[string]string{ + "argocd.tracking": Name(), + }, + }, + }, metav1.CreateOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Delete resource to bring application back in sync + FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + And(func() { + // Add extra resource that carries the default tracking label + // We expect the app to stay in sync, because the configured + // label is different. + FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-configmap", + Labels: map[string]string{ + common.LabelKeyAppInstance: Name(), + }, + }, + }, metav1.CreateOptions{})) + }). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)) +} diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index a58a8e25dc0ce..dbb7031606ec8 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -341,3 +341,13 @@ func (a *Actions) verifyAction() { a.Then().Expect(Success("")) } } + +func (a *Actions) SetTrackingMethod(trackingMethod string) *Actions { + fixture.SetTrackingMethod(trackingMethod) + return a +} + +func (a *Actions) SetTrackingLabel(trackingLabel string) *Actions { + fixture.SetTrackingLabel(trackingLabel) + return a +} diff --git a/test/e2e/fixture/app/context.go b/test/e2e/fixture/app/context.go index fafff85a4c916..a527405ea6aee 100644 --- a/test/e2e/fixture/app/context.go +++ b/test/e2e/fixture/app/context.go @@ -308,3 +308,8 @@ func (c *Context) HelmSkipCrds() *Context { c.helmSkipCrds = true return c } + +func (c *Context) SetTrackingMethod(trackingMethod string) *Context { + fixture.SetTrackingMethod(trackingMethod) + return c +} diff --git a/test/e2e/fixture/fixture.go b/test/e2e/fixture/fixture.go index f910d6b91c701..6acb1c26825f6 100644 --- a/test/e2e/fixture/fixture.go +++ b/test/e2e/fixture/fixture.go @@ -357,6 +357,13 @@ func SetTrackingMethod(trackingMethod string) { }) } +func SetTrackingLabel(trackingLabel string) { + updateSettingConfigMap(func(cm *corev1.ConfigMap) error { + cm.Data["application.instanceLabelKey"] = trackingLabel + return nil + }) +} + func SetResourceOverridesSplitKeys(overrides map[string]v1alpha1.ResourceOverride) { updateSettingConfigMap(func(cm *corev1.ConfigMap) error { for k, v := range overrides { diff --git a/util/argo/resource_tracking.go b/util/argo/resource_tracking.go index 9c8f4a75d8f80..fce9a45102e27 100644 --- a/util/argo/resource_tracking.go +++ b/util/argo/resource_tracking.go @@ -54,7 +54,7 @@ func NewResourceTracking() ResourceTracking { // GetTrackingMethod retrieve tracking method from settings func GetTrackingMethod(settingsMgr *settings.SettingsManager) v1alpha1.TrackingMethod { tm, err := settingsMgr.GetTrackingMethod() - if err != nil { + if err != nil || tm == "" { return TrackingMethodLabel } return v1alpha1.TrackingMethod(tm) diff --git a/util/settings/settings.go b/util/settings/settings.go index 63b2aaa1169e6..3f82c402f317a 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -99,6 +99,8 @@ type ArgoCDSettings struct { ServerRBACLogEnforceEnable bool `json:"serverRBACLogEnforceEnable"` // ExecEnabled indicates whether the UI exec feature is enabled ExecEnabled bool `json:"execEnabled"` + // TrackingMethod defines the resource tracking method to be used + TrackingMethod string `json:"application.resourceTrackingMethod,omitempty"` } type GoogleAnalytics struct { @@ -1271,6 +1273,7 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi } settings.InClusterEnabled = argoCDCM.Data[inClusterEnabledKey] != "false" settings.ExecEnabled = argoCDCM.Data[execEnabledKey] == "true" + settings.TrackingMethod = argoCDCM.Data[settingsResourceTrackingMethodKey] } // validateExternalURL ensures the external URL that is set on the configmap is valid