mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-04-04 15:58:49 +02:00
* feat: added grace period for repo errors to prevent aggressive sync state unknowns Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> * feat: updated docs Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> * fix: e2e test Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> * feat: resolved review comments Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> * feat: update unit test Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> * fix: codegen Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> --------- Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com> Signed-off-by: gdsoumya <44349253+gdsoumya@users.noreply.github.com>
389 lines
12 KiB
Go
389 lines
12 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/argoproj/gitops-engine/pkg/sync"
|
|
"github.com/argoproj/gitops-engine/pkg/sync/common"
|
|
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"github.com/argoproj/argo-cd/v2/controller/testdata"
|
|
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
|
"github.com/argoproj/argo-cd/v2/test"
|
|
"github.com/argoproj/argo-cd/v2/util/argo/diff"
|
|
)
|
|
|
|
func TestPersistRevisionHistory(t *testing.T) {
|
|
app := newFakeApp()
|
|
app.Status.OperationState = nil
|
|
app.Status.History = nil
|
|
|
|
defaultProject := &v1alpha1.AppProject{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Name: "default",
|
|
},
|
|
}
|
|
data := fakeData{
|
|
apps: []runtime.Object{app, defaultProject},
|
|
manifestResponse: &apiclient.ManifestResponse{
|
|
Manifests: []string{},
|
|
Namespace: test.FakeDestNamespace,
|
|
Server: test.FakeClusterURL,
|
|
Revision: "abc123",
|
|
},
|
|
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
|
}
|
|
ctrl := newFakeController(&data, nil)
|
|
|
|
// Sync with source unspecified
|
|
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
|
Sync: &v1alpha1.SyncOperation{},
|
|
}}
|
|
ctrl.appStateManager.SyncAppState(app, opState)
|
|
// Ensure we record spec.source into sync result
|
|
assert.Equal(t, app.Spec.GetSource(), opState.SyncResult.Source)
|
|
|
|
updatedApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(context.Background(), app.Name, v1.GetOptions{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, len(updatedApp.Status.History))
|
|
assert.Equal(t, app.Spec.GetSource(), updatedApp.Status.History[0].Source)
|
|
assert.Equal(t, "abc123", updatedApp.Status.History[0].Revision)
|
|
}
|
|
|
|
func TestPersistManagedNamespaceMetadataState(t *testing.T) {
|
|
app := newFakeApp()
|
|
app.Status.OperationState = nil
|
|
app.Status.History = nil
|
|
app.Spec.SyncPolicy.ManagedNamespaceMetadata = &v1alpha1.ManagedNamespaceMetadata{
|
|
Labels: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
Annotations: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
}
|
|
|
|
defaultProject := &v1alpha1.AppProject{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Name: "default",
|
|
},
|
|
}
|
|
data := fakeData{
|
|
apps: []runtime.Object{app, defaultProject},
|
|
manifestResponse: &apiclient.ManifestResponse{
|
|
Manifests: []string{},
|
|
Namespace: test.FakeDestNamespace,
|
|
Server: test.FakeClusterURL,
|
|
Revision: "abc123",
|
|
},
|
|
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
|
}
|
|
ctrl := newFakeController(&data, nil)
|
|
|
|
// Sync with source unspecified
|
|
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
|
Sync: &v1alpha1.SyncOperation{},
|
|
}}
|
|
ctrl.appStateManager.SyncAppState(app, opState)
|
|
// Ensure we record spec.syncPolicy.managedNamespaceMetadata into sync result
|
|
assert.Equal(t, app.Spec.SyncPolicy.ManagedNamespaceMetadata, opState.SyncResult.ManagedNamespaceMetadata)
|
|
}
|
|
|
|
func TestPersistRevisionHistoryRollback(t *testing.T) {
|
|
app := newFakeApp()
|
|
app.Status.OperationState = nil
|
|
app.Status.History = nil
|
|
defaultProject := &v1alpha1.AppProject{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Name: "default",
|
|
},
|
|
}
|
|
data := fakeData{
|
|
apps: []runtime.Object{app, defaultProject},
|
|
manifestResponse: &apiclient.ManifestResponse{
|
|
Manifests: []string{},
|
|
Namespace: test.FakeDestNamespace,
|
|
Server: test.FakeClusterURL,
|
|
Revision: "abc123",
|
|
},
|
|
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
|
}
|
|
ctrl := newFakeController(&data, nil)
|
|
|
|
// Sync with source specified
|
|
source := v1alpha1.ApplicationSource{
|
|
Helm: &v1alpha1.ApplicationSourceHelm{
|
|
Parameters: []v1alpha1.HelmParameter{
|
|
{
|
|
Name: "test",
|
|
Value: "123",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
|
Sync: &v1alpha1.SyncOperation{
|
|
Source: &source,
|
|
},
|
|
}}
|
|
ctrl.appStateManager.SyncAppState(app, opState)
|
|
// Ensure we record opState's source into sync result
|
|
assert.Equal(t, source, opState.SyncResult.Source)
|
|
|
|
updatedApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(context.Background(), app.Name, v1.GetOptions{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, len(updatedApp.Status.History))
|
|
assert.Equal(t, source, updatedApp.Status.History[0].Source)
|
|
assert.Equal(t, "abc123", updatedApp.Status.History[0].Revision)
|
|
}
|
|
|
|
func TestSyncComparisonError(t *testing.T) {
|
|
app := newFakeApp()
|
|
app.Status.OperationState = nil
|
|
app.Status.History = nil
|
|
|
|
defaultProject := &v1alpha1.AppProject{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Name: "default",
|
|
},
|
|
Spec: v1alpha1.AppProjectSpec{
|
|
SignatureKeys: []v1alpha1.SignatureKey{{KeyID: "test"}},
|
|
},
|
|
}
|
|
data := fakeData{
|
|
apps: []runtime.Object{app, defaultProject},
|
|
manifestResponse: &apiclient.ManifestResponse{
|
|
Manifests: []string{},
|
|
Namespace: test.FakeDestNamespace,
|
|
Server: test.FakeClusterURL,
|
|
Revision: "abc123",
|
|
VerifyResult: "something went wrong",
|
|
},
|
|
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
|
}
|
|
ctrl := newFakeController(&data, nil)
|
|
|
|
// Sync with source unspecified
|
|
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
|
Sync: &v1alpha1.SyncOperation{},
|
|
}}
|
|
t.Setenv("ARGOCD_GPG_ENABLED", "true")
|
|
ctrl.appStateManager.SyncAppState(app, opState)
|
|
|
|
conditions := app.Status.GetConditions(map[v1alpha1.ApplicationConditionType]bool{v1alpha1.ApplicationConditionComparisonError: true})
|
|
assert.NotEmpty(t, conditions)
|
|
assert.Equal(t, "abc123", opState.SyncResult.Revision)
|
|
}
|
|
|
|
func TestAppStateManager_SyncAppState(t *testing.T) {
|
|
type fixture struct {
|
|
project *v1alpha1.AppProject
|
|
application *v1alpha1.Application
|
|
controller *ApplicationController
|
|
}
|
|
|
|
setup := func() *fixture {
|
|
app := newFakeApp()
|
|
app.Status.OperationState = nil
|
|
app.Status.History = nil
|
|
|
|
project := &v1alpha1.AppProject{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Name: "default",
|
|
},
|
|
Spec: v1alpha1.AppProjectSpec{
|
|
SignatureKeys: []v1alpha1.SignatureKey{{KeyID: "test"}},
|
|
},
|
|
}
|
|
data := fakeData{
|
|
apps: []runtime.Object{app, project},
|
|
manifestResponse: &apiclient.ManifestResponse{
|
|
Manifests: []string{},
|
|
Namespace: test.FakeDestNamespace,
|
|
Server: test.FakeClusterURL,
|
|
Revision: "abc123",
|
|
},
|
|
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
|
}
|
|
ctrl := newFakeController(&data, nil)
|
|
|
|
return &fixture{
|
|
project: project,
|
|
application: app,
|
|
controller: ctrl,
|
|
}
|
|
}
|
|
|
|
t.Run("will fail the sync if finds shared resources", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
f := setup()
|
|
syncErrorMsg := "deployment already applied by another application"
|
|
condition := v1alpha1.ApplicationCondition{
|
|
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
|
|
Message: syncErrorMsg,
|
|
}
|
|
f.application.Status.Conditions = append(f.application.Status.Conditions, condition)
|
|
|
|
// Sync with source unspecified
|
|
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
|
Sync: &v1alpha1.SyncOperation{
|
|
Source: &v1alpha1.ApplicationSource{},
|
|
SyncOptions: []string{"FailOnSharedResource=true"},
|
|
},
|
|
}}
|
|
|
|
// when
|
|
f.controller.appStateManager.SyncAppState(f.application, opState)
|
|
|
|
// then
|
|
assert.Equal(t, common.OperationFailed, opState.Phase)
|
|
assert.Contains(t, opState.Message, syncErrorMsg)
|
|
})
|
|
}
|
|
|
|
func TestNormalizeTargetResources(t *testing.T) {
|
|
type fixture struct {
|
|
comparisonResult *comparisonResult
|
|
}
|
|
setup := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
|
|
t.Helper()
|
|
dc, err := diff.NewDiffConfigBuilder().
|
|
WithDiffSettings(ignores, nil, true).
|
|
WithNoCache().
|
|
Build()
|
|
require.NoError(t, err)
|
|
live := test.YamlToUnstructured(testdata.LiveDeploymentYaml)
|
|
target := test.YamlToUnstructured(testdata.TargetDeploymentYaml)
|
|
return &fixture{
|
|
&comparisonResult{
|
|
reconciliationResult: sync.ReconciliationResult{
|
|
Live: []*unstructured.Unstructured{live},
|
|
Target: []*unstructured.Unstructured{target},
|
|
},
|
|
diffConfig: dc,
|
|
},
|
|
}
|
|
}
|
|
t.Run("will modify target resource adding live state in fields it should ignore", func(t *testing.T) {
|
|
// given
|
|
ignore := v1alpha1.ResourceIgnoreDifferences{
|
|
Group: "*",
|
|
Kind: "*",
|
|
ManagedFieldsManagers: []string{"janitor"},
|
|
}
|
|
ignores := []v1alpha1.ResourceIgnoreDifferences{ignore}
|
|
f := setup(t, ignores)
|
|
|
|
// when
|
|
targets, err := normalizeTargetResources(f.comparisonResult)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(targets))
|
|
iksmVersion := targets[0].GetAnnotations()["iksm-version"]
|
|
assert.Equal(t, "2.0", iksmVersion)
|
|
})
|
|
t.Run("will not modify target resource if ignore difference is not configured", func(t *testing.T) {
|
|
// given
|
|
f := setup(t, []v1alpha1.ResourceIgnoreDifferences{})
|
|
|
|
// when
|
|
targets, err := normalizeTargetResources(f.comparisonResult)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(targets))
|
|
iksmVersion := targets[0].GetAnnotations()["iksm-version"]
|
|
assert.Equal(t, "1.0", iksmVersion)
|
|
})
|
|
t.Run("will remove fields from target if not present in live", func(t *testing.T) {
|
|
ignore := v1alpha1.ResourceIgnoreDifferences{
|
|
Group: "apps",
|
|
Kind: "Deployment",
|
|
JSONPointers: []string{"/metadata/annotations/iksm-version"},
|
|
}
|
|
ignores := []v1alpha1.ResourceIgnoreDifferences{ignore}
|
|
f := setup(t, ignores)
|
|
live := f.comparisonResult.reconciliationResult.Live[0]
|
|
unstructured.RemoveNestedField(live.Object, "metadata", "annotations", "iksm-version")
|
|
|
|
// when
|
|
targets, err := normalizeTargetResources(f.comparisonResult)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(targets))
|
|
_, ok := targets[0].GetAnnotations()["iksm-version"]
|
|
assert.False(t, ok)
|
|
})
|
|
t.Run("will correctly normalize with multiple ignore configurations", func(t *testing.T) {
|
|
// given
|
|
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
|
{
|
|
Group: "apps",
|
|
Kind: "Deployment",
|
|
JSONPointers: []string{"/spec/replicas"},
|
|
},
|
|
{
|
|
Group: "*",
|
|
Kind: "*",
|
|
ManagedFieldsManagers: []string{"janitor"},
|
|
},
|
|
}
|
|
f := setup(t, ignores)
|
|
|
|
// when
|
|
targets, err := normalizeTargetResources(f.comparisonResult)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(targets))
|
|
normalized := targets[0]
|
|
iksmVersion, ok := normalized.GetAnnotations()["iksm-version"]
|
|
require.True(t, ok)
|
|
assert.Equal(t, "2.0", iksmVersion)
|
|
replicas, ok, err := unstructured.NestedInt64(normalized.Object, "spec", "replicas")
|
|
require.NoError(t, err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, int64(4), replicas)
|
|
})
|
|
t.Run("will keep new array entries not found in live state if not ignored", func(t *testing.T) {
|
|
t.Skip("limitation in the current implementation")
|
|
// given
|
|
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
|
{
|
|
Group: "apps",
|
|
Kind: "Deployment",
|
|
JQPathExpressions: []string{".spec.template.spec.containers[] | select(.name == \"guestbook-ui\")"},
|
|
},
|
|
}
|
|
f := setup(t, ignores)
|
|
target := test.YamlToUnstructured(testdata.TargetDeploymentNewEntries)
|
|
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
|
|
|
// when
|
|
targets, err := normalizeTargetResources(f.comparisonResult)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(targets))
|
|
containers, ok, err := unstructured.NestedSlice(targets[0].Object, "spec", "template", "spec", "containers")
|
|
require.NoError(t, err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, 2, len(containers))
|
|
})
|
|
}
|