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 }