diff --git a/controller/cache/cache.go b/controller/cache/cache.go index 8a08c8cd39be3..797163be2e4c5 100644 --- a/controller/cache/cache.go +++ b/controller/cache/cache.go @@ -183,6 +183,9 @@ type cacheSettings struct { trackingMethod appv1.TrackingMethod // resourceOverrides provides a list of ignored differences to ignore watched resource updates resourceOverrides map[string]appv1.ResourceOverride + + // ignoreResourceUpdates is a flag to enable resource-ignore rules. + ignoreResourceUpdatesEnabled bool } type liveStateCache struct { @@ -209,6 +212,10 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) { if err != nil { return nil, err } + ignoreResourceUpdatesEnabled, err := c.settingsMgr.GetIsIgnoreResourceUpdatesEnabled() + if err != nil { + return nil, err + } resourcesFilter, err := c.settingsMgr.GetResourcesFilter() if err != nil { return nil, err @@ -222,7 +229,7 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) { ResourcesFilter: resourcesFilter, } - return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), resourceUpdatesOverrides}, nil + return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), resourceUpdatesOverrides, ignoreResourceUpdatesEnabled}, nil } func asResourceNode(r *clustercache.Resource) appv1.ResourceNode { @@ -462,9 +469,10 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e if isRoot && appName != "" { res.AppName = appName } + gvk := un.GroupVersionKind() - if shouldHashManifest(appName, gvk) { + if cacheSettings.ignoreResourceUpdatesEnabled && shouldHashManifest(appName, gvk) { hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides) if err != nil { log.Errorf("Failed to generate manifest hash: %v", err) @@ -492,7 +500,11 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e ref = oldRes.Ref } - if oldRes != nil && newRes != nil && skipResourceUpdate(resInfo(oldRes), resInfo(newRes)) { + c.lock.RLock() + cacheSettings := c.cacheSettings + c.lock.RUnlock() + + if cacheSettings.ignoreResourceUpdatesEnabled && oldRes != nil && newRes != nil && skipResourceUpdate(resInfo(oldRes), resInfo(newRes)) { // Additional check for debug level so we don't need to evaluate the // format string in case of non-debug scenarios if log.GetLevel() >= log.DebugLevel { diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml index 0d66f8de3ebd7..748471498798a 100644 --- a/docs/operator-manual/argocd-cm.yaml +++ b/docs/operator-manual/argocd-cm.yaml @@ -102,6 +102,10 @@ data: jsonPointers: - /spec/replicas + # Enable resource.customizations.ignoreResourceUpdates rules. If "false," those rules are not applied, and all updates + # to resources are applied to the cluster cache. Default is false. + resource.ignoreResourceUpdatesEnabled: "false" + # Configuration to define customizations ignoring differences during watched resource updates to skip application reconciles. resource.customizations.ignoreResourceUpdates.all: | jsonPointers: diff --git a/docs/operator-manual/reconcile.md b/docs/operator-manual/reconcile.md index 6bf7932298d02..a3273c97d9922 100644 --- a/docs/operator-manual/reconcile.md +++ b/docs/operator-manual/reconcile.md @@ -11,8 +11,11 @@ When a resource update is ignored, if the resource's [health status](./health.md ## System-Level Configuration Argo CD allows ignoring resource updates at a specific JSON path, using [RFC6902 JSON patches](https://tools.ietf.org/html/rfc6902) and [JQ path expressions](https://stedolan.github.io/jq/manual/#path(path_expression)). It can be configured for a specified group and kind -in `resource.customizations` key of `argocd-cm` ConfigMap. Following is an example of a customization which ignores the `refreshTime` status field -of an [`ExternalSecret`](https://external-secrets.io/main/api/externalsecret/) resource: +in `resource.customizations` key of the `argocd-cm` ConfigMap. + +The feature is behind a flag. To enable it, set `resource.ignoreResourceUpdatesEnabled` to `"true"` in the `argocd-cm` ConfigMap. + +Following is an example of a customization which ignores the `refreshTime` status field of an [`ExternalSecret`](https://external-secrets.io/main/api/externalsecret/) resource: ```yaml data: diff --git a/util/settings/settings.go b/util/settings/settings.go index 3717fd305f543..1c05a2f9a2f5d 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -421,6 +421,8 @@ const ( resourceExclusionsKey = "resource.exclusions" // resourceInclusions is the key to the list of explicitly watched resources resourceInclusionsKey = "resource.inclusions" + // resourceIgnoreResourceUpdatesEnabledKey is the key to a boolean determining whether the resourceIgnoreUpdates feature is enabled + resourceIgnoreResourceUpdatesEnabledKey = "resource.ignoreResourceUpdatesEnabled" // resourceCustomLabelKey is the key to a custom label to show in node info, if present resourceCustomLabelsKey = "resource.customLabels" // kustomizeBuildOptionsKey is a string of kustomize build parameters @@ -810,11 +812,24 @@ func (mgr *SettingsManager) GetIgnoreResourceUpdatesOverrides() (map[string]v1al addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/resourceVersion") addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/generation") - addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/managedFields") + addIgnoreDiffJQItemOverrideToGK(resourceOverrides, "*/*", ".metadata.managedFields[]?.time") return resourceOverrides, nil } +func (mgr *SettingsManager) GetIsIgnoreResourceUpdatesEnabled() (bool, error) { + argoCDCM, err := mgr.getConfigMap() + if err != nil { + return false, err + } + + if argoCDCM.Data[resourceIgnoreResourceUpdatesEnabledKey] == "" { + return false, nil + } + + return strconv.ParseBool(argoCDCM.Data[resourceIgnoreResourceUpdatesEnabledKey]) +} + // GetResourceOverrides loads Resource Overrides from argocd-cm ConfigMap func (mgr *SettingsManager) GetResourceOverrides() (map[string]v1alpha1.ResourceOverride, error) { argoCDCM, err := mgr.getConfigMap() @@ -886,6 +901,17 @@ func addIgnoreDiffItemOverrideToGK(resourceOverrides map[string]v1alpha1.Resourc } } +func addIgnoreDiffJQItemOverrideToGK(resourceOverrides map[string]v1alpha1.ResourceOverride, groupKind, ignoreItemJQ string) { + if val, ok := resourceOverrides[groupKind]; ok { + val.IgnoreDifferences.JQPathExpressions = append(val.IgnoreDifferences.JQPathExpressions, ignoreItemJQ) + resourceOverrides[groupKind] = val + } else { + resourceOverrides[groupKind] = v1alpha1.ResourceOverride{ + IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JQPathExpressions: []string{ignoreItemJQ}}, + } + } +} + func (mgr *SettingsManager) appendResourceOverridesFromSplitKeys(cmData map[string]string, resourceOverrides map[string]v1alpha1.ResourceOverride) error { for k, v := range cmData { if !strings.HasPrefix(k, resourceCustomizationsKey) { diff --git a/util/settings/settings_test.go b/util/settings/settings_test.go index 9f87d9cb4074d..b8fe3569300f9 100644 --- a/util/settings/settings_test.go +++ b/util/settings/settings_test.go @@ -185,6 +185,22 @@ func TestGetServerRBACLogEnforceEnableKeyDefaultFalse(t *testing.T) { assert.Equal(t, false, serverRBACLogEnforceEnable) } +func TestGetIsIgnoreResourceUpdatesEnabled(t *testing.T) { + _, settingsManager := fixtures(map[string]string{ + "resource.ignoreResourceUpdatesEnabled": "true", + }) + ignoreResourceUpdatesEnabled, err := settingsManager.GetIsIgnoreResourceUpdatesEnabled() + assert.NoError(t, err) + assert.True(t, ignoreResourceUpdatesEnabled) +} + +func TestGetIsIgnoreResourceUpdatesEnabledDefaultFalse(t *testing.T) { + _, settingsManager := fixtures(nil) + ignoreResourceUpdatesEnabled, err := settingsManager.GetIsIgnoreResourceUpdatesEnabled() + assert.NoError(t, err) + assert.False(t, ignoreResourceUpdatesEnabled) +} + func TestGetServerRBACLogEnforceEnableKey(t *testing.T) { _, settingsManager := fixtures(map[string]string{ "server.rbac.log.enforce.enable": "true",