github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/sync_options_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/argoproj/gitops-engine/pkg/health"
     9  	. "github.com/argoproj/gitops-engine/pkg/sync/common"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	corev1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/types"
    15  
    16  	. "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    17  	. "github.com/argoproj/argo-cd/v3/test/e2e/fixture"
    18  	. "github.com/argoproj/argo-cd/v3/test/e2e/fixture/app"
    19  	"github.com/argoproj/argo-cd/v3/util/errors"
    20  )
    21  
    22  // TestSyncWithCreateNamespace verifies that the namespace is created when the
    23  // CreateNamespace=true is provided as part of the normal sync resources
    24  func TestSyncWithCreateNamespace(t *testing.T) {
    25  	newNamespace := getNewNamespace(t)
    26  	defer func() {
    27  		if !t.Skipped() {
    28  			errors.NewHandler(t).FailOnErr(Run("", "kubectl", "delete", "namespace", newNamespace))
    29  		}
    30  	}()
    31  
    32  	Given(t).
    33  		Path(guestbookPath).
    34  		When().
    35  		CreateFromFile(func(app *Application) {
    36  			app.Spec.Destination.Namespace = newNamespace
    37  			app.Spec.SyncPolicy = &SyncPolicy{
    38  				SyncOptions: SyncOptions{
    39  					"CreateNamespace=true",
    40  				},
    41  			}
    42  		}).
    43  		Then().
    44  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
    45  		When().
    46  		Sync().
    47  		Then().
    48  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
    49  		Expect(HealthIs(health.HealthStatusHealthy)).
    50  		Expect(OperationPhaseIs(OperationSucceeded)).
    51  		Expect(ResourceResultNumbering(3))
    52  }
    53  
    54  // TestSyncWithCreateNamespaceAndDryRunError verifies that the namespace is created before the
    55  // DryRun validation is made on the resources, even if the sync fails. This allows transient errors
    56  // to be resolved on sync retries
    57  func TestSyncWithCreateNamespaceAndDryRunError(t *testing.T) {
    58  	newNamespace := getNewNamespace(t)
    59  	defer func() {
    60  		if !t.Skipped() {
    61  			errors.NewHandler(t).FailOnErr(Run("", "kubectl", "delete", "namespace", newNamespace))
    62  		}
    63  	}()
    64  
    65  	Given(t).
    66  		Path("failure-during-sync").
    67  		When().
    68  		CreateFromFile(func(app *Application) {
    69  			app.Spec.Destination.Namespace = newNamespace
    70  			app.Spec.SyncPolicy = &SyncPolicy{
    71  				SyncOptions: SyncOptions{
    72  					"CreateNamespace=true",
    73  				},
    74  			}
    75  		}).
    76  		Then().
    77  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
    78  		When().
    79  		IgnoreErrors().
    80  		Sync().
    81  		Then().
    82  		Expect(OperationPhaseIs(OperationFailed)).
    83  		Expect(ResourceResultNumbering(2)).
    84  		Expect(ResourceResultMatches(ResourceResult{Version: "v1", Kind: "Namespace", Name: newNamespace, Status: ResultCodeSynced, Message: fmt.Sprintf("namespace/%s created", newNamespace), HookPhase: OperationRunning, SyncPhase: SyncPhasePreSync})).
    85  		Expect(ResourceResultMatches(ResourceResult{Version: "v1", Kind: "ServiceAccount", Namespace: newNamespace, Name: "failure-during-sync", Status: ResultCodeSyncFailed, Message: `ServiceAccount "failure-during-sync" is invalid: metadata.labels: Invalid value`, HookPhase: OperationFailed, SyncPhase: SyncPhaseSync}))
    86  }
    87  
    88  // TestSyncOptionsValidateFalse verifies we can disable validation during kubectl apply, using the
    89  // 'argocd.argoproj.io/sync-options: Validate=false' sync option
    90  func TestSyncOptionsValidateFalse(t *testing.T) {
    91  	Given(t).
    92  		Path("sync-options-validate-false").
    93  		When().
    94  		CreateApp().
    95  		Sync().
    96  		Then().
    97  		Expect(OperationPhaseIs(OperationSucceeded))
    98  	// NOTE: it is a bug that we do not detect this as OutOfSync. This is because we
    99  	// are dropping fields as part of remarshalling. See: https://github.com/argoproj/argo-cd/issues/1787
   100  	// Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
   101  }
   102  
   103  // TestSyncOptionsValidateTrue verifies when 'argocd.argoproj.io/sync-options: Validate=false' is
   104  // not present, then validation is performed and we fail during the apply
   105  func TestSyncOptionsValidateTrue(t *testing.T) {
   106  	// k3s does not validate at all, so this test does not work
   107  	if os.Getenv("ARGOCD_E2E_K3S") == "true" {
   108  		t.SkipNow()
   109  	}
   110  	Given(t).
   111  		Path("sync-options-validate-false").
   112  		When().
   113  		IgnoreErrors().
   114  		CreateApp().
   115  		PatchFile("invalid-cm.yaml", `[{"op": "remove", "path": "/metadata/annotations"}]`).
   116  		Sync().
   117  		Then().
   118  		Expect(OperationPhaseIs(OperationFailed))
   119  }
   120  
   121  func TestSyncWithStatusIgnored(t *testing.T) {
   122  	Given(t).
   123  		Path(guestbookPath).
   124  		When().
   125  		And(func() {
   126  			require.NoError(t, SetResourceOverrides(map[string]ResourceOverride{
   127  				"/": {
   128  					IgnoreDifferences: OverrideIgnoreDiff{JSONPointers: []string{"/status"}},
   129  				},
   130  			}))
   131  		}).
   132  		CreateFromFile(func(app *Application) {
   133  			app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: true}}
   134  		}).
   135  		Then().
   136  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   137  		// app should remain synced if git change detected
   138  		When().
   139  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "add", "path": "/status", "value": { "observedGeneration": 1 }}]`).
   140  		Refresh(RefreshTypeNormal).
   141  		Then().
   142  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   143  		// app should remain synced if k8s change detected
   144  		When().
   145  		And(func() {
   146  			errors.NewHandler(t).FailOnErr(KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Patch(t.Context(),
   147  				"guestbook-ui", types.JSONPatchType, []byte(`[{ "op": "replace", "path": "/status/observedGeneration", "value": 2 }]`), metav1.PatchOptions{}))
   148  		}).
   149  		Then().
   150  		Expect(SyncStatusIs(SyncStatusCodeSynced))
   151  }
   152  
   153  func TestSyncWithApplyOutOfSyncOnly(t *testing.T) {
   154  	var ns string
   155  	Given(t).
   156  		Path(guestbookPath).
   157  		ApplyOutOfSyncOnly().
   158  		When().
   159  		CreateFromFile(func(app *Application) {
   160  			ns = app.Spec.Destination.Namespace
   161  		}).
   162  		Then().
   163  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   164  		When().
   165  		Sync().
   166  		Then().
   167  		When().
   168  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "replace", "path": "/spec/replicas", "value": 1 }]`).
   169  		Sync().
   170  		Then().
   171  		// Only one resource should be in sync result
   172  		Expect(ResourceResultNumbering(1)).
   173  		Expect(ResourceResultIs(ResourceResult{Group: "apps", Version: "v1", Kind: "Deployment", Namespace: ns, Name: "guestbook-ui", Message: "deployment.apps/guestbook-ui configured", SyncPhase: SyncPhaseSync, HookPhase: OperationRunning, Status: ResultCodeSynced, Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.2"}}))
   174  }
   175  
   176  func TestSyncWithSkipHook(t *testing.T) {
   177  	SkipOnEnv(t, "OPENSHIFT")
   178  	Given(t).
   179  		Path(guestbookPath).
   180  		When().
   181  		CreateFromFile(func(app *Application) {
   182  			app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: true}}
   183  		}).
   184  		Then().
   185  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   186  		// app should remain synced when app has skipped annotation even if git change detected
   187  		When().
   188  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "add", "path": "/metadata/annotations", "value": { "argocd.argoproj.io/hook": "Skip" }}]`).
   189  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "replace", "path": "/spec/replicas", "value": 1 }]`).
   190  		Refresh(RefreshTypeNormal).
   191  		Then().
   192  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   193  		// app should not remain synced if skipped annotation removed
   194  		When().
   195  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "remove", "path": "/metadata/annotations" }]`).
   196  		Refresh(RefreshTypeNormal).
   197  		Then().
   198  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
   199  }
   200  
   201  func TestSyncWithForceReplace(t *testing.T) {
   202  	Given(t).
   203  		Path(guestbookPath).
   204  		When().
   205  		CreateApp().
   206  		Sync().
   207  		Then().
   208  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   209  		// app having `Replace=true` and `Force=true` annotation should sync succeed if change in immutable field
   210  		When().
   211  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "add", "path": "/metadata/annotations", "value": { "argocd.argoproj.io/sync-options": "Force=true,Replace=true" }}]`).
   212  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "add", "path": "/spec/selector/matchLabels/env", "value": "e2e" }, { "op": "add", "path": "/spec/template/metadata/labels/env", "value": "e2e" }]`).
   213  		PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "replace", "path": "/spec/replicas", "value": 1 }]`).
   214  		Refresh(RefreshTypeNormal).
   215  		Sync().
   216  		Then().
   217  		Expect(SyncStatusIs(SyncStatusCodeSynced))
   218  }
   219  
   220  // Given application is set with --sync-option CreateNamespace=true and --sync-option ServerSideApply=true
   221  //
   222  //		application --dest-namespace exists
   223  //
   224  //	Then, --dest-namespace is created with server side apply
   225  //		  	application is synced and healthy with resource
   226  //		  	application resources created with server side apply in the newly created namespace.
   227  func TestNamespaceCreationWithSSA(t *testing.T) {
   228  	SkipOnEnv(t, "OPENSHIFT")
   229  	namespace := getNewNamespace(t)
   230  	defer func() {
   231  		if !t.Skipped() {
   232  			errors.NewHandler(t).FailOnErr(Run("", "kubectl", "delete", "namespace", namespace))
   233  		}
   234  	}()
   235  
   236  	Given(t).
   237  		Path("guestbook").
   238  		When().
   239  		CreateFromFile(func(app *Application) {
   240  			app.Spec.Destination.Namespace = namespace
   241  			app.Spec.SyncPolicy = &SyncPolicy{
   242  				SyncOptions: SyncOptions{"CreateNamespace=true", "ServerSideApply=true"},
   243  			}
   244  		}).
   245  		Then().
   246  		Expect(NoNamespace(namespace)).
   247  		When().
   248  		Sync().
   249  		Then().
   250  		Expect(Success("")).
   251  		Expect(Namespace(namespace, func(_ *Application, ns *corev1.Namespace) {
   252  			assert.NotContains(t, ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration")
   253  		})).
   254  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   255  		Expect(HealthIs(health.HealthStatusHealthy)).
   256  		Expect(OperationPhaseIs(OperationSucceeded)).
   257  		Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", namespace, health.HealthStatusHealthy)).
   258  		Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", namespace, SyncStatusCodeSynced)).
   259  		Expect(ResourceHealthWithNamespaceIs("Service", "guestbook-ui", namespace, health.HealthStatusHealthy)).
   260  		Expect(ResourceSyncStatusWithNamespaceIs("Service", "guestbook-ui", namespace, SyncStatusCodeSynced))
   261  }