mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 17:07:16 +00:00
* feat: ignore watched resource update Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * add doc and CLI Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * update doc index Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * add command Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * codegen Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * revert formatting Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * do not skip on health change Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * update doc Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * update logging to use context Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * fix typos. local build broken... Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * change after review Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * manifestHash to string Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * more doc Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * example for argoproj Application Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * add unit test for ignored logs Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * codegen Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * Update docs/operator-manual/reconcile.md Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * move hash and set log to debug Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * Update util/settings/settings.go Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * Update util/settings/settings.go Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * feature flag Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * fix Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * less aggressive managedFields ignore rule Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * Update docs/operator-manual/reconcile.md Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * use local settings Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * latest settings Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * safety first Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * since it's behind a feature flag, go aggressive on overrides Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> --------- Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
This commit is contained in:
parent
771012bb65
commit
88994ea5cd
24 changed files with 1508 additions and 676 deletions
1
USERS.md
1
USERS.md
|
|
@ -101,6 +101,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||
1. [Glovo](https://www.glovoapp.com)
|
||||
1. [GMETRI](https://gmetri.com/)
|
||||
1. [Gojek](https://www.gojek.io/)
|
||||
1. [GoTo](https://www.goto.com/)
|
||||
1. [GoTo Financial](https://gotofinancial.com/)
|
||||
1. [Greenpass](https://www.greenpass.com.br/)
|
||||
1. [Gridfuse](https://gridfuse.com/)
|
||||
|
|
|
|||
|
|
@ -8007,6 +8007,9 @@
|
|||
"ignoreDifferences": {
|
||||
"$ref": "#/definitions/v1alpha1OverrideIgnoreDiff"
|
||||
},
|
||||
"ignoreResourceUpdates": {
|
||||
"$ref": "#/definitions/v1alpha1OverrideIgnoreDiff"
|
||||
},
|
||||
"knownTypeFields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
|||
|
|
@ -349,6 +349,7 @@ func NewResourceOverridesCommand(cmdCtx commandContext) *cobra.Command {
|
|||
},
|
||||
}
|
||||
command.AddCommand(NewResourceIgnoreDifferencesCommand(cmdCtx))
|
||||
command.AddCommand(NewResourceIgnoreResourceUpdatesCommand(cmdCtx))
|
||||
command.AddCommand(NewResourceActionListCommand(cmdCtx))
|
||||
command.AddCommand(NewResourceActionRunCommand(cmdCtx))
|
||||
command.AddCommand(NewResourceHealthCommand(cmdCtx))
|
||||
|
|
@ -380,6 +381,31 @@ func executeResourceOverrideCommand(ctx context.Context, cmdCtx commandContext,
|
|||
callback(res, override, overrides)
|
||||
}
|
||||
|
||||
func executeIgnoreResourceUpdatesOverrideCommand(ctx context.Context, cmdCtx commandContext, args []string, callback func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride)) {
|
||||
data, err := os.ReadFile(args[0])
|
||||
errors.CheckError(err)
|
||||
|
||||
res := unstructured.Unstructured{}
|
||||
errors.CheckError(yaml.Unmarshal(data, &res))
|
||||
|
||||
settingsManager, err := cmdCtx.createSettingsManager(ctx)
|
||||
errors.CheckError(err)
|
||||
|
||||
overrides, err := settingsManager.GetIgnoreResourceUpdatesOverrides()
|
||||
errors.CheckError(err)
|
||||
gvk := res.GroupVersionKind()
|
||||
key := gvk.Kind
|
||||
if gvk.Group != "" {
|
||||
key = fmt.Sprintf("%s/%s", gvk.Group, gvk.Kind)
|
||||
}
|
||||
override, hasOverride := overrides[key]
|
||||
if !hasOverride {
|
||||
_, _ = fmt.Printf("No overrides configured for '%s/%s'\n", gvk.Group, gvk.Kind)
|
||||
return
|
||||
}
|
||||
callback(res, override, overrides)
|
||||
}
|
||||
|
||||
func NewResourceIgnoreDifferencesCommand(cmdCtx commandContext) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "ignore-differences RESOURCE_YAML_PATH",
|
||||
|
|
@ -430,6 +456,52 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
|
|||
return command
|
||||
}
|
||||
|
||||
func NewResourceIgnoreResourceUpdatesCommand(cmdCtx commandContext) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "ignore-resource-updates RESOURCE_YAML_PATH",
|
||||
Short: "Renders fields excluded from resource updates",
|
||||
Long: "Renders ignored fields using the 'ignoreResourceUpdates' setting specified in the 'resource.customizations' field of 'argocd-cm' ConfigMap",
|
||||
Example: `
|
||||
argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml --argocd-cm-path ./argocd-cm.yaml`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
if len(args) < 1 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
executeIgnoreResourceUpdatesOverrideCommand(ctx, cmdCtx, args, func(res unstructured.Unstructured, override v1alpha1.ResourceOverride, overrides map[string]v1alpha1.ResourceOverride) {
|
||||
gvk := res.GroupVersionKind()
|
||||
if len(override.IgnoreResourceUpdates.JSONPointers) == 0 && len(override.IgnoreResourceUpdates.JQPathExpressions) == 0 {
|
||||
_, _ = fmt.Printf("Ignore resource updates are not configured for '%s/%s'\n", gvk.Group, gvk.Kind)
|
||||
return
|
||||
}
|
||||
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
|
||||
errors.CheckError(err)
|
||||
|
||||
normalizedRes := res.DeepCopy()
|
||||
logs := collectLogs(func() {
|
||||
errors.CheckError(normalizer.Normalize(normalizedRes))
|
||||
})
|
||||
if logs != "" {
|
||||
_, _ = fmt.Println(logs)
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(&res, normalizedRes) {
|
||||
_, _ = fmt.Printf("No fields are ignored by ignoreResourceUpdates settings: \n%s\n", override.IgnoreResourceUpdates)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = fmt.Printf("Following fields are ignored:\n\n")
|
||||
_ = cli.PrintDiff(res.GetName(), &res, normalizedRes)
|
||||
})
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
func NewResourceHealthCommand(cmdCtx commandContext) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "health RESOURCE_YAML_PATH",
|
||||
|
|
|
|||
|
|
@ -359,17 +359,18 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
|
|||
level = CompareWithRecent
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var resKey string
|
||||
if ref.Namespace != "" {
|
||||
resKey = ref.Namespace + "/" + ref.Name
|
||||
} else {
|
||||
resKey = "(cluster-scoped)/" + ref.Name
|
||||
}
|
||||
log.Debugf("Refreshing app %s for change in cluster of object %s of type %s/%s", appKey, resKey, ref.APIVersion, ref.Kind)
|
||||
namespace := ref.Namespace
|
||||
if ref.Namespace == "" {
|
||||
namespace = "(cluster-scoped)"
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"application": appKey,
|
||||
"level": level,
|
||||
"namespace": namespace,
|
||||
"name": ref.Name,
|
||||
"api-version": ref.APIVersion,
|
||||
"kind": ref.Kind,
|
||||
}).Debug("Requesting app refresh caused by object update")
|
||||
|
||||
ctrl.requestAppRefresh(app.QualifiedName(), &level, nil)
|
||||
}
|
||||
|
|
|
|||
75
controller/cache/cache.go
vendored
75
controller/cache/cache.go
vendored
|
|
@ -29,6 +29,7 @@ import (
|
|||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
|
|
@ -149,6 +150,8 @@ type ResourceInfo struct {
|
|||
PodInfo *PodInfo
|
||||
// NodeInfo is available for nodes only
|
||||
NodeInfo *NodeInfo
|
||||
|
||||
manifestHash string
|
||||
}
|
||||
|
||||
func NewLiveStateCache(
|
||||
|
|
@ -178,6 +181,11 @@ type cacheSettings struct {
|
|||
clusterSettings clustercache.Settings
|
||||
appInstanceLabelKey string
|
||||
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 {
|
||||
|
|
@ -200,6 +208,14 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceUpdatesOverrides, err := c.settingsMgr.GetIgnoreResourceUpdatesOverrides()
|
||||
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
|
||||
|
|
@ -212,7 +228,8 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
|
|||
ResourceHealthOverride: lua.ResourceHealthOverrides(resourceOverrides),
|
||||
ResourcesFilter: resourcesFilter,
|
||||
}
|
||||
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr)}, nil
|
||||
|
||||
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), resourceUpdatesOverrides, ignoreResourceUpdatesEnabled}, nil
|
||||
}
|
||||
|
||||
func asResourceNode(r *clustercache.Resource) appv1.ResourceNode {
|
||||
|
|
@ -309,6 +326,27 @@ func skipAppRequeuing(key kube.ResourceKey) bool {
|
|||
return ignoredRefreshResources[key.Group+"/"+key.Kind]
|
||||
}
|
||||
|
||||
func skipResourceUpdate(oldInfo, newInfo *ResourceInfo) bool {
|
||||
if oldInfo == nil || newInfo == nil {
|
||||
return false
|
||||
}
|
||||
isSameHealthStatus := (oldInfo.Health == nil && newInfo.Health == nil) || oldInfo.Health != nil && newInfo.Health != nil && oldInfo.Health.Status == newInfo.Health.Status
|
||||
isSameManifest := oldInfo.manifestHash != "" && newInfo.manifestHash != "" && oldInfo.manifestHash == newInfo.manifestHash
|
||||
return isSameHealthStatus && isSameManifest
|
||||
}
|
||||
|
||||
// shouldHashManifest validates if the API resource needs to be hashed.
|
||||
// If there's an app name from resource tracking, or if this is itself an app, we should generate a hash.
|
||||
// Otherwise, the hashing should be skipped to save CPU time.
|
||||
func shouldHashManifest(appName string, gvk schema.GroupVersionKind) bool {
|
||||
// Only hash if the resource belongs to an app.
|
||||
// Best - Only hash for resources that are part of an app or their dependencies
|
||||
// (current) - Only hash for resources that are part of an app + all apps that might be from an ApplicationSet
|
||||
// Orphan - If orphan is enabled, hash should be made on all resource of that namespace and a config to disable it
|
||||
// Worst - Hash all resources watched by Argo
|
||||
return appName != "" || (gvk.Group == application.Group && gvk.Kind == application.ApplicationKind)
|
||||
}
|
||||
|
||||
// isRetryableError is a helper method to see whether an error
|
||||
// returned from the dynamic client is potentially retryable.
|
||||
func isRetryableError(err error) bool {
|
||||
|
|
@ -424,14 +462,25 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
|||
c.lock.RLock()
|
||||
cacheSettings := c.cacheSettings
|
||||
c.lock.RUnlock()
|
||||
|
||||
res.Health, _ = health.GetResourceHealth(un, cacheSettings.clusterSettings.ResourceHealthOverride)
|
||||
|
||||
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod)
|
||||
if isRoot && appName != "" {
|
||||
res.AppName = appName
|
||||
}
|
||||
|
||||
gvk := un.GroupVersionKind()
|
||||
|
||||
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)
|
||||
} else {
|
||||
res.manifestHash = hash
|
||||
}
|
||||
}
|
||||
|
||||
// edge case. we do not label CRDs, so they miss the tracking label we inject. But we still
|
||||
// want the full resource to be available in our cache (to diff), so we store all CRDs
|
||||
return res, res.AppName != "" || gvk.Kind == kube.CustomResourceDefinitionKind
|
||||
|
|
@ -450,6 +499,30 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
|||
} else {
|
||||
ref = oldRes.Ref
|
||||
}
|
||||
|
||||
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 {
|
||||
namespace := ref.Namespace
|
||||
if ref.Namespace == "" {
|
||||
namespace = "(cluster-scoped)"
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"server": clusterCache.GetClusterInfo().Server,
|
||||
"namespace": namespace,
|
||||
"name": ref.Name,
|
||||
"api-version": ref.APIVersion,
|
||||
"kind": ref.Kind,
|
||||
}).Debug("Ignoring change of object because none of the watched resource fields have changed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range []*clustercache.Resource{newRes, oldRes} {
|
||||
if r == nil {
|
||||
continue
|
||||
|
|
|
|||
124
controller/cache/cache_test.go
vendored
124
controller/cache/cache_test.go
vendored
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
|
@ -202,3 +203,126 @@ func Test_asResourceNode_owner_refs(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, expected, resNode)
|
||||
}
|
||||
|
||||
func TestSkipResourceUpdate(t *testing.T) {
|
||||
var (
|
||||
hash1_x string = "x"
|
||||
hash2_y string = "y"
|
||||
hash3_x string = "x"
|
||||
)
|
||||
info := &ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
Message: "default",
|
||||
},
|
||||
}
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(nil, nil))
|
||||
})
|
||||
t.Run("From Nil", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(nil, info))
|
||||
})
|
||||
t.Run("To Nil", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(info, nil))
|
||||
})
|
||||
t.Run("No hash", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(&ResourceInfo{}, &ResourceInfo{}))
|
||||
})
|
||||
t.Run("Same hash", func(t *testing.T) {
|
||||
assert.True(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash value", func(t *testing.T) {
|
||||
assert.True(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
}))
|
||||
})
|
||||
t.Run("Different hash value", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash2_y,
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash, empty health", func(t *testing.T) {
|
||||
assert.True(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{},
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
Health: &health.HealthStatus{},
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash, old health", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy},
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
Health: nil,
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash, new health", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{},
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
},
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash, same health", func(t *testing.T) {
|
||||
assert.True(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
Message: "same",
|
||||
},
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
Message: "same",
|
||||
},
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash, different health status", func(t *testing.T) {
|
||||
assert.False(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
Message: "same",
|
||||
},
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusDegraded,
|
||||
Message: "same",
|
||||
},
|
||||
}))
|
||||
})
|
||||
t.Run("Same hash, different health message", func(t *testing.T) {
|
||||
assert.True(t, skipResourceUpdate(&ResourceInfo{
|
||||
manifestHash: hash1_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
Message: "same",
|
||||
},
|
||||
}, &ResourceInfo{
|
||||
manifestHash: hash3_x,
|
||||
Health: &health.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
Message: "different",
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
27
controller/cache/info.go
vendored
27
controller/cache/info.go
vendored
|
|
@ -3,12 +3,14 @@ package cache
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/text"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
@ -16,6 +18,7 @@ import (
|
|||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/resource"
|
||||
)
|
||||
|
||||
|
|
@ -386,3 +389,27 @@ func populateHostNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
|||
SystemInfo: node.Status.NodeInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (string, error) {
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating normalizer: %w", err)
|
||||
}
|
||||
|
||||
resource := un.DeepCopy()
|
||||
err = normalizer.Normalize(resource)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error normalizing resource: %w", err)
|
||||
}
|
||||
|
||||
data, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error marshaling resource: %w", err)
|
||||
}
|
||||
hash := hash(data)
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func hash(data []byte) string {
|
||||
return strconv.FormatUint(xxhash.Sum64(data), 16)
|
||||
}
|
||||
|
|
|
|||
59
controller/cache/info_test.go
vendored
59
controller/cache/info_test.go
vendored
|
|
@ -694,3 +694,62 @@ func TestCustomLabel(t *testing.T) {
|
|||
assert.Equal(t, "other-label", info.Info[1].Name)
|
||||
assert.Equal(t, "value2", info.Info[1].Value)
|
||||
}
|
||||
|
||||
func TestManifestHash(t *testing.T) {
|
||||
manifest := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: helm-guestbook-pod
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: extensions/v1beta1
|
||||
kind: ReplicaSet
|
||||
name: helm-guestbook-rs
|
||||
resourceVersion: "123"
|
||||
labels:
|
||||
app: guestbook
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- image: bar
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
`)
|
||||
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "*",
|
||||
Kind: "*",
|
||||
JSONPointers: []string{"/metadata/resourceVersion"},
|
||||
},
|
||||
}
|
||||
|
||||
data, _ := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: helm-guestbook-pod
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: extensions/v1beta1
|
||||
kind: ReplicaSet
|
||||
name: helm-guestbook-rs
|
||||
labels:
|
||||
app: guestbook
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- image: bar
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
`).MarshalJSON()
|
||||
|
||||
expected := hash(data)
|
||||
|
||||
hash, err := generateManifestHash(manifest, ignores, nil)
|
||||
assert.Equal(t, expected, hash)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ data:
|
|||
# Configuration to customize resource behavior (optional) can be configured via splitted sub keys.
|
||||
# Keys are in the form: resource.customizations.ignoreDifferences.<group_kind>, resource.customizations.health.<group_kind>
|
||||
# resource.customizations.actions.<group_kind>, resource.customizations.knownTypeFields.<group-kind>
|
||||
# resource.customizations.ignoreResourceUpdates.<group-kind>
|
||||
resource.customizations.ignoreDifferences.admissionregistration.k8s.io_MutatingWebhookConfiguration: |
|
||||
jsonPointers:
|
||||
- /webhooks/0/clientConfig/caBundle
|
||||
|
|
@ -101,6 +102,33 @@ 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:
|
||||
- /metadata/resourceVersion
|
||||
|
||||
# Configuration to define customizations ignoring differences during watched resource updates can be configured via splitted sub key.
|
||||
resource.customizations.ignoreResourceUpdates.argoproj.io_Application: |
|
||||
jsonPointers:
|
||||
- /status
|
||||
|
||||
# jsonPointers and jqPathExpressions can be specified.
|
||||
resource.customizations.ignoreResourceUpdates.autoscaling_HorizontalPodAutoscaler: |
|
||||
jqPathExpressions:
|
||||
- '.metadata.annotations."autoscaling.alpha.kubernetes.io/behavior"'
|
||||
- '.metadata.annotations."autoscaling.alpha.kubernetes.io/conditions"'
|
||||
- '.metadata.annotations."autoscaling.alpha.kubernetes.io/metrics"'
|
||||
- '.metadata.annotations."autoscaling.alpha.kubernetes.io/current-metrics"'
|
||||
jsonPointers:
|
||||
- /metadata/annotations/autoscaling.alpha.kubernetes.io~1behavior
|
||||
- /metadata/annotations/autoscaling.alpha.kubernetes.io~1conditions
|
||||
- /metadata/annotations/autoscaling.alpha.kubernetes.io~1metrics
|
||||
- /metadata/annotations/autoscaling.alpha.kubernetes.io~1current-metrics
|
||||
|
||||
resource.customizations.health.certmanager.k8s.io-Certificate: |
|
||||
hs = {}
|
||||
if obj.status ~= nil then
|
||||
|
|
|
|||
64
docs/operator-manual/reconcile.md
Normal file
64
docs/operator-manual/reconcile.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Reconcile Optimization
|
||||
|
||||
By default, an Argo CD Application is refreshed everytime a resource that belongs to it changes.
|
||||
|
||||
Kubernetes controllers often update the resources they watch periodically, causing continuous reconcile operation on the Application
|
||||
and a high CPU usage on the `argocd-application-controller`. Argo CD allows you to optionally ignore resource updates on specific fields
|
||||
for [tracked resources](../user-guide/resource_tracking.md).
|
||||
|
||||
When a resource update is ignored, if the resource's [health status](./health.md) does not change, the Application that this resource belongs to will not be reconciled.
|
||||
|
||||
## 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 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:
|
||||
resource.customizations.ignoreResourceUpdates.external-secrets.io_ExternalSecret: |
|
||||
jsonPointers:
|
||||
- /status/refreshTime
|
||||
```
|
||||
|
||||
It is possible to configure `ignoreResourceUpdates` to be applied to all tracked resources in every Application managed by an Argo CD instance. In order to do so, resource customizations can be configured like in the example below:
|
||||
|
||||
```yaml
|
||||
data:
|
||||
resource.customizations.ignoreResourceUpdates.all: |
|
||||
jsonPointers:
|
||||
- /status
|
||||
```
|
||||
|
||||
### Using ignoreDifferences to ignore reconcile
|
||||
|
||||
It is possible to use existing system-level `ignoreDifferences` customizations to ignore resource updates as well. Instead of copying all configurations,
|
||||
the `ignoreDifferencesOnResourceUpdates` setting can be used to add all ignored differences as ignored resource updates:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
data:
|
||||
resource.compareoptions: |
|
||||
ignoreDifferencesOnResourceUpdates: true
|
||||
```
|
||||
|
||||
## Default Configuration
|
||||
|
||||
By default, the metadata fields `generation`, `resourceVersion` and `managedFields` are always ignored for all resources.
|
||||
|
||||
## Finding Resources to Ignore
|
||||
|
||||
The application controller logs when a resource change triggers a refresh. You can use these logs to find
|
||||
high-churn resource kinds and then inspect those resources to find which fields to ignore.
|
||||
|
||||
To find these logs, search for `"Requesting app refresh caused by object update"`. The logs include structured
|
||||
fields for `api-version` and `kind`. Counting the number of refreshes triggered, by api-version/kind should
|
||||
reveal the high-churn resource kinds.
|
||||
|
||||
Note that these logs are at the `debug` level. Configure the application-controller's log level to `debug`.
|
||||
|
|
@ -61,6 +61,7 @@ argocd admin settings resource-overrides [flags]
|
|||
* [argocd admin settings](argocd_admin_settings.md) - Provides set of commands for settings validation and troubleshooting
|
||||
* [argocd admin settings resource-overrides health](argocd_admin_settings_resource-overrides_health.md) - Assess resource health
|
||||
* [argocd admin settings resource-overrides ignore-differences](argocd_admin_settings_resource-overrides_ignore-differences.md) - Renders fields excluded from diffing
|
||||
* [argocd admin settings resource-overrides ignore-resource-updates](argocd_admin_settings_resource-overrides_ignore-resource-updates.md) - Renders fields excluded from resource updates
|
||||
* [argocd admin settings resource-overrides list-actions](argocd_admin_settings_resource-overrides_list-actions.md) - List available resource actions
|
||||
* [argocd admin settings resource-overrides run-action](argocd_admin_settings_resource-overrides_run-action.md) - Executes resource action
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
## argocd admin settings resource-overrides ignore-resource-updates
|
||||
|
||||
Renders fields excluded from resource updates
|
||||
|
||||
### Synopsis
|
||||
|
||||
Renders ignored fields using the 'ignoreResourceUpdates' setting specified in the 'resource.customizations' field of 'argocd-cm' ConfigMap
|
||||
|
||||
```
|
||||
argocd admin settings resource-overrides ignore-resource-updates RESOURCE_YAML_PATH [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
|
||||
argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml --argocd-cm-path ./argocd-cm.yaml
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for ignore-resource-updates
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--argocd-cm-path string Path to local argocd-cm.yaml file
|
||||
--argocd-secret-path string Path to local argocd-secret.yaml file
|
||||
--as string Username to impersonate for the operation
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation
|
||||
--auth-token string Authentication token
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-crt string Client certificate file
|
||||
--client-crt-key string Client certificate key file
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--config string Path to Argo CD config (default "/home/user/.config/argocd/config")
|
||||
--context string The name of the kubeconfig context to use
|
||||
--core If set to true then CLI talks directly to Kubernetes instead of talking to Argo CD API server
|
||||
--grpc-web Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2.
|
||||
--grpc-web-root-path string Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2. Set web root.
|
||||
-H, --header strings Sets additional header to all requests made by Argo CD CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers)
|
||||
--http-retry-max int Maximum number of retries to establish http connection to Argo CD server
|
||||
--insecure Skip server certificate and domain verification
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--kube-context string Directs the command to the given kube-context
|
||||
--kubeconfig string Path to a kube config. Only required if out-of-cluster
|
||||
--load-cluster-settings Indicates that config map and secret should be loaded from cluster unless local file path is provided
|
||||
--logformat string Set the logging format. One of: text|json (default "text")
|
||||
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
--password string Password for basic authentication to the API server
|
||||
--plaintext Disable TLS
|
||||
--port-forward Connect to a random argocd-server port using port forwarding
|
||||
--port-forward-namespace string Namespace name which should be used for port forwarding
|
||||
--proxy-url string If provided, this URL will be used to connect via proxy
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--server string The address and port of the Kubernetes API server
|
||||
--server-crt string Server certificate file
|
||||
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
--username string Username for basic authentication to the API server
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [argocd admin settings resource-overrides](argocd_admin_settings_resource-overrides.md) - Troubleshoot resource overrides
|
||||
|
||||
2
go.mod
2
go.mod
|
|
@ -125,7 +125,7 @@ require (
|
|||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0
|
||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
|
|
|
|||
|
|
@ -43,8 +43,9 @@ nav:
|
|||
- operator-manual/tls.md
|
||||
- operator-manual/cluster-bootstrapping.md
|
||||
- operator-manual/secret-management.md
|
||||
- operator-manual/high_availability.md
|
||||
- operator-manual/disaster_recovery.md
|
||||
- operator-manual/high_availability.md
|
||||
- operator-manual/reconcile.md
|
||||
- operator-manual/webhook.md
|
||||
- operator-manual/health.md
|
||||
- operator-manual/resource_actions.md
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/applicat
|
|||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ResourceOverride,Actions
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ResourceOverride,HealthLua
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ResourceOverride,IgnoreDifferences
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ResourceOverride,IgnoreResourceUpdates
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ResourceOverride,KnownTypeFields
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ResourceOverride,UseOpenLibs
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,objectMeta,Name
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1662,6 +1662,8 @@ message ResourceOverride {
|
|||
|
||||
optional OverrideIgnoreDiff ignoreDifferences = 2;
|
||||
|
||||
optional OverrideIgnoreDiff ignoreResourceUpdates = 6;
|
||||
|
||||
repeated KnownTypeField knownTypeFields = 4;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5835,6 +5835,12 @@ func schema_pkg_apis_application_v1alpha1_ResourceOverride(ref common.ReferenceC
|
|||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OverrideIgnoreDiff"),
|
||||
},
|
||||
},
|
||||
"IgnoreResourceUpdates": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OverrideIgnoreDiff"),
|
||||
},
|
||||
},
|
||||
"KnownTypeFields": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
|
|
@ -5849,7 +5855,7 @@ func schema_pkg_apis_application_v1alpha1_ResourceOverride(ref common.ReferenceC
|
|||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"HealthLua", "UseOpenLibs", "Actions", "IgnoreDifferences", "KnownTypeFields"},
|
||||
Required: []string{"HealthLua", "UseOpenLibs", "Actions", "IgnoreDifferences", "IgnoreResourceUpdates", "KnownTypeFields"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
|
|
@ -7457,6 +7463,12 @@ func schema_pkg_apis_application_v1alpha1_rawResourceOverride(ref common.Referen
|
|||
Format: "",
|
||||
},
|
||||
},
|
||||
"ignoreResourceUpdates": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"knownTypeFields": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
|
|
|
|||
|
|
@ -1848,21 +1848,23 @@ type OverrideIgnoreDiff struct {
|
|||
}
|
||||
|
||||
type rawResourceOverride struct {
|
||||
HealthLua string `json:"health.lua,omitempty"`
|
||||
UseOpenLibs bool `json:"health.lua.useOpenLibs,omitempty"`
|
||||
Actions string `json:"actions,omitempty"`
|
||||
IgnoreDifferences string `json:"ignoreDifferences,omitempty"`
|
||||
KnownTypeFields []KnownTypeField `json:"knownTypeFields,omitempty"`
|
||||
HealthLua string `json:"health.lua,omitempty"`
|
||||
UseOpenLibs bool `json:"health.lua.useOpenLibs,omitempty"`
|
||||
Actions string `json:"actions,omitempty"`
|
||||
IgnoreDifferences string `json:"ignoreDifferences,omitempty"`
|
||||
IgnoreResourceUpdates string `json:"ignoreResourceUpdates,omitempty"`
|
||||
KnownTypeFields []KnownTypeField `json:"knownTypeFields,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceOverride holds configuration to customize resource diffing and health assessment
|
||||
// TODO: describe the members of this type
|
||||
type ResourceOverride struct {
|
||||
HealthLua string `protobuf:"bytes,1,opt,name=healthLua"`
|
||||
UseOpenLibs bool `protobuf:"bytes,5,opt,name=useOpenLibs"`
|
||||
Actions string `protobuf:"bytes,3,opt,name=actions"`
|
||||
IgnoreDifferences OverrideIgnoreDiff `protobuf:"bytes,2,opt,name=ignoreDifferences"`
|
||||
KnownTypeFields []KnownTypeField `protobuf:"bytes,4,opt,name=knownTypeFields"`
|
||||
HealthLua string `protobuf:"bytes,1,opt,name=healthLua"`
|
||||
UseOpenLibs bool `protobuf:"bytes,5,opt,name=useOpenLibs"`
|
||||
Actions string `protobuf:"bytes,3,opt,name=actions"`
|
||||
IgnoreDifferences OverrideIgnoreDiff `protobuf:"bytes,2,opt,name=ignoreDifferences"`
|
||||
IgnoreResourceUpdates OverrideIgnoreDiff `protobuf:"bytes,6,opt,name=ignoreResourceUpdates"`
|
||||
KnownTypeFields []KnownTypeField `protobuf:"bytes,4,opt,name=knownTypeFields"`
|
||||
}
|
||||
|
||||
// TODO: describe this method
|
||||
|
|
@ -1875,7 +1877,15 @@ func (s *ResourceOverride) UnmarshalJSON(data []byte) error {
|
|||
s.HealthLua = raw.HealthLua
|
||||
s.UseOpenLibs = raw.UseOpenLibs
|
||||
s.Actions = raw.Actions
|
||||
return yaml.Unmarshal([]byte(raw.IgnoreDifferences), &s.IgnoreDifferences)
|
||||
err := yaml.Unmarshal([]byte(raw.IgnoreDifferences), &s.IgnoreDifferences)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = yaml.Unmarshal([]byte(raw.IgnoreResourceUpdates), &s.IgnoreResourceUpdates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: describe this method
|
||||
|
|
@ -1884,7 +1894,11 @@ func (s ResourceOverride) MarshalJSON() ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw := &rawResourceOverride{s.HealthLua, s.UseOpenLibs, s.Actions, string(ignoreDifferencesData), s.KnownTypeFields}
|
||||
ignoreResourceUpdatesData, err := yaml.Marshal(s.IgnoreResourceUpdates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw := &rawResourceOverride{s.HealthLua, s.UseOpenLibs, s.Actions, string(ignoreDifferencesData), string(ignoreResourceUpdatesData), s.KnownTypeFields}
|
||||
return json.Marshal(raw)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3233,6 +3233,7 @@ func (in *ResourceNode) DeepCopy() *ResourceNode {
|
|||
func (in *ResourceOverride) DeepCopyInto(out *ResourceOverride) {
|
||||
*out = *in
|
||||
in.IgnoreDifferences.DeepCopyInto(&out.IgnoreDifferences)
|
||||
in.IgnoreResourceUpdates.DeepCopyInto(&out.IgnoreResourceUpdates)
|
||||
if in.KnownTypeFields != nil {
|
||||
in, out := &in.KnownTypeFields, &out.KnownTypeFields
|
||||
*out = make([]KnownTypeField, len(*in))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package normalizers
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
|
|
@ -179,7 +180,9 @@ func (n *ignoreNormalizer) Normalize(un *unstructured.Unstructured) error {
|
|||
for _, patch := range matched {
|
||||
patchedDocData, err := patch.Apply(docData)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to apply normalization: %v", err)
|
||||
if shouldLogError(err) {
|
||||
log.Debugf("Failed to apply normalization: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
docData = patchedDocData
|
||||
|
|
@ -191,3 +194,13 @@ func (n *ignoreNormalizer) Normalize(un *unstructured.Unstructured) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldLogError(e error) bool {
|
||||
if strings.Contains(e.Error(), "Unable to remove nonexistent key") {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(e.Error(), "remove operation does not apply: doc is missing path") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package normalizers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -219,3 +221,34 @@ func TestNormalizeJQPathExpressionWithError(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, originalDeployment, normalizedDeployment)
|
||||
}
|
||||
|
||||
func TestNormalizeExpectedErrorAreSilenced(t *testing.T) {
|
||||
normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{
|
||||
"*/*": {
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
||||
JSONPointers: []string{"/invalid", "/invalid/json/path"},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
ignoreNormalizer := normalizer.(*ignoreNormalizer)
|
||||
assert.Len(t, ignoreNormalizer.patches, 2)
|
||||
jsonPatch := ignoreNormalizer.patches[0]
|
||||
jqPatch := ignoreNormalizer.patches[1]
|
||||
|
||||
deployment := test.NewDeployment()
|
||||
deploymentData, err := json.Marshal(deployment)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Error: "error in remove for path: '/invalid': Unable to remove nonexistent key: invalid: missing value"
|
||||
_, err = jsonPatch.Apply(deploymentData)
|
||||
assert.False(t, shouldLogError(err))
|
||||
|
||||
// Error: "remove operation does not apply: doc is missing path: \"/invalid/json/path\": missing value"
|
||||
_, err = jqPatch.Apply(deploymentData)
|
||||
assert.False(t, shouldLogError(err))
|
||||
|
||||
assert.True(t, shouldLogError(fmt.Errorf("An error that should not be ignored")))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -528,6 +530,9 @@ type ArgoCDDiffOptions struct {
|
|||
|
||||
// If set to true then differences caused by status are ignored.
|
||||
IgnoreResourceStatusField IgnoreStatus `json:"ignoreResourceStatusField,omitempty"`
|
||||
|
||||
// If set to true then ignoreDifferences are applied to ignore application refresh on resource updates.
|
||||
IgnoreDifferencesOnResourceUpdates bool `json:"ignoreDifferencesOnResourceUpdates,omitempty"`
|
||||
}
|
||||
|
||||
func (e *incompleteSettingsError) Error() string {
|
||||
|
|
@ -777,6 +782,54 @@ func (mgr *SettingsManager) GetEnabledSourceTypes() (map[string]bool, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (mgr *SettingsManager) GetIgnoreResourceUpdatesOverrides() (map[string]v1alpha1.ResourceOverride, error) {
|
||||
compareOptions, err := mgr.GetResourceCompareOptions()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get compare options: %w", err)
|
||||
}
|
||||
|
||||
resourceOverrides, err := mgr.GetResourceOverrides()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get resource overrides: %w", err)
|
||||
}
|
||||
|
||||
for k, v := range resourceOverrides {
|
||||
resourceUpdates := v.IgnoreResourceUpdates
|
||||
if compareOptions.IgnoreDifferencesOnResourceUpdates {
|
||||
resourceUpdates.JQPathExpressions = append(resourceUpdates.JQPathExpressions, v.IgnoreDifferences.JQPathExpressions...)
|
||||
resourceUpdates.JSONPointers = append(resourceUpdates.JSONPointers, v.IgnoreDifferences.JSONPointers...)
|
||||
resourceUpdates.ManagedFieldsManagers = append(resourceUpdates.ManagedFieldsManagers, v.IgnoreDifferences.ManagedFieldsManagers...)
|
||||
}
|
||||
// Set the IgnoreDifferences because these are the overrides used by Normalizers
|
||||
v.IgnoreDifferences = resourceUpdates
|
||||
v.IgnoreResourceUpdates = v1alpha1.OverrideIgnoreDiff{}
|
||||
resourceOverrides[k] = v
|
||||
}
|
||||
|
||||
if compareOptions.IgnoreDifferencesOnResourceUpdates {
|
||||
log.Info("Using diffing customizations to ignore resource updates")
|
||||
}
|
||||
|
||||
addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/resourceVersion")
|
||||
addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/generation")
|
||||
addIgnoreDiffItemOverrideToGK(resourceOverrides, "*/*", "/metadata/managedFields")
|
||||
|
||||
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()
|
||||
|
|
@ -893,6 +946,13 @@ func (mgr *SettingsManager) appendResourceOverridesFromSplitKeys(cmData map[stri
|
|||
return err
|
||||
}
|
||||
overrideVal.IgnoreDifferences = overrideIgnoreDiff
|
||||
case "ignoreResourceUpdates":
|
||||
overrideIgnoreUpdate := v1alpha1.OverrideIgnoreDiff{}
|
||||
err := yaml.Unmarshal([]byte(v), &overrideIgnoreUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
overrideVal.IgnoreResourceUpdates = overrideIgnoreUpdate
|
||||
case "knownTypeFields":
|
||||
var knownTypeFields []v1alpha1.KnownTypeField
|
||||
err := yaml.Unmarshal([]byte(v), &knownTypeFields)
|
||||
|
|
@ -922,7 +982,7 @@ func convertToOverrideKey(groupKind string) (string, error) {
|
|||
}
|
||||
|
||||
func GetDefaultDiffOptions() ArgoCDDiffOptions {
|
||||
return ArgoCDDiffOptions{IgnoreAggregatedRoles: false}
|
||||
return ArgoCDDiffOptions{IgnoreAggregatedRoles: false, IgnoreDifferencesOnResourceUpdates: false}
|
||||
}
|
||||
|
||||
// GetResourceCompareOptions loads the resource compare options settings from the ConfigMap
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -210,7 +226,12 @@ func TestGetResourceOverrides(t *testing.T) {
|
|||
jsonPointers:
|
||||
- /webhooks/0/clientConfig/caBundle
|
||||
jqPathExpressions:
|
||||
- .webhooks[0].clientConfig.caBundle`,
|
||||
- .webhooks[0].clientConfig.caBundle
|
||||
ignoreResourceUpdates: |
|
||||
jsonPointers:
|
||||
- /webhooks/1/clientConfig/caBundle
|
||||
jqPathExpressions:
|
||||
- .webhooks[1].clientConfig.caBundle`,
|
||||
})
|
||||
overrides, err := settingsManager.GetResourceOverrides()
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -223,6 +244,10 @@ func TestGetResourceOverrides(t *testing.T) {
|
|||
JSONPointers: []string{"/webhooks/0/clientConfig/caBundle"},
|
||||
JQPathExpressions: []string{".webhooks[0].clientConfig.caBundle"},
|
||||
},
|
||||
IgnoreResourceUpdates: v1alpha1.OverrideIgnoreDiff{
|
||||
JSONPointers: []string{"/webhooks/1/clientConfig/caBundle"},
|
||||
JQPathExpressions: []string{".webhooks[1].clientConfig.caBundle"},
|
||||
},
|
||||
}, webHookOverrides)
|
||||
|
||||
// by default, crd status should be ignored
|
||||
|
|
@ -324,6 +349,9 @@ func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|||
ignoreDifferences: |
|
||||
jsonPointers:
|
||||
- foo
|
||||
ignoreResourceUpdates: |
|
||||
jsonPointers:
|
||||
- foo
|
||||
certmanager.k8s.io/Certificate:
|
||||
health.lua.useOpenLibs: true
|
||||
health.lua: |
|
||||
|
|
@ -346,6 +374,8 @@ func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|||
assert.Equal(t, 2, len(overrides[crdGK].IgnoreDifferences.JSONPointers))
|
||||
assert.Equal(t, 1, len(overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers))
|
||||
assert.Equal(t, "foo", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers[0])
|
||||
assert.Equal(t, 1, len(overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers))
|
||||
assert.Equal(t, "foo", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers[0])
|
||||
assert.Equal(t, "foo\n", overrides["certmanager.k8s.io/Certificate"].HealthLua)
|
||||
assert.Equal(t, true, overrides["certmanager.k8s.io/Certificate"].UseOpenLibs)
|
||||
assert.Equal(t, "foo\n", overrides["cert-manager.io/Certificate"].HealthLua)
|
||||
|
|
@ -357,6 +387,8 @@ func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|||
newData := map[string]string{
|
||||
"resource.customizations.health.admissionregistration.k8s.io_MutatingWebhookConfiguration": "bar",
|
||||
"resource.customizations.ignoreDifferences.admissionregistration.k8s.io_MutatingWebhookConfiguration": `jsonPointers:
|
||||
- bar`,
|
||||
"resource.customizations.ignoreResourceUpdates.admissionregistration.k8s.io_MutatingWebhookConfiguration": `jsonPointers:
|
||||
- bar`,
|
||||
"resource.customizations.knownTypeFields.admissionregistration.k8s.io_MutatingWebhookConfiguration": `
|
||||
- field: foo
|
||||
|
|
@ -373,9 +405,13 @@ func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|||
- bar`,
|
||||
"resource.customizations.ignoreDifferences.apps_Deployment": `jqPathExpressions:
|
||||
- bar`,
|
||||
"resource.customizations.ignoreDifferences.all": `managedFieldsManagers:
|
||||
"resource.customizations.ignoreDifferences.all": `managedFieldsManagers:
|
||||
- kube-controller-manager
|
||||
- argo-rollouts`,
|
||||
"resource.customizations.ignoreResourceUpdates.iam-manager.k8s.io_Iamrole": `jsonPointers:
|
||||
- bar`,
|
||||
"resource.customizations.ignoreResourceUpdates.apps_Deployment": `jqPathExpressions:
|
||||
- bar`,
|
||||
}
|
||||
crdGK := "apiextensions.k8s.io/CustomResourceDefinition"
|
||||
|
||||
|
|
@ -389,6 +425,8 @@ func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|||
assert.Equal(t, "/spec/preserveUnknownFields", overrides[crdGK].IgnoreDifferences.JSONPointers[1])
|
||||
assert.Equal(t, 1, len(overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers))
|
||||
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers[0])
|
||||
assert.Equal(t, 1, len(overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers))
|
||||
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers[0])
|
||||
assert.Equal(t, 1, len(overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].KnownTypeFields))
|
||||
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].KnownTypeFields[0].Type)
|
||||
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].HealthLua)
|
||||
|
|
@ -406,6 +444,9 @@ func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|||
assert.Equal(t, 2, len(overrides["*/*"].IgnoreDifferences.ManagedFieldsManagers))
|
||||
assert.Equal(t, "kube-controller-manager", overrides["*/*"].IgnoreDifferences.ManagedFieldsManagers[0])
|
||||
assert.Equal(t, "argo-rollouts", overrides["*/*"].IgnoreDifferences.ManagedFieldsManagers[1])
|
||||
assert.Equal(t, 1, len(overrides["iam-manager.k8s.io/Iamrole"].IgnoreResourceUpdates.JSONPointers))
|
||||
assert.Equal(t, 1, len(overrides["apps/Deployment"].IgnoreResourceUpdates.JQPathExpressions))
|
||||
assert.Equal(t, "bar", overrides["apps/Deployment"].IgnoreResourceUpdates.JQPathExpressions[0])
|
||||
})
|
||||
|
||||
t.Run("SplitKeysCompareOptionsAll", func(t *testing.T) {
|
||||
|
|
@ -451,6 +492,64 @@ func mergemaps(mapA map[string]string, mapB map[string]string) map[string]string
|
|||
return mapB
|
||||
}
|
||||
|
||||
func TestGetIgnoreResourceUpdatesOverrides(t *testing.T) {
|
||||
allDefault := v1alpha1.ResourceOverride{IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
||||
JSONPointers: []string{"/metadata/resourceVersion", "/metadata/generation", "/metadata/managedFields"},
|
||||
}}
|
||||
allGK := "*/*"
|
||||
|
||||
testCustomizations := map[string]string{
|
||||
"resource.customizations": `
|
||||
admissionregistration.k8s.io/MutatingWebhookConfiguration:
|
||||
ignoreDifferences: |
|
||||
jsonPointers:
|
||||
- /webhooks/0/clientConfig/caBundle
|
||||
jqPathExpressions:
|
||||
- .webhooks[0].clientConfig.caBundle
|
||||
ignoreResourceUpdates: |
|
||||
jsonPointers:
|
||||
- /webhooks/1/clientConfig/caBundle
|
||||
jqPathExpressions:
|
||||
- .webhooks[1].clientConfig.caBundle`,
|
||||
}
|
||||
|
||||
_, settingsManager := fixtures(testCustomizations)
|
||||
overrides, err := settingsManager.GetIgnoreResourceUpdatesOverrides()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// default overrides should always be present
|
||||
allOverrides := overrides[allGK]
|
||||
assert.NotNil(t, allOverrides)
|
||||
assert.Equal(t, allDefault, allOverrides)
|
||||
|
||||
// without ignoreDifferencesOnResourceUpdates, only ignoreResourceUpdates should be added
|
||||
assert.NotNil(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
||||
assert.Equal(t, v1alpha1.ResourceOverride{
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
||||
JSONPointers: []string{"/webhooks/1/clientConfig/caBundle"},
|
||||
JQPathExpressions: []string{".webhooks[1].clientConfig.caBundle"},
|
||||
},
|
||||
IgnoreResourceUpdates: v1alpha1.OverrideIgnoreDiff{},
|
||||
}, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
||||
|
||||
// with ignoreDifferencesOnResourceUpdates, ignoreDifferences should be added
|
||||
_, settingsManager = fixtures(mergemaps(testCustomizations, map[string]string{
|
||||
"resource.compareoptions": `
|
||||
ignoreDifferencesOnResourceUpdates: true`,
|
||||
}))
|
||||
overrides, err = settingsManager.GetIgnoreResourceUpdatesOverrides()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
||||
assert.Equal(t, v1alpha1.ResourceOverride{
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
||||
JSONPointers: []string{"/webhooks/1/clientConfig/caBundle", "/webhooks/0/clientConfig/caBundle"},
|
||||
JQPathExpressions: []string{".webhooks[1].clientConfig.caBundle", ".webhooks[0].clientConfig.caBundle"},
|
||||
},
|
||||
IgnoreResourceUpdates: v1alpha1.OverrideIgnoreDiff{},
|
||||
}, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
||||
}
|
||||
|
||||
func TestConvertToOverrideKey(t *testing.T) {
|
||||
key, err := convertToOverrideKey("cert-manager.io_Certificate")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -488,6 +587,26 @@ func TestGetResourceCompareOptions(t *testing.T) {
|
|||
assert.False(t, compareOptions.IgnoreAggregatedRoles)
|
||||
}
|
||||
|
||||
// ignoreDifferencesOnResourceUpdates is true
|
||||
{
|
||||
_, settingsManager := fixtures(map[string]string{
|
||||
"resource.compareoptions": "ignoreDifferencesOnResourceUpdates: true",
|
||||
})
|
||||
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
||||
}
|
||||
|
||||
// ignoreDifferencesOnResourceUpdates is false
|
||||
{
|
||||
_, settingsManager := fixtures(map[string]string{
|
||||
"resource.compareoptions": "ignoreDifferencesOnResourceUpdates: false",
|
||||
})
|
||||
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
||||
}
|
||||
|
||||
// The empty resource.compareoptions should result in default being returned
|
||||
{
|
||||
_, settingsManager := fixtures(map[string]string{
|
||||
|
|
@ -497,6 +616,7 @@ func TestGetResourceCompareOptions(t *testing.T) {
|
|||
defaultOptions := GetDefaultDiffOptions()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, defaultOptions.IgnoreAggregatedRoles, compareOptions.IgnoreAggregatedRoles)
|
||||
assert.Equal(t, defaultOptions.IgnoreDifferencesOnResourceUpdates, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
||||
}
|
||||
|
||||
// resource.compareoptions not defined - should result in default being returned
|
||||
|
|
@ -506,6 +626,7 @@ func TestGetResourceCompareOptions(t *testing.T) {
|
|||
defaultOptions := GetDefaultDiffOptions()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, defaultOptions.IgnoreAggregatedRoles, compareOptions.IgnoreAggregatedRoles)
|
||||
assert.Equal(t, defaultOptions.IgnoreDifferencesOnResourceUpdates, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue