Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions controller/appcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1441,9 +1441,9 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
}
now := metav1.Now()

compareResult := ctrl.appStateManager.CompareAppState(app, project, revisions, sources,
compareResult := ctrl.appStateManager.CompareAppStateWithComparisonLevel(app, project, revisions, sources,
refreshType == appv1.RefreshTypeHard,
comparisonLevel == CompareWithLatestForceResolve, localManifests, hasMultipleSources)
comparisonLevel == CompareWithLatestForceResolve, localManifests, hasMultipleSources, comparisonLevel == CompareWithRecent)

for k, v := range compareResult.timings {
logCtx = logCtx.WithField(k, v.Milliseconds())
Expand Down
37 changes: 31 additions & 6 deletions controller/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type managedResource struct {
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string, hasMultipleSources bool) *comparisonResult
CompareAppStateWithComparisonLevel(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string, hasMultipleSources bool, isCompareWithRecent bool) *comparisonResult
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
}

Expand Down Expand Up @@ -282,20 +283,24 @@ func DeduplicateTargetObjects(

// getComparisonSettings will return the system level settings related to the
// diff/normalization process.
func (m *appStateManager) getComparisonSettings() (string, map[string]v1alpha1.ResourceOverride, *settings.ResourcesFilter, error) {
func (m *appStateManager) getComparisonSettings() (string, map[string]v1alpha1.ResourceOverride, *settings.ResourcesFilter, bool, error) {
resourceOverrides, err := m.settingsMgr.GetResourceOverrides()
if err != nil {
return "", nil, nil, err
return "", nil, nil, false, err
}
appLabelKey, err := m.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
return "", nil, nil, err
return "", nil, nil, false, err
}
resFilter, err := m.settingsMgr.GetResourcesFilter()
if err != nil {
return "", nil, nil, err
return "", nil, nil, false, err
}
return appLabelKey, resourceOverrides, resFilter, nil
ignoreResourceUpdatedEnabled, err := m.settingsMgr.GetIsIgnoreResourceUpdatesEnabled()
if err != nil {
return "", nil, nil, false, err
}
return appLabelKey, resourceOverrides, resFilter, ignoreResourceUpdatedEnabled, nil
}

// verifyGnuPGSignature verifies the result of a GnuPG operation for a given git
Expand Down Expand Up @@ -341,8 +346,12 @@ func verifyGnuPGSignature(revision string, project *v1alpha1.AppProject, manifes
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool) *comparisonResult {
return m.CompareAppStateWithComparisonLevel(app, project, revisions, sources, noCache, noRevisionCache, localManifests, hasMultipleSources, false)
}

func (m *appStateManager) CompareAppStateWithComparisonLevel(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool, isCompareWithRecent bool) *comparisonResult {
ts := stats.NewTimingStats()
appLabelKey, resourceOverrides, resFilter, err := m.getComparisonSettings()
appLabelKey, resourceOverrides, resFilter, ignoreResourceUpdatedEnabled, err := m.getComparisonSettings()

ts.AddCheckpoint("settings_ms")

Expand Down Expand Up @@ -520,6 +529,22 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
_, refreshRequested := app.IsRefreshRequested()
noCache = noCache || refreshRequested || app.Status.Expired(m.statusRefreshTimeout) || specChanged || revisionChanged

if isCompareWithRecent && ignoreResourceUpdatedEnabled {
// Although we modify resourceOverrides here, it is not persisted
// since m.getComparisonSettings() is reading from the configMap every time its called
for k, v := range resourceOverrides {
resourceUpdates := v.IgnoreResourceUpdates
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
}
log.Info("Using ignore resource updates as ignoreDifferences on comparing with recent")
}

diffConfigBuilder := argodiff.NewDiffConfigBuilder().
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
WithTracking(appLabelKey, string(trackingMethod))
Expand Down
137 changes: 135 additions & 2 deletions controller/state_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package controller

import (
"bytes"
"encoding/json"
log "github.com/sirupsen/logrus"
"os"
"testing"
"time"
Expand Down Expand Up @@ -281,8 +283,139 @@ func TestCompareAppStateCompareOptionIgnoreExtraneous(t *testing.T) {
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 0)
assert.Len(t, compRes.managedResources, 0)
assert.Len(t, app.Status.Conditions, 0)
}

func TestCompareAppStateIgnoreResourceUpdatesWhenComparingWithRecent(t *testing.T) {
log.SetLevel(log.DebugLevel)

t.Run("Modified app should be OutOfSync", func(t *testing.T) {
// given
//t.Parallel()
obj1 := NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
app := newFakeApp()
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, obj1)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(obj1): obj1,
},
}

// Modify the live object to have a different cpu request
obj1.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})["cpu"] = 0.5

sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppStateWithComparisonLevel(app, &defaultProj, revisions, sources, false, false, nil, false, true)

// then
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.Len(t, app.Status.Conditions, 0)
})

t.Run("Modified app ignored by ignoreDifferences should be Synced", func(t *testing.T) {
var logBuffer bytes.Buffer
log.SetOutput(&logBuffer)
defer func() {
log.SetOutput(log.StandardLogger().Out) // Restore the original logger output
}()

// given
obj1 := NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
app := newFakeApp()
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, obj1)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(obj1): obj1,
},
}

// Modify the live object to have a different cpu request
obj1.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})["cpu"] = 0.5

sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
data.configMapData = map[string]string{
"resource.ignoreResourceUpdatesEnabled": "true", // test our modification doesn't affect ignoreDifferences
"resource.customizations.ignoreDifferences.all": `jsonPointers:
- /spec/containers/0/resources/requests/cpu`,
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppStateWithComparisonLevel(app, &defaultProj, revisions, sources, false, false, nil, false, true)

// then
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.Len(t, app.Status.Conditions, 0)
assert.Contains(t, logBuffer.String(), "Using ignore resource updates as ignoreDifferences on comparing with recent")
})

t.Run("Modified app ignored by ignoreResourceUpdates should be Synced", func(t *testing.T) {
var logBuffer bytes.Buffer
log.SetOutput(&logBuffer)
defer func() {
log.SetOutput(log.StandardLogger().Out) // Restore the original logger output
}()
// given
obj1 := NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
app := newFakeApp()
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, obj1)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(obj1): obj1,
},
}

// Modify the live object to have a different cpu request
obj1.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})["cpu"] = 0.5

sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
data.configMapData = map[string]string{
"resource.ignoreResourceUpdatesEnabled": "true",
"resource.customizations.ignoreResourceUpdates.all": `jsonPointers:
- /spec/containers/0/resources/requests/cpu`,
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppStateWithComparisonLevel(app, &defaultProj, revisions, sources, false, false, nil, false, true)

// then
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.Len(t, app.Status.Conditions, 0)
assert.Contains(t, logBuffer.String(), "Using ignore resource updates as ignoreDifferences on comparing with recent")
})
}

// TestCompareAppStateExtraHook tests when there is an extra _hook_ object in live but not defined in git
Expand Down
11 changes: 11 additions & 0 deletions docs/operator-manual/reconcile.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ data:
- /status
```

### Using ignoreResourceUpdates with managedFieldsManagers
It is possible to use `ignoreResourceUpdates` to ignore resource updates from fields owned by specific managers defined in `metadata.managedFields` in live resources.
The following example will ignore resource updates on all fields owned by `kube-controller-manager` for all resources:

```yaml
data:
resource.customizations.ignoreResourceUpdates.all: |
managedFieldsManagers:
- kube-controller-manager
```

### 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,
Expand Down