github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/app_management_ns_test.go (about) 1 package e2e 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path" 8 "reflect" 9 "testing" 10 "time" 11 12 "github.com/argoproj/gitops-engine/pkg/diff" 13 "github.com/argoproj/gitops-engine/pkg/health" 14 . "github.com/argoproj/gitops-engine/pkg/sync/common" 15 "github.com/argoproj/gitops-engine/pkg/utils/kube" 16 log "github.com/sirupsen/logrus" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 corev1 "k8s.io/api/core/v1" 20 networkingv1 "k8s.io/api/networking/v1" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 "k8s.io/apimachinery/pkg/runtime/schema" 23 "k8s.io/apimachinery/pkg/types" 24 "k8s.io/apimachinery/pkg/util/intstr" 25 "k8s.io/utils/ptr" 26 27 "github.com/argoproj/argo-cd/v3/common" 28 applicationpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/application" 29 . "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 30 "github.com/argoproj/argo-cd/v3/test/e2e/fixture" 31 accountFixture "github.com/argoproj/argo-cd/v3/test/e2e/fixture/account" 32 . "github.com/argoproj/argo-cd/v3/test/e2e/fixture/app" 33 projectFixture "github.com/argoproj/argo-cd/v3/test/e2e/fixture/project" 34 repoFixture "github.com/argoproj/argo-cd/v3/test/e2e/fixture/repos" 35 "github.com/argoproj/argo-cd/v3/test/e2e/testdata" 36 37 "github.com/argoproj/argo-cd/v3/pkg/apis/application" 38 . "github.com/argoproj/argo-cd/v3/util/argo" 39 "github.com/argoproj/argo-cd/v3/util/errors" 40 utilio "github.com/argoproj/argo-cd/v3/util/io" 41 "github.com/argoproj/argo-cd/v3/util/settings" 42 ) 43 44 // This empty test is here only for clarity, to conform to logs rbac tests structure in account. This exact usecase is covered in the TestAppLogs test 45 func TestNamespacedGetLogsAllow(_ *testing.T) { 46 } 47 48 func TestNamespacedGetLogsDeny(t *testing.T) { 49 fixture.SkipOnEnv(t, "OPENSHIFT") 50 51 accountFixture.Given(t). 52 Name("test"). 53 When(). 54 Create(). 55 Login(). 56 SetPermissions([]fixture.ACL{ 57 { 58 Resource: "applications", 59 Action: "create", 60 Scope: "*", 61 }, 62 { 63 Resource: "applications", 64 Action: "get", 65 Scope: "*", 66 }, 67 { 68 Resource: "applications", 69 Action: "sync", 70 Scope: "*", 71 }, 72 { 73 Resource: "projects", 74 Action: "get", 75 Scope: "*", 76 }, 77 }, "app-creator") 78 79 ctx := GivenWithSameState(t) 80 ctx.SetAppNamespace(fixture.ArgoCDAppNamespace) 81 ctx. 82 Path("guestbook-logs"). 83 SetTrackingMethod("annotation"). 84 When(). 85 CreateApp(). 86 Sync(). 87 Then(). 88 Expect(HealthIs(health.HealthStatusHealthy)). 89 And(func(_ *Application) { 90 _, err := fixture.RunCliWithRetry(5, "app", "logs", ctx.AppQualifiedName(), "--kind", "Deployment", "--group", "", "--name", "guestbook-ui") 91 assert.ErrorContains(t, err, "permission denied") 92 }) 93 } 94 95 func TestNamespacedGetLogsAllowNS(t *testing.T) { 96 fixture.SkipOnEnv(t, "OPENSHIFT") 97 98 accountFixture.Given(t). 99 Name("test"). 100 When(). 101 Create(). 102 Login(). 103 SetPermissions([]fixture.ACL{ 104 { 105 Resource: "applications", 106 Action: "create", 107 Scope: "*", 108 }, 109 { 110 Resource: "applications", 111 Action: "get", 112 Scope: "*", 113 }, 114 { 115 Resource: "applications", 116 Action: "sync", 117 Scope: "*", 118 }, 119 { 120 Resource: "projects", 121 Action: "get", 122 Scope: "*", 123 }, 124 { 125 Resource: "logs", 126 Action: "get", 127 Scope: "*", 128 }, 129 }, "app-creator") 130 131 ctx := GivenWithSameState(t) 132 ctx.SetAppNamespace(fixture.AppNamespace()) 133 ctx. 134 Path("guestbook-logs"). 135 SetTrackingMethod("annotation"). 136 When(). 137 CreateApp(). 138 Sync(). 139 Then(). 140 Expect(HealthIs(health.HealthStatusHealthy)). 141 And(func(_ *Application) { 142 out, err := fixture.RunCliWithRetry(5, "app", "logs", ctx.AppQualifiedName(), "--kind", "Deployment", "--group", "", "--name", "guestbook-ui") 143 require.NoError(t, err) 144 assert.Contains(t, out, "Hi") 145 }). 146 And(func(_ *Application) { 147 out, err := fixture.RunCliWithRetry(5, "app", "logs", ctx.AppQualifiedName(), "--kind", "Pod") 148 require.NoError(t, err) 149 assert.Contains(t, out, "Hi") 150 }). 151 And(func(_ *Application) { 152 out, err := fixture.RunCliWithRetry(5, "app", "logs", ctx.AppQualifiedName(), "--kind", "Service") 153 require.NoError(t, err) 154 assert.NotContains(t, out, "Hi") 155 }) 156 } 157 158 func TestNamespacedSyncToUnsignedCommit(t *testing.T) { 159 fixture.SkipOnEnv(t, "GPG") 160 GivenWithNamespace(t, fixture.AppNamespace()). 161 SetTrackingMethod("annotation"). 162 Project("gpg"). 163 Path(guestbookPath). 164 When(). 165 IgnoreErrors(). 166 CreateApp(). 167 Sync(). 168 Then(). 169 Expect(OperationPhaseIs(OperationError)). 170 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 171 Expect(HealthIs(health.HealthStatusMissing)) 172 } 173 174 func TestNamespacedSyncToSignedCommitWKK(t *testing.T) { 175 fixture.SkipOnEnv(t, "GPG") 176 Given(t). 177 SetAppNamespace(fixture.AppNamespace()). 178 SetTrackingMethod("annotation"). 179 Project("gpg"). 180 Path(guestbookPath). 181 When(). 182 AddSignedFile("test.yaml", "null"). 183 IgnoreErrors(). 184 CreateApp(). 185 Sync(). 186 Then(). 187 Expect(OperationPhaseIs(OperationError)). 188 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 189 Expect(HealthIs(health.HealthStatusMissing)) 190 } 191 192 func TestNamespacedSyncToSignedCommitKWKK(t *testing.T) { 193 fixture.SkipOnEnv(t, "GPG") 194 Given(t). 195 SetAppNamespace(fixture.AppNamespace()). 196 SetTrackingMethod("annotation"). 197 Project("gpg"). 198 Path(guestbookPath). 199 GPGPublicKeyAdded(). 200 Sleep(2). 201 When(). 202 AddSignedFile("test.yaml", "null"). 203 IgnoreErrors(). 204 CreateApp(). 205 Sync(). 206 Then(). 207 Expect(OperationPhaseIs(OperationSucceeded)). 208 Expect(SyncStatusIs(SyncStatusCodeSynced)). 209 Expect(HealthIs(health.HealthStatusHealthy)) 210 } 211 212 func TestNamespacedAppCreation(t *testing.T) { 213 ctx := Given(t) 214 ctx. 215 Path(guestbookPath). 216 SetTrackingMethod("annotation"). 217 SetAppNamespace(fixture.AppNamespace()). 218 When(). 219 CreateApp(). 220 Then(). 221 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 222 And(func(app *Application) { 223 assert.Equal(t, fixture.Name(), app.Name) 224 assert.Equal(t, fixture.AppNamespace(), app.Namespace) 225 assert.Equal(t, fixture.RepoURL(fixture.RepoURLTypeFile), app.Spec.GetSource().RepoURL) 226 assert.Equal(t, guestbookPath, app.Spec.GetSource().Path) 227 assert.Equal(t, fixture.DeploymentNamespace(), app.Spec.Destination.Namespace) 228 assert.Equal(t, KubernetesInternalAPIServerAddr, app.Spec.Destination.Server) 229 }). 230 Expect(NamespacedEvent(fixture.AppNamespace(), EventReasonResourceCreated, "create")). 231 And(func(_ *Application) { 232 // app should be listed 233 output, err := fixture.RunCli("app", "list") 234 require.NoError(t, err) 235 assert.Contains(t, output, ctx.AppQualifiedName()) 236 }). 237 When(). 238 // ensure that create is idempotent 239 CreateApp(). 240 Then(). 241 Given(). 242 Revision("master"). 243 When(). 244 // ensure that update replaces spec and merge labels and annotations 245 And(func() { 246 errors.NewHandler(t).FailOnErr(fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Patch(t.Context(), 247 ctx.GetName(), types.MergePatchType, []byte(`{"metadata": {"labels": { "test": "label" }, "annotations": { "test": "annotation" }}}`), metav1.PatchOptions{})) 248 }). 249 CreateApp("--upsert"). 250 Then(). 251 And(func(app *Application) { 252 assert.Equal(t, "label", app.Labels["test"]) 253 assert.Equal(t, "annotation", app.Annotations["test"]) 254 assert.Equal(t, "master", app.Spec.GetSource().TargetRevision) 255 }) 256 } 257 258 func TestNamespacedAppCreationWithoutForceUpdate(t *testing.T) { 259 ctx := Given(t) 260 261 ctx. 262 Path(guestbookPath). 263 SetTrackingMethod("annotation"). 264 SetAppNamespace(fixture.AppNamespace()). 265 DestName("in-cluster"). 266 When(). 267 CreateApp(). 268 Then(). 269 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 270 And(func(app *Application) { 271 assert.Equal(t, ctx.AppName(), app.Name) 272 assert.Equal(t, fixture.AppNamespace(), app.Namespace) 273 assert.Equal(t, fixture.RepoURL(fixture.RepoURLTypeFile), app.Spec.GetSource().RepoURL) 274 assert.Equal(t, guestbookPath, app.Spec.GetSource().Path) 275 assert.Equal(t, fixture.DeploymentNamespace(), app.Spec.Destination.Namespace) 276 assert.Equal(t, "in-cluster", app.Spec.Destination.Name) 277 }). 278 Expect(NamespacedEvent(fixture.AppNamespace(), EventReasonResourceCreated, "create")). 279 And(func(_ *Application) { 280 // app should be listed 281 output, err := fixture.RunCli("app", "list") 282 require.NoError(t, err) 283 assert.Contains(t, output, ctx.AppQualifiedName()) 284 }). 285 When(). 286 IgnoreErrors(). 287 CreateApp("--dest-server", KubernetesInternalAPIServerAddr). 288 Then(). 289 Expect(Error("", "existing application spec is different, use upsert flag to force update")) 290 } 291 292 func TestNamespacedDeleteAppResource(t *testing.T) { 293 ctx := Given(t) 294 295 ctx. 296 Path(guestbookPath). 297 SetTrackingMethod("annotation"). 298 SetAppNamespace(fixture.AppNamespace()). 299 When(). 300 CreateApp(). 301 Sync(). 302 Then(). 303 Expect(SyncStatusIs(SyncStatusCodeSynced)). 304 And(func(_ *Application) { 305 // app should be listed 306 if _, err := fixture.RunCli("app", "delete-resource", ctx.AppQualifiedName(), "--kind", "Service", "--resource-name", "guestbook-ui"); err != nil { 307 require.NoError(t, err) 308 } 309 }). 310 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 311 Expect(HealthIs(health.HealthStatusMissing)) 312 } 313 314 // demonstrate that we cannot use a standard sync when an immutable field is changed, we must use "force" 315 func TestNamespacedImmutableChange(t *testing.T) { 316 fixture.SkipOnEnv(t, "OPENSHIFT") 317 Given(t). 318 Path("secrets"). 319 SetTrackingMethod("annotation"). 320 SetAppNamespace(fixture.AppNamespace()). 321 When(). 322 CreateApp(). 323 PatchFile("secrets.yaml", `[{"op": "add", "path": "/data/new-field", "value": "dGVzdA=="}, {"op": "add", "path": "/immutable", "value": true}]`). 324 Sync(). 325 Then(). 326 Expect(OperationPhaseIs(OperationSucceeded)). 327 Expect(SyncStatusIs(SyncStatusCodeSynced)). 328 Expect(HealthIs(health.HealthStatusHealthy)). 329 When(). 330 PatchFile("secrets.yaml", `[{"op": "add", "path": "/data/new-field", "value": "dGVzdDI="}]`). 331 IgnoreErrors(). 332 Sync(). 333 DoNotIgnoreErrors(). 334 Then(). 335 Expect(OperationPhaseIs(OperationFailed)). 336 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 337 Expect(ResourceResultNumbering(1)). 338 Expect(ResourceResultMatches(ResourceResult{ 339 Kind: "Secret", 340 Version: "v1", 341 Namespace: fixture.DeploymentNamespace(), 342 Name: "test-secret", 343 SyncPhase: "Sync", 344 Status: "SyncFailed", 345 HookPhase: "Failed", 346 Message: `Secret "test-secret" is invalid`, 347 })). 348 // now we can do this will a force 349 Given(). 350 Force(). 351 When(). 352 Sync(). 353 Then(). 354 Expect(OperationPhaseIs(OperationSucceeded)). 355 Expect(SyncStatusIs(SyncStatusCodeSynced)). 356 Expect(HealthIs(health.HealthStatusHealthy)) 357 } 358 359 func TestNamespacedInvalidAppProject(t *testing.T) { 360 Given(t). 361 SetTrackingMethod("annotation"). 362 Path(guestbookPath). 363 SetAppNamespace(fixture.AppNamespace()). 364 Project("does-not-exist"). 365 When(). 366 IgnoreErrors(). 367 CreateApp(). 368 Then(). 369 // We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic 370 // permission denied error. 371 Expect(Error("", "is not allowed")) 372 } 373 374 func TestNamespacedAppDeletion(t *testing.T) { 375 ctx := Given(t) 376 ctx. 377 Path(guestbookPath). 378 SetTrackingMethod("annotation"). 379 SetAppNamespace(fixture.AppNamespace()). 380 When(). 381 CreateApp(). 382 Then(). 383 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 384 When(). 385 Delete(true). 386 Then(). 387 Expect(DoesNotExist()). 388 Expect(NamespacedEvent(fixture.AppNamespace(), EventReasonResourceDeleted, "delete")) 389 390 output, err := fixture.RunCli("app", "list") 391 require.NoError(t, err) 392 assert.NotContains(t, output, ctx.AppQualifiedName()) 393 } 394 395 func TestNamespacedAppLabels(t *testing.T) { 396 ctx := Given(t) 397 ctx. 398 Path("config-map"). 399 SetTrackingMethod("annotation"). 400 SetAppNamespace(fixture.AppNamespace()). 401 When(). 402 CreateApp("-l", "foo=bar"). 403 Then(). 404 And(func(_ *Application) { 405 assert.Contains(t, errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "list")), ctx.AppQualifiedName()) 406 assert.Contains(t, errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "list", "-l", "foo=bar")), ctx.AppQualifiedName()) 407 assert.NotContains(t, errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "list", "-l", "foo=rubbish")), ctx.AppQualifiedName()) 408 }). 409 Given(). 410 // remove both name and replace labels means nothing will sync 411 Name(""). 412 When(). 413 IgnoreErrors(). 414 Sync("-l", "foo=rubbish"). 415 DoNotIgnoreErrors(). 416 Then(). 417 Expect(Error("", "No matching apps found for filter: selector foo=rubbish")). 418 // check we can update the app and it is then sync'd 419 Given(). 420 When(). 421 Sync("-l", "foo=bar") 422 } 423 424 func TestNamespacedTrackAppStateAndSyncApp(t *testing.T) { 425 Given(t). 426 Path(guestbookPath). 427 SetTrackingMethod("annotation"). 428 SetAppNamespace(fixture.AppNamespace()). 429 When(). 430 CreateApp(). 431 Sync(). 432 Then(). 433 Expect(OperationPhaseIs(OperationSucceeded)). 434 Expect(SyncStatusIs(SyncStatusCodeSynced)). 435 Expect(HealthIs(health.HealthStatusHealthy)). 436 Expect(Success(fmt.Sprintf("Service %s guestbook-ui Synced ", fixture.DeploymentNamespace()))). 437 Expect(Success(fmt.Sprintf("apps Deployment %s guestbook-ui Synced", fixture.DeploymentNamespace()))). 438 Expect(NamespacedEvent(fixture.AppNamespace(), EventReasonResourceUpdated, "sync")). 439 And(func(app *Application) { 440 assert.NotNil(t, app.Status.OperationState.SyncResult) 441 }) 442 } 443 444 func TestNamespacedAppRollbackSuccessful(t *testing.T) { 445 ctx := Given(t) 446 ctx. 447 Path(guestbookPath). 448 SetTrackingMethod("annotation"). 449 SetAppNamespace(fixture.AppNamespace()). 450 When(). 451 CreateApp(). 452 Sync(). 453 Then(). 454 Expect(SyncStatusIs(SyncStatusCodeSynced)). 455 And(func(app *Application) { 456 assert.NotEmpty(t, app.Status.Sync.Revision) 457 }). 458 And(func(app *Application) { 459 appWithHistory := app.DeepCopy() 460 appWithHistory.Status.History = []RevisionHistory{{ 461 ID: 1, 462 Revision: app.Status.Sync.Revision, 463 DeployedAt: metav1.Time{Time: metav1.Now().UTC().Add(-1 * time.Minute)}, 464 Source: app.Spec.GetSource(), 465 }, { 466 ID: 2, 467 Revision: "cdb", 468 DeployedAt: metav1.Time{Time: metav1.Now().UTC().Add(-2 * time.Minute)}, 469 Source: app.Spec.GetSource(), 470 }} 471 patch, _, err := diff.CreateTwoWayMergePatch(app, appWithHistory, &Application{}) 472 require.NoError(t, err) 473 app, err = fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Patch(t.Context(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{}) 474 require.NoError(t, err) 475 476 // sync app and make sure it reaches InSync state 477 _, err = fixture.RunCli("app", "rollback", app.QualifiedName(), "1") 478 require.NoError(t, err) 479 }). 480 Expect(NamespacedEvent(fixture.AppNamespace(), EventReasonOperationStarted, "rollback")). 481 Expect(SyncStatusIs(SyncStatusCodeSynced)). 482 And(func(app *Application) { 483 assert.Equal(t, SyncStatusCodeSynced, app.Status.Sync.Status) 484 require.NotNil(t, app.Status.OperationState.SyncResult) 485 assert.Len(t, app.Status.OperationState.SyncResult.Resources, 2) 486 assert.Equal(t, OperationSucceeded, app.Status.OperationState.Phase) 487 assert.Len(t, app.Status.History, 3) 488 }) 489 } 490 491 func TestNamespacedComparisonFailsIfClusterNotAdded(t *testing.T) { 492 Given(t). 493 Path(guestbookPath). 494 SetTrackingMethod("annotation"). 495 SetAppNamespace(fixture.AppNamespace()). 496 DestServer("https://not-registered-cluster/api"). 497 When(). 498 IgnoreErrors(). 499 CreateApp(). 500 Then(). 501 Expect(DoesNotExist()) 502 } 503 504 func TestNamespacedCannotSetInvalidPath(t *testing.T) { 505 Given(t). 506 Path(guestbookPath). 507 SetTrackingMethod("annotation"). 508 SetAppNamespace(fixture.AppNamespace()). 509 When(). 510 CreateApp(). 511 IgnoreErrors(). 512 AppSet("--path", "garbage"). 513 Then(). 514 Expect(Error("", "app path does not exist")) 515 } 516 517 func TestNamespacedManipulateApplicationResources(t *testing.T) { 518 ctx := Given(t) 519 ctx. 520 Path(guestbookPath). 521 SetTrackingMethod("annotation"). 522 SetAppNamespace(fixture.AppNamespace()). 523 When(). 524 CreateApp(). 525 Sync(). 526 Then(). 527 Expect(SyncStatusIs(SyncStatusCodeSynced)). 528 And(func(app *Application) { 529 manifests, err := fixture.RunCli("app", "manifests", ctx.AppQualifiedName(), "--source", "live") 530 require.NoError(t, err) 531 resources, err := kube.SplitYAML([]byte(manifests)) 532 require.NoError(t, err) 533 534 index := -1 535 for i := range resources { 536 if resources[i].GetKind() == kube.DeploymentKind { 537 index = i 538 break 539 } 540 } 541 assert.Greater(t, index, -1) 542 543 deployment := resources[index] 544 545 closer, client, err := fixture.ArgoCDClientset.NewApplicationClient() 546 require.NoError(t, err) 547 defer utilio.Close(closer) 548 549 _, err = client.DeleteResource(t.Context(), &applicationpkg.ApplicationResourceDeleteRequest{ 550 Name: &app.Name, 551 AppNamespace: ptr.To(fixture.AppNamespace()), 552 Group: ptr.To(deployment.GroupVersionKind().Group), 553 Kind: ptr.To(deployment.GroupVersionKind().Kind), 554 Version: ptr.To(deployment.GroupVersionKind().Version), 555 Namespace: ptr.To(deployment.GetNamespace()), 556 ResourceName: ptr.To(deployment.GetName()), 557 }) 558 require.NoError(t, err) 559 }). 560 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)) 561 } 562 563 func TestNamespacedAppWithSecrets(t *testing.T) { 564 closer, client, err := fixture.ArgoCDClientset.NewApplicationClient() 565 require.NoError(t, err) 566 defer utilio.Close(closer) 567 568 ctx := Given(t) 569 ctx. 570 Path("secrets"). 571 SetAppNamespace(fixture.AppNamespace()). 572 SetTrackingMethod("annotation"). 573 When(). 574 CreateApp(). 575 Sync(). 576 Then(). 577 Expect(SyncStatusIs(SyncStatusCodeSynced)). 578 And(func(app *Application) { 579 res := errors.NewHandler(t).FailOnErr(client.GetResource(t.Context(), &applicationpkg.ApplicationResourceRequest{ 580 Namespace: &app.Spec.Destination.Namespace, 581 AppNamespace: ptr.To(fixture.AppNamespace()), 582 Kind: ptr.To(kube.SecretKind), 583 Group: ptr.To(""), 584 Name: &app.Name, 585 Version: ptr.To("v1"), 586 ResourceName: ptr.To("test-secret"), 587 })).(*applicationpkg.ApplicationResourceResponse) 588 assetSecretDataHidden(t, res.GetManifest()) 589 590 manifests, err := client.GetManifests(t.Context(), &applicationpkg.ApplicationManifestQuery{ 591 Name: &app.Name, 592 AppNamespace: ptr.To(fixture.AppNamespace()), 593 }) 594 require.NoError(t, err) 595 596 for _, manifest := range manifests.Manifests { 597 assetSecretDataHidden(t, manifest) 598 } 599 600 diffOutput := errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "diff", ctx.AppQualifiedName())).(string) 601 assert.Empty(t, diffOutput) 602 603 // make sure resource update error does not print secret details 604 _, err = fixture.RunCli("app", "patch-resource", ctx.AppQualifiedName(), "--resource-name", "test-secret", 605 "--kind", "Secret", "--patch", `{"op": "add", "path": "/data", "value": "hello"}'`, 606 "--patch-type", "application/json-patch+json") 607 require.ErrorContains(t, err, fmt.Sprintf("failed to patch Secret %s/test-secret", fixture.DeploymentNamespace())) 608 assert.NotContains(t, err.Error(), "username") 609 assert.NotContains(t, err.Error(), "password") 610 611 // patch secret and make sure app is out of sync and diff detects the change 612 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.CoreV1().Secrets(fixture.DeploymentNamespace()).Patch(t.Context(), 613 "test-secret", types.JSONPatchType, []byte(`[ 614 {"op": "remove", "path": "/data/username"}, 615 {"op": "add", "path": "/stringData", "value": {"password": "foo"}} 616 ]`), metav1.PatchOptions{})) 617 }). 618 When(). 619 Refresh(RefreshTypeNormal). 620 Then(). 621 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 622 And(func(app *Application) { 623 diffOutput, err := fixture.RunCli("app", "diff", ctx.AppQualifiedName()) 624 require.Error(t, err) 625 assert.Contains(t, diffOutput, "username: ++++++++") 626 assert.Contains(t, diffOutput, "password: ++++++++++++") 627 628 // local diff should ignore secrets 629 diffOutput = errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "diff", ctx.AppQualifiedName(), "--local", "testdata/secrets")).(string) 630 assert.Empty(t, diffOutput) 631 632 // ignore missing field and make sure diff shows no difference 633 app.Spec.IgnoreDifferences = []ResourceIgnoreDifferences{{ 634 Kind: kube.SecretKind, JSONPointers: []string{"/data"}, 635 }} 636 errors.NewHandler(t).FailOnErr(client.UpdateSpec(t.Context(), &applicationpkg.ApplicationUpdateSpecRequest{Name: &app.Name, AppNamespace: ptr.To(fixture.AppNamespace()), Spec: &app.Spec})) 637 }). 638 When(). 639 Refresh(RefreshTypeNormal). 640 Then(). 641 Expect(OperationPhaseIs(OperationSucceeded)). 642 Expect(SyncStatusIs(SyncStatusCodeSynced)). 643 And(func(_ *Application) { 644 diffOutput := errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "diff", ctx.AppQualifiedName())).(string) 645 assert.Empty(t, diffOutput) 646 }). 647 // verify not committed secret also ignore during diffing 648 When(). 649 WriteFile("secret3.yaml", ` 650 apiVersion: v1 651 kind: Secret 652 metadata: 653 name: test-secret3 654 stringData: 655 username: test-username`). 656 Then(). 657 And(func(_ *Application) { 658 diffOutput := errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "diff", ctx.AppQualifiedName(), "--local", "testdata/secrets")).(string) 659 assert.Empty(t, diffOutput) 660 }) 661 } 662 663 func TestNamespacedResourceDiffing(t *testing.T) { 664 ctx := Given(t) 665 ctx. 666 Path(guestbookPath). 667 SetTrackingMethod("annotation"). 668 SetAppNamespace(fixture.AppNamespace()). 669 When(). 670 CreateApp(). 671 Sync(). 672 Then(). 673 Expect(SyncStatusIs(SyncStatusCodeSynced)). 674 And(func(_ *Application) { 675 // Patch deployment 676 _, err := fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(t.Context(), 677 "guestbook-ui", types.JSONPatchType, []byte(`[{ "op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "test" }]`), metav1.PatchOptions{}) 678 require.NoError(t, err) 679 }). 680 When(). 681 Refresh(RefreshTypeNormal). 682 Then(). 683 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 684 And(func(_ *Application) { 685 diffOutput, err := fixture.RunCli("app", "diff", ctx.AppQualifiedName(), "--local-repo-root", ".", "--local", "testdata/guestbook") 686 require.Error(t, err) 687 assert.Contains(t, diffOutput, fmt.Sprintf("===== apps/Deployment %s/guestbook-ui ======", fixture.DeploymentNamespace())) 688 }). 689 Given(). 690 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": { 691 IgnoreDifferences: OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers/0/image"}}, 692 }}). 693 When(). 694 Refresh(RefreshTypeNormal). 695 Then(). 696 Expect(SyncStatusIs(SyncStatusCodeSynced)). 697 And(func(_ *Application) { 698 diffOutput, err := fixture.RunCli("app", "diff", ctx.AppQualifiedName(), "--local-repo-root", ".", "--local", "testdata/guestbook") 699 require.NoError(t, err) 700 assert.Empty(t, diffOutput) 701 }). 702 Given(). 703 When(). 704 // Now we migrate from client-side apply to server-side apply 705 // This is necessary, as starting with kubectl 1.26, all previously 706 // client-side owned fields have ownership migrated to the manager from 707 // the first ssa. 708 // More details: https://github.com/kubernetes/kubectl/issues/1337 709 PatchApp(`[{ 710 "op": "add", 711 "path": "/spec/syncPolicy", 712 "value": { "syncOptions": ["ServerSideApply=true"] } 713 }]`). 714 Sync(). 715 And(func() { 716 output, err := fixture.RunWithStdin(testdata.SSARevisionHistoryDeployment, "", "kubectl", "apply", "-n", fixture.DeploymentNamespace(), "--server-side=true", "--field-manager=revision-history-manager", "--validate=false", "--force-conflicts", "-f", "-") 717 require.NoError(t, err) 718 assert.Contains(t, output, "serverside-applied") 719 }). 720 Refresh(RefreshTypeNormal). 721 Then(). 722 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 723 Given(). 724 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": { 725 IgnoreDifferences: OverrideIgnoreDiff{ 726 ManagedFieldsManagers: []string{"revision-history-manager"}, 727 JSONPointers: []string{"/spec/template/spec/containers/0/image"}, 728 }, 729 }}). 730 When(). 731 Refresh(RefreshTypeNormal). 732 Then(). 733 Expect(SyncStatusIs(SyncStatusCodeSynced)). 734 Given(). 735 When(). 736 Sync(). 737 PatchApp(`[{ 738 "op": "add", 739 "path": "/spec/syncPolicy", 740 "value": { "syncOptions": ["RespectIgnoreDifferences=true"] } 741 }]`). 742 And(func() { 743 deployment, err := fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Get(t.Context(), "guestbook-ui", metav1.GetOptions{}) 744 require.NoError(t, err) 745 assert.Equal(t, int32(3), *deployment.Spec.RevisionHistoryLimit) 746 }). 747 And(func() { 748 output, err := fixture.RunWithStdin(testdata.SSARevisionHistoryDeployment, "", "kubectl", "apply", "-n", fixture.DeploymentNamespace(), "--server-side=true", "--field-manager=revision-history-manager", "--validate=false", "--force-conflicts", "-f", "-") 749 require.NoError(t, err) 750 assert.Contains(t, output, "serverside-applied") 751 }). 752 Then(). 753 When().Refresh(RefreshTypeNormal). 754 Then(). 755 Expect(SyncStatusIs(SyncStatusCodeSynced)). 756 And(func(_ *Application) { 757 deployment, err := fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Get(t.Context(), "guestbook-ui", metav1.GetOptions{}) 758 require.NoError(t, err) 759 assert.Equal(t, int32(1), *deployment.Spec.RevisionHistoryLimit) 760 }). 761 When().Sync().Then().Expect(SyncStatusIs(SyncStatusCodeSynced)). 762 And(func(_ *Application) { 763 deployment, err := fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Get(t.Context(), "guestbook-ui", metav1.GetOptions{}) 764 require.NoError(t, err) 765 assert.Equal(t, int32(1), *deployment.Spec.RevisionHistoryLimit) 766 }) 767 } 768 769 // func TestCRDs(t *testing.T) { 770 // testEdgeCasesApplicationResources(t, "crd-creation", health.HealthStatusHealthy) 771 // } 772 773 func TestNamespacedKnownTypesInCRDDiffing(t *testing.T) { 774 dummiesGVR := schema.GroupVersionResource{Group: application.Group, Version: "v1alpha1", Resource: "dummies"} 775 776 ctx := Given(t) 777 ctx. 778 Path("crd-creation"). 779 SetTrackingMethod("annotation"). 780 SetAppNamespace(fixture.AppNamespace()). 781 When().CreateApp().Sync().Then(). 782 Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeSynced)). 783 When(). 784 And(func() { 785 dummyResIf := fixture.DynamicClientset.Resource(dummiesGVR).Namespace(fixture.DeploymentNamespace()) 786 patchData := []byte(`{"spec":{"cpu": "2"}}`) 787 errors.NewHandler(t).FailOnErr(dummyResIf.Patch(t.Context(), "dummy-crd-instance", types.MergePatchType, patchData, metav1.PatchOptions{})) 788 }).Refresh(RefreshTypeNormal). 789 Then(). 790 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 791 When(). 792 And(func() { 793 require.NoError(t, fixture.SetResourceOverrides(map[string]ResourceOverride{ 794 "argoproj.io/Dummy": { 795 KnownTypeFields: []KnownTypeField{{ 796 Field: "spec", 797 Type: "core/v1/ResourceList", 798 }}, 799 }, 800 })) 801 }). 802 Refresh(RefreshTypeNormal). 803 Then(). 804 Expect(SyncStatusIs(SyncStatusCodeSynced)) 805 } 806 807 // TODO(jannfis): This somehow doesn't work -- I suspect tracking method 808 // func TestNamespacedDuplicatedResources(t *testing.T) { 809 // testNSEdgeCasesApplicationResources(t, "duplicated-resources", health.HealthStatusHealthy) 810 // } 811 812 func TestNamespacedConfigMap(t *testing.T) { 813 testNSEdgeCasesApplicationResources(t, "config-map", health.HealthStatusHealthy, "my-map Synced configmap/my-map created") 814 } 815 816 func testNSEdgeCasesApplicationResources(t *testing.T, appPath string, statusCode health.HealthStatusCode, message ...string) { 817 t.Helper() 818 ctx := Given(t) 819 expect := ctx. 820 Path(appPath). 821 SetTrackingMethod("annotation"). 822 SetAppNamespace(fixture.AppNamespace()). 823 When(). 824 CreateApp(). 825 Sync(). 826 Then(). 827 Expect(OperationPhaseIs(OperationSucceeded)). 828 Expect(SyncStatusIs(SyncStatusCodeSynced)) 829 for i := range message { 830 expect = expect.Expect(Success(message[i])) 831 } 832 expect. 833 Expect(HealthIs(statusCode)). 834 And(func(_ *Application) { 835 diffOutput, err := fixture.RunCli("app", "diff", ctx.AppQualifiedName(), "--local-repo-root", ".", "--local", path.Join("testdata", appPath)) 836 assert.Empty(t, diffOutput) 837 require.NoError(t, err) 838 }) 839 } 840 841 // // We don't have tracking label in namespaced tests, thus we need a unique 842 // // resource action that modifies annotations instead of labels. 843 // const nsActionsConfig = `discovery.lua: return { sample = {} } 844 // definitions: 845 // - name: sample 846 // action.lua: | 847 // obj.metadata.annotations.sample = 'test' 848 // return obj` 849 850 func TestNamespacedResourceAction(t *testing.T) { 851 ctx := Given(t) 852 ctx. 853 Path(guestbookPath). 854 SetTrackingMethod("annotation"). 855 SetAppNamespace(fixture.AppNamespace()). 856 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {Actions: actionsConfig}}). 857 When(). 858 CreateApp(). 859 Sync(). 860 Then(). 861 And(func(app *Application) { 862 closer, client, err := fixture.ArgoCDClientset.NewApplicationClient() 863 require.NoError(t, err) 864 defer utilio.Close(closer) 865 866 actions, err := client.ListResourceActions(t.Context(), &applicationpkg.ApplicationResourceRequest{ 867 Name: &app.Name, 868 AppNamespace: ptr.To(fixture.AppNamespace()), 869 Group: ptr.To("apps"), 870 Kind: ptr.To("Deployment"), 871 Version: ptr.To("v1"), 872 Namespace: ptr.To(fixture.DeploymentNamespace()), 873 ResourceName: ptr.To("guestbook-ui"), 874 }) 875 require.NoError(t, err) 876 assert.Equal(t, []*ResourceAction{{Name: "sample", Disabled: false}}, actions.Actions) 877 878 _, err = client.RunResourceActionV2(t.Context(), &applicationpkg.ResourceActionRunRequestV2{ 879 Name: &app.Name, 880 Group: ptr.To("apps"), 881 Kind: ptr.To("Deployment"), 882 Version: ptr.To("v1"), 883 Namespace: ptr.To(fixture.DeploymentNamespace()), 884 ResourceName: ptr.To("guestbook-ui"), 885 Action: ptr.To("sample"), 886 AppNamespace: ptr.To(fixture.AppNamespace()), 887 }) 888 require.NoError(t, err) 889 890 deployment, err := fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Get(t.Context(), "guestbook-ui", metav1.GetOptions{}) 891 require.NoError(t, err) 892 893 assert.Equal(t, "test", deployment.Labels["sample"]) 894 }) 895 } 896 897 func TestNamespacedSyncResourceByLabel(t *testing.T) { 898 ctx := Given(t) 899 ctx. 900 Path(guestbookPath). 901 SetTrackingMethod("annotation"). 902 SetAppNamespace(fixture.AppNamespace()). 903 When(). 904 CreateApp(). 905 Sync(). 906 Then(). 907 And(func(app *Application) { 908 _, _ = fixture.RunCli("app", "sync", ctx.AppQualifiedName(), "--label", "app.kubernetes.io/instance="+app.Name) 909 }). 910 Expect(SyncStatusIs(SyncStatusCodeSynced)). 911 And(func(_ *Application) { 912 _, err := fixture.RunCli("app", "sync", ctx.AppQualifiedName(), "--label", "this-label=does-not-exist") 913 assert.ErrorContains(t, err, "\"level\":\"fatal\"") 914 }) 915 } 916 917 func TestNamespacedLocalManifestSync(t *testing.T) { 918 ctx := Given(t) 919 ctx. 920 Path(guestbookPath). 921 SetTrackingMethod("annotation"). 922 SetAppNamespace(fixture.AppNamespace()). 923 When(). 924 CreateApp(). 925 Sync(). 926 Then(). 927 And(func(_ *Application) { 928 res, _ := fixture.RunCli("app", "manifests", ctx.AppQualifiedName()) 929 assert.Contains(t, res, "containerPort: 80") 930 assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2") 931 }). 932 Given(). 933 LocalPath(guestbookPathLocal). 934 When(). 935 Sync("--local-repo-root", "."). 936 Then(). 937 Expect(SyncStatusIs(SyncStatusCodeSynced)). 938 And(func(_ *Application) { 939 res, _ := fixture.RunCli("app", "manifests", ctx.AppQualifiedName()) 940 assert.Contains(t, res, "containerPort: 81") 941 assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.3") 942 }). 943 Given(). 944 LocalPath(""). 945 When(). 946 Sync(). 947 Then(). 948 Expect(SyncStatusIs(SyncStatusCodeSynced)). 949 And(func(_ *Application) { 950 res, _ := fixture.RunCli("app", "manifests", ctx.AppQualifiedName()) 951 assert.Contains(t, res, "containerPort: 80") 952 assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2") 953 }) 954 } 955 956 func TestNamespacedLocalSync(t *testing.T) { 957 Given(t). 958 // we've got to use Helm as this uses kubeVersion 959 Path("helm"). 960 SetTrackingMethod("annotation"). 961 SetAppNamespace(fixture.AppNamespace()). 962 When(). 963 CreateApp(). 964 Then(). 965 And(func(app *Application) { 966 errors.NewHandler(t).FailOnErr(fixture.RunCli("app", "sync", app.QualifiedName(), "--local", "testdata/helm")) 967 }) 968 } 969 970 func TestNamespacedNoLocalSyncWithAutosyncEnabled(t *testing.T) { 971 Given(t). 972 Path(guestbookPath). 973 SetTrackingMethod("annotation"). 974 SetAppNamespace(fixture.AppNamespace()). 975 When(). 976 CreateApp(). 977 Sync(). 978 Then(). 979 And(func(app *Application) { 980 _, err := fixture.RunCli("app", "set", app.QualifiedName(), "--sync-policy", "automated") 981 require.NoError(t, err) 982 983 _, err = fixture.RunCli("app", "sync", app.QualifiedName(), "--local", guestbookPathLocal) 984 assert.ErrorContains(t, err, "Cannot use local sync") 985 }) 986 } 987 988 func TestNamespacedLocalSyncDryRunWithASEnabled(t *testing.T) { 989 Given(t). 990 Path(guestbookPath). 991 SetTrackingMethod("annotation"). 992 SetAppNamespace(fixture.AppNamespace()). 993 When(). 994 CreateApp(). 995 Sync(). 996 Then(). 997 And(func(app *Application) { 998 _, err := fixture.RunCli("app", "set", app.QualifiedName(), "--sync-policy", "automated") 999 require.NoError(t, err) 1000 1001 appBefore := app.DeepCopy() 1002 _, err = fixture.RunCli("app", "sync", app.QualifiedName(), "--dry-run", "--local-repo-root", ".", "--local", guestbookPathLocal) 1003 require.NoError(t, err) 1004 1005 appAfter := app.DeepCopy() 1006 assert.True(t, reflect.DeepEqual(appBefore, appAfter)) 1007 }) 1008 } 1009 1010 func TestNamespacedSyncAsync(t *testing.T) { 1011 Given(t). 1012 Path(guestbookPath). 1013 SetTrackingMethod("annotation"). 1014 SetAppNamespace(fixture.AppNamespace()). 1015 Async(true). 1016 When(). 1017 CreateApp(). 1018 Sync(). 1019 Then(). 1020 Expect(Success("")). 1021 Expect(OperationPhaseIs(OperationSucceeded)). 1022 Expect(SyncStatusIs(SyncStatusCodeSynced)) 1023 } 1024 1025 // assertResourceActions verifies if view/modify resource actions are successful/failing for given application 1026 func assertNSResourceActions(t *testing.T, appName string, successful bool) { 1027 t.Helper() 1028 assertError := func(err error, message string) { 1029 if successful { 1030 require.NoError(t, err) 1031 } else { 1032 assert.ErrorContains(t, err, message) 1033 } 1034 } 1035 1036 closer, cdClient := fixture.ArgoCDClientset.NewApplicationClientOrDie() 1037 defer utilio.Close(closer) 1038 1039 deploymentResource, err := fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Get(t.Context(), "guestbook-ui", metav1.GetOptions{}) 1040 require.NoError(t, err) 1041 1042 logs, err := cdClient.PodLogs(t.Context(), &applicationpkg.ApplicationPodLogsQuery{ 1043 Group: ptr.To("apps"), 1044 Kind: ptr.To("Deployment"), 1045 Name: &appName, 1046 AppNamespace: ptr.To(fixture.AppNamespace()), 1047 Namespace: ptr.To(fixture.DeploymentNamespace()), 1048 Container: ptr.To(""), 1049 SinceSeconds: ptr.To(int64(0)), 1050 TailLines: ptr.To(int64(0)), 1051 Follow: ptr.To(false), 1052 }) 1053 require.NoError(t, err) 1054 _, err = logs.Recv() 1055 assertError(err, "EOF") 1056 1057 expectedError := "Deployment apps guestbook-ui not found as part of application " + appName 1058 1059 _, err = cdClient.ListResourceEvents(t.Context(), &applicationpkg.ApplicationResourceEventsQuery{ 1060 Name: &appName, 1061 AppNamespace: ptr.To(fixture.AppNamespace()), 1062 ResourceName: ptr.To("guestbook-ui"), 1063 ResourceNamespace: ptr.To(fixture.DeploymentNamespace()), 1064 ResourceUID: ptr.To(string(deploymentResource.UID)), 1065 }) 1066 assertError(err, fmt.Sprintf("%s not found as part of application %s", "guestbook-ui", appName)) 1067 1068 _, err = cdClient.GetResource(t.Context(), &applicationpkg.ApplicationResourceRequest{ 1069 Name: &appName, 1070 AppNamespace: ptr.To(fixture.AppNamespace()), 1071 ResourceName: ptr.To("guestbook-ui"), 1072 Namespace: ptr.To(fixture.DeploymentNamespace()), 1073 Version: ptr.To("v1"), 1074 Group: ptr.To("apps"), 1075 Kind: ptr.To("Deployment"), 1076 }) 1077 assertError(err, expectedError) 1078 1079 _, err = cdClient.RunResourceActionV2(t.Context(), &applicationpkg.ResourceActionRunRequestV2{ 1080 Name: &appName, 1081 AppNamespace: ptr.To(fixture.AppNamespace()), 1082 ResourceName: ptr.To("guestbook-ui"), 1083 Namespace: ptr.To(fixture.DeploymentNamespace()), 1084 Version: ptr.To("v1"), 1085 Group: ptr.To("apps"), 1086 Kind: ptr.To("Deployment"), 1087 Action: ptr.To("restart"), 1088 }) 1089 assertError(err, expectedError) 1090 1091 _, err = cdClient.DeleteResource(t.Context(), &applicationpkg.ApplicationResourceDeleteRequest{ 1092 Name: &appName, 1093 AppNamespace: ptr.To(fixture.AppNamespace()), 1094 ResourceName: ptr.To("guestbook-ui"), 1095 Namespace: ptr.To(fixture.DeploymentNamespace()), 1096 Version: ptr.To("v1"), 1097 Group: ptr.To("apps"), 1098 Kind: ptr.To("Deployment"), 1099 }) 1100 assertError(err, expectedError) 1101 } 1102 1103 func TestNamespacedPermissions(t *testing.T) { 1104 appCtx := Given(t) 1105 projName := "argo-project" 1106 projActions := projectFixture. 1107 Given(t). 1108 Name(projName). 1109 SourceNamespaces([]string{fixture.AppNamespace()}). 1110 When(). 1111 Create() 1112 1113 sourceError := fmt.Sprintf("application repo %s is not permitted in project 'argo-project'", fixture.RepoURL(fixture.RepoURLTypeFile)) 1114 destinationError := fmt.Sprintf("application destination server '%s' and namespace '%s' do not match any of the allowed destinations in project 'argo-project'", KubernetesInternalAPIServerAddr, fixture.DeploymentNamespace()) 1115 1116 appCtx. 1117 Path("guestbook-logs"). 1118 SetTrackingMethod("annotation"). 1119 SetAppNamespace(fixture.AppNamespace()). 1120 Project(projName). 1121 When(). 1122 IgnoreErrors(). 1123 // ensure app is not created if project permissions are missing 1124 CreateApp(). 1125 Then(). 1126 Expect(Error("", sourceError)). 1127 Expect(Error("", destinationError)). 1128 When(). 1129 DoNotIgnoreErrors(). 1130 // add missing permissions, create and sync app 1131 And(func() { 1132 projActions.AddDestination("*", "*") 1133 projActions.AddSource("*") 1134 }). 1135 CreateApp(). 1136 Sync(). 1137 Wait(). 1138 Then(). 1139 // make sure application resource actiions are successful 1140 And(func(app *Application) { 1141 assertNSResourceActions(t, app.Name, true) 1142 }). 1143 When(). 1144 // remove projet permissions and "refresh" app 1145 And(func() { 1146 projActions.UpdateProject(func(proj *AppProject) { 1147 proj.Spec.Destinations = nil 1148 proj.Spec.SourceRepos = nil 1149 }) 1150 }). 1151 Refresh(RefreshTypeNormal). 1152 Then(). 1153 // ensure app resource tree is empty when source/destination permissions are missing 1154 Expect(Condition(ApplicationConditionInvalidSpecError, destinationError)). 1155 Expect(Condition(ApplicationConditionInvalidSpecError, sourceError)). 1156 And(func(app *Application) { 1157 closer, cdClient := fixture.ArgoCDClientset.NewApplicationClientOrDie() 1158 defer utilio.Close(closer) 1159 tree, err := cdClient.ResourceTree(t.Context(), &applicationpkg.ResourcesQuery{ApplicationName: &app.Name, AppNamespace: &app.Namespace}) 1160 require.NoError(t, err) 1161 assert.Empty(t, tree.Nodes) 1162 assert.Empty(t, tree.OrphanedNodes) 1163 }). 1164 When(). 1165 // add missing permissions but deny management of Deployment kind 1166 And(func() { 1167 projActions. 1168 AddDestination("*", "*"). 1169 AddSource("*"). 1170 UpdateProject(func(proj *AppProject) { 1171 proj.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{{Group: "*", Kind: "Deployment"}} 1172 }) 1173 }). 1174 Refresh(RefreshTypeNormal). 1175 Then(). 1176 // make sure application resource actiions are failing 1177 And(func(app *Application) { 1178 assertNSResourceActions(t, app.Name, false) 1179 }) 1180 } 1181 1182 func TestNamespacedPermissionWithScopedRepo(t *testing.T) { 1183 projName := "argo-project" 1184 fixture.EnsureCleanState(t) 1185 projectFixture. 1186 Given(t). 1187 Name(projName). 1188 SourceNamespaces([]string{fixture.AppNamespace()}). 1189 Destination("*,*"). 1190 When(). 1191 Create() 1192 1193 repoFixture.GivenWithSameState(t). 1194 When(). 1195 Path(fixture.RepoURL(fixture.RepoURLTypeFile)). 1196 Project(projName). 1197 Create() 1198 1199 GivenWithSameState(t). 1200 Project(projName). 1201 RepoURLType(fixture.RepoURLTypeFile). 1202 Path("two-nice-pods"). 1203 SetTrackingMethod("annotation"). 1204 SetAppNamespace(fixture.AppNamespace()). 1205 When(). 1206 PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`). 1207 CreateApp(). 1208 Sync(). 1209 Then(). 1210 Expect(OperationPhaseIs(OperationSucceeded)). 1211 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1212 When(). 1213 DeleteFile("pod-1.yaml"). 1214 Refresh(RefreshTypeHard). 1215 IgnoreErrors(). 1216 Sync(). 1217 Then(). 1218 Expect(OperationPhaseIs(OperationSucceeded)). 1219 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 1220 Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync)) 1221 } 1222 1223 func TestNamespacedPermissionDeniedWithScopedRepo(t *testing.T) { 1224 projName := "argo-project" 1225 projectFixture. 1226 Given(t). 1227 Name(projName). 1228 Destination("*,*"). 1229 SourceNamespaces([]string{fixture.AppNamespace()}). 1230 When(). 1231 Create() 1232 1233 repoFixture.GivenWithSameState(t). 1234 When(). 1235 Path(fixture.RepoURL(fixture.RepoURLTypeFile)). 1236 Create() 1237 1238 GivenWithSameState(t). 1239 Project(projName). 1240 RepoURLType(fixture.RepoURLTypeFile). 1241 SetTrackingMethod("annotation"). 1242 SetAppNamespace(fixture.AppNamespace()). 1243 Path("two-nice-pods"). 1244 When(). 1245 PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`). 1246 IgnoreErrors(). 1247 CreateApp(). 1248 Then(). 1249 Expect(Error("", "is not permitted in project")) 1250 } 1251 1252 // make sure that if we deleted a resource from the app, it is not pruned if annotated with Prune=false 1253 func TestNamespacedSyncOptionPruneFalse(t *testing.T) { 1254 Given(t). 1255 Path("two-nice-pods"). 1256 SetTrackingMethod("annotation"). 1257 SetAppNamespace(fixture.AppNamespace()). 1258 When(). 1259 PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`). 1260 CreateApp(). 1261 Sync(). 1262 Then(). 1263 Expect(OperationPhaseIs(OperationSucceeded)). 1264 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1265 When(). 1266 DeleteFile("pod-1.yaml"). 1267 Refresh(RefreshTypeHard). 1268 IgnoreErrors(). 1269 Sync(). 1270 Then(). 1271 Expect(OperationPhaseIs(OperationSucceeded)). 1272 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 1273 Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync)) 1274 } 1275 1276 // make sure that if we have an invalid manifest, we can add it if we disable validation, we get a server error rather than a client error 1277 func TestNamespacedSyncOptionValidateFalse(t *testing.T) { 1278 Given(t). 1279 Path("crd-validation"). 1280 SetTrackingMethod("annotation"). 1281 SetAppNamespace(fixture.AppNamespace()). 1282 When(). 1283 CreateApp(). 1284 Then(). 1285 Expect(Success("")). 1286 When(). 1287 IgnoreErrors(). 1288 Sync(). 1289 Then(). 1290 // client error. K8s API changed error message w/ 1.25, so for now, we need to check both 1291 Expect(ErrorRegex("error validating data|of type int32", "")). 1292 When(). 1293 PatchFile("deployment.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Validate=false"}}]`). 1294 Sync(). 1295 Then(). 1296 // server error 1297 Expect(Error("cannot be handled as a Deployment", "")) 1298 } 1299 1300 // make sure that, if we have a resource that needs pruning, but we're ignoring it, the app is in-sync 1301 func TestNamespacedCompareOptionIgnoreExtraneous(t *testing.T) { 1302 Given(t). 1303 Prune(false). 1304 SetTrackingMethod("annotation"). 1305 SetAppNamespace(fixture.AppNamespace()). 1306 Path("two-nice-pods"). 1307 When(). 1308 PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/compare-options": "IgnoreExtraneous"}}]`). 1309 CreateApp(). 1310 Sync(). 1311 Then(). 1312 Expect(OperationPhaseIs(OperationSucceeded)). 1313 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1314 When(). 1315 DeleteFile("pod-1.yaml"). 1316 Refresh(RefreshTypeHard). 1317 Then(). 1318 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1319 And(func(app *Application) { 1320 assert.Len(t, app.Status.Resources, 2) 1321 statusByName := map[string]SyncStatusCode{} 1322 for _, r := range app.Status.Resources { 1323 statusByName[r.Name] = r.Status 1324 } 1325 assert.Equal(t, SyncStatusCodeOutOfSync, statusByName["pod-1"]) 1326 assert.Equal(t, SyncStatusCodeSynced, statusByName["pod-2"]) 1327 }). 1328 When(). 1329 Sync(). 1330 Then(). 1331 Expect(OperationPhaseIs(OperationSucceeded)). 1332 Expect(SyncStatusIs(SyncStatusCodeSynced)) 1333 } 1334 1335 func TestNamespacedSelfManagedApps(t *testing.T) { 1336 Given(t). 1337 Path("self-managed-app"). 1338 SetTrackingMethod("annotation"). 1339 SetAppNamespace(fixture.AppNamespace()). 1340 When(). 1341 PatchFile("resources.yaml", fmt.Sprintf(`[{"op": "replace", "path": "/spec/source/repoURL", "value": %q}]`, fixture.RepoURL(fixture.RepoURLTypeFile))). 1342 CreateApp(). 1343 Sync(). 1344 Then(). 1345 Expect(OperationPhaseIs(OperationSucceeded)). 1346 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1347 And(func(a *Application) { 1348 ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) 1349 defer cancel() 1350 1351 reconciledCount := 0 1352 var lastReconciledAt *metav1.Time 1353 for event := range fixture.ArgoCDClientset.WatchApplicationWithRetry(ctx, a.QualifiedName(), a.ResourceVersion) { 1354 reconciledAt := event.Application.Status.ReconciledAt 1355 if reconciledAt == nil { 1356 reconciledAt = &metav1.Time{} 1357 } 1358 if lastReconciledAt != nil && !lastReconciledAt.Equal(reconciledAt) { 1359 reconciledCount = reconciledCount + 1 1360 } 1361 lastReconciledAt = reconciledAt 1362 } 1363 1364 assert.Less(t, reconciledCount, 3, "Application was reconciled too many times") 1365 }) 1366 } 1367 1368 func TestNamespacedExcludedResource(t *testing.T) { 1369 Given(t). 1370 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {Actions: actionsConfig}}). 1371 SetTrackingMethod("annotation"). 1372 SetAppNamespace(fixture.AppNamespace()). 1373 Path(guestbookPath). 1374 ResourceFilter(settings.ResourcesFilter{ 1375 ResourceExclusions: []settings.FilteredResource{{Kinds: []string{kube.DeploymentKind}}}, 1376 }). 1377 When(). 1378 CreateApp(). 1379 Sync(). 1380 Refresh(RefreshTypeNormal). 1381 Then(). 1382 Expect(Condition(ApplicationConditionExcludedResourceWarning, "Resource apps/Deployment guestbook-ui is excluded in the settings")) 1383 } 1384 1385 func TestNamespacedRevisionHistoryLimit(t *testing.T) { 1386 Given(t). 1387 Path("config-map"). 1388 SetTrackingMethod("annotation"). 1389 SetAppNamespace(fixture.AppNamespace()). 1390 When(). 1391 CreateApp(). 1392 Sync(). 1393 Then(). 1394 Expect(OperationPhaseIs(OperationSucceeded)). 1395 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1396 And(func(app *Application) { 1397 assert.Len(t, app.Status.History, 1) 1398 }). 1399 When(). 1400 AppSet("--revision-history-limit", "1"). 1401 Sync(). 1402 Then(). 1403 Expect(OperationPhaseIs(OperationSucceeded)). 1404 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1405 And(func(app *Application) { 1406 assert.Len(t, app.Status.History, 1) 1407 }) 1408 } 1409 1410 func TestNamespacedOrphanedResource(t *testing.T) { 1411 fixture.SkipOnEnv(t, "OPENSHIFT") 1412 Given(t). 1413 ProjectSpec(AppProjectSpec{ 1414 SourceRepos: []string{"*"}, 1415 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1416 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: ptr.To(true)}, 1417 SourceNamespaces: []string{fixture.AppNamespace()}, 1418 }). 1419 SetTrackingMethod("annotation"). 1420 SetAppNamespace(fixture.AppNamespace()). 1421 Path(guestbookPath). 1422 When(). 1423 CreateApp(). 1424 Sync(). 1425 Then(). 1426 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1427 Expect(NoConditions()). 1428 When(). 1429 And(func() { 1430 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.CoreV1().ConfigMaps(fixture.DeploymentNamespace()).Create(t.Context(), &corev1.ConfigMap{ 1431 ObjectMeta: metav1.ObjectMeta{ 1432 Name: "orphaned-configmap", 1433 }, 1434 }, metav1.CreateOptions{})) 1435 }). 1436 Refresh(RefreshTypeNormal). 1437 Then(). 1438 Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")). 1439 And(func(app *Application) { 1440 output, err := fixture.RunCli("app", "resources", app.QualifiedName()) 1441 require.NoError(t, err) 1442 assert.Contains(t, output, "orphaned-configmap") 1443 }). 1444 Given(). 1445 ProjectSpec(AppProjectSpec{ 1446 SourceRepos: []string{"*"}, 1447 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1448 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: ptr.To(true), Ignore: []OrphanedResourceKey{{Group: "Test", Kind: "ConfigMap"}}}, 1449 SourceNamespaces: []string{fixture.AppNamespace()}, 1450 }). 1451 When(). 1452 Refresh(RefreshTypeNormal). 1453 Then(). 1454 Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")). 1455 And(func(app *Application) { 1456 output, err := fixture.RunCli("app", "resources", app.QualifiedName()) 1457 require.NoError(t, err) 1458 assert.Contains(t, output, "orphaned-configmap") 1459 }). 1460 Given(). 1461 ProjectSpec(AppProjectSpec{ 1462 SourceRepos: []string{"*"}, 1463 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1464 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: ptr.To(true), Ignore: []OrphanedResourceKey{{Kind: "ConfigMap"}}}, 1465 SourceNamespaces: []string{fixture.AppNamespace()}, 1466 }). 1467 When(). 1468 Refresh(RefreshTypeNormal). 1469 Then(). 1470 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1471 Expect(NoConditions()). 1472 And(func(app *Application) { 1473 output, err := fixture.RunCli("app", "resources", app.QualifiedName()) 1474 require.NoError(t, err) 1475 assert.NotContains(t, output, "orphaned-configmap") 1476 }). 1477 Given(). 1478 ProjectSpec(AppProjectSpec{ 1479 SourceRepos: []string{"*"}, 1480 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1481 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: ptr.To(true), Ignore: []OrphanedResourceKey{{Kind: "ConfigMap", Name: "orphaned-configmap"}}}, 1482 SourceNamespaces: []string{fixture.AppNamespace()}, 1483 }). 1484 When(). 1485 Refresh(RefreshTypeNormal). 1486 Then(). 1487 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1488 Expect(NoConditions()). 1489 And(func(app *Application) { 1490 output, err := fixture.RunCli("app", "resources", app.QualifiedName()) 1491 require.NoError(t, err) 1492 assert.NotContains(t, output, "orphaned-configmap") 1493 }). 1494 Given(). 1495 ProjectSpec(AppProjectSpec{ 1496 SourceRepos: []string{"*"}, 1497 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1498 OrphanedResources: nil, 1499 SourceNamespaces: []string{fixture.AppNamespace()}, 1500 }). 1501 When(). 1502 Refresh(RefreshTypeNormal). 1503 Then(). 1504 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1505 Expect(NoConditions()) 1506 } 1507 1508 func TestNamespacedNotPermittedResources(t *testing.T) { 1509 ctx := Given(t) 1510 ctx.SetAppNamespace(fixture.AppNamespace()) 1511 pathType := networkingv1.PathTypePrefix 1512 ingress := &networkingv1.Ingress{ 1513 ObjectMeta: metav1.ObjectMeta{ 1514 Name: "sample-ingress", 1515 Annotations: map[string]string{ 1516 common.AnnotationKeyAppInstance: fmt.Sprintf("%s_%s:networking/Ingress:%s/sample-ingress", fixture.AppNamespace(), ctx.AppName(), fixture.DeploymentNamespace()), 1517 }, 1518 }, 1519 Spec: networkingv1.IngressSpec{ 1520 Rules: []networkingv1.IngressRule{{ 1521 IngressRuleValue: networkingv1.IngressRuleValue{ 1522 HTTP: &networkingv1.HTTPIngressRuleValue{ 1523 Paths: []networkingv1.HTTPIngressPath{{ 1524 Path: "/", 1525 Backend: networkingv1.IngressBackend{ 1526 Service: &networkingv1.IngressServiceBackend{ 1527 Name: "guestbook-ui", 1528 Port: networkingv1.ServiceBackendPort{Number: 80}, 1529 }, 1530 }, 1531 PathType: &pathType, 1532 }}, 1533 }, 1534 }, 1535 }}, 1536 }, 1537 } 1538 defer func() { 1539 log.Infof("Ingress 'sample-ingress' deleted from %s", fixture.TestNamespace()) 1540 require.NoError(t, fixture.KubeClientset.NetworkingV1().Ingresses(fixture.TestNamespace()).Delete(t.Context(), "sample-ingress", metav1.DeleteOptions{})) 1541 }() 1542 1543 svc := &corev1.Service{ 1544 ObjectMeta: metav1.ObjectMeta{ 1545 Name: "guestbook-ui", 1546 Annotations: map[string]string{ 1547 common.AnnotationKeyAppInstance: fmt.Sprintf("%s_%s:Service:%s/guesbook-ui", fixture.TestNamespace(), ctx.AppQualifiedName(), fixture.DeploymentNamespace()), 1548 }, 1549 }, 1550 Spec: corev1.ServiceSpec{ 1551 Ports: []corev1.ServicePort{{ 1552 Port: 80, 1553 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 80}, 1554 }}, 1555 Selector: map[string]string{ 1556 "app": "guestbook-ui", 1557 }, 1558 }, 1559 } 1560 1561 ctx.ProjectSpec(AppProjectSpec{ 1562 SourceRepos: []string{"*"}, 1563 Destinations: []ApplicationDestination{{Namespace: fixture.DeploymentNamespace(), Server: "*"}}, 1564 SourceNamespaces: []string{fixture.AppNamespace()}, 1565 NamespaceResourceBlacklist: []metav1.GroupKind{ 1566 {Group: "", Kind: "Service"}, 1567 }, 1568 }). 1569 And(func() { 1570 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.NetworkingV1().Ingresses(fixture.TestNamespace()).Create(t.Context(), ingress, metav1.CreateOptions{})) 1571 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.CoreV1().Services(fixture.DeploymentNamespace()).Create(t.Context(), svc, metav1.CreateOptions{})) 1572 }). 1573 Path(guestbookPath). 1574 When(). 1575 CreateApp(). 1576 Then(). 1577 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 1578 And(func(app *Application) { 1579 statusByKind := make(map[string]ResourceStatus) 1580 for _, res := range app.Status.Resources { 1581 statusByKind[res.Kind] = res 1582 } 1583 _, hasIngress := statusByKind[kube.IngressKind] 1584 assert.False(t, hasIngress, "Ingress is prohibited not managed object and should be even visible to user") 1585 serviceStatus := statusByKind[kube.ServiceKind] 1586 assert.Equal(t, SyncStatusCodeUnknown, serviceStatus.Status, "Service is prohibited managed resource so should be set to Unknown") 1587 deploymentStatus := statusByKind[kube.DeploymentKind] 1588 assert.Equal(t, SyncStatusCodeOutOfSync, deploymentStatus.Status) 1589 }). 1590 When(). 1591 Delete(true). 1592 Then(). 1593 Expect(DoesNotExist()) 1594 1595 // Make sure prohibited resources are not deleted during application deletion 1596 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.NetworkingV1().Ingresses(fixture.TestNamespace()).Get(t.Context(), "sample-ingress", metav1.GetOptions{})) 1597 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.CoreV1().Services(fixture.DeploymentNamespace()).Get(t.Context(), "guestbook-ui", metav1.GetOptions{})) 1598 } 1599 1600 func TestNamespacedSyncWithInfos(t *testing.T) { 1601 expectedInfo := make([]*Info, 2) 1602 expectedInfo[0] = &Info{Name: "name1", Value: "val1"} 1603 expectedInfo[1] = &Info{Name: "name2", Value: "val2"} 1604 1605 Given(t). 1606 SetAppNamespace(fixture.AppNamespace()). 1607 SetTrackingMethod("annotation"). 1608 Path(guestbookPath). 1609 When(). 1610 CreateApp(). 1611 Then(). 1612 And(func(app *Application) { 1613 _, err := fixture.RunCli("app", "sync", app.QualifiedName(), 1614 "--info", fmt.Sprintf("%s=%s", expectedInfo[0].Name, expectedInfo[0].Value), 1615 "--info", fmt.Sprintf("%s=%s", expectedInfo[1].Name, expectedInfo[1].Value)) 1616 require.NoError(t, err) 1617 }). 1618 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1619 And(func(app *Application) { 1620 assert.ElementsMatch(t, app.Status.OperationState.Operation.Info, expectedInfo) 1621 }) 1622 } 1623 1624 // Given: argocd app create does not provide --dest-namespace 1625 // 1626 // Manifest contains resource console which does not require namespace 1627 // 1628 // Expect: no app.Status.Conditions 1629 func TestNamespacedCreateAppWithNoNameSpaceForGlobalResource(t *testing.T) { 1630 Given(t). 1631 SetAppNamespace(fixture.AppNamespace()). 1632 SetTrackingMethod("annotation"). 1633 Path(globalWithNoNameSpace). 1634 When(). 1635 CreateWithNoNameSpace(). 1636 Then(). 1637 And(func(app *Application) { 1638 app, err := fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Get(t.Context(), app.Name, metav1.GetOptions{}) 1639 require.NoError(t, err) 1640 assert.Empty(t, app.Status.Conditions) 1641 }) 1642 } 1643 1644 // Given: argocd app create does not provide --dest-namespace 1645 // 1646 // Manifest contains resource deployment, and service which requires namespace 1647 // Deployment and service do not have namespace in manifest 1648 // 1649 // Expect: app.Status.Conditions for deployment ans service which does not have namespace in manifest 1650 func TestNamespacedCreateAppWithNoNameSpaceWhenRequired(t *testing.T) { 1651 Given(t). 1652 SetAppNamespace(fixture.AppNamespace()). 1653 SetTrackingMethod("annotation"). 1654 Path(guestbookPath). 1655 When(). 1656 CreateWithNoNameSpace(). 1657 Refresh(RefreshTypeNormal). 1658 Then(). 1659 And(func(app *Application) { 1660 updatedApp, err := fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Get(t.Context(), app.Name, metav1.GetOptions{}) 1661 require.NoError(t, err) 1662 1663 assert.Len(t, updatedApp.Status.Conditions, 2) 1664 assert.Equal(t, ApplicationConditionInvalidSpecError, updatedApp.Status.Conditions[0].Type) 1665 assert.Equal(t, ApplicationConditionInvalidSpecError, updatedApp.Status.Conditions[1].Type) 1666 }) 1667 } 1668 1669 // Given: argocd app create does not provide --dest-namespace 1670 // 1671 // Manifest contains resource deployment, and service which requires namespace 1672 // Some deployment and service has namespace in manifest 1673 // Some deployment and service does not have namespace in manifest 1674 // 1675 // Expect: app.Status.Conditions for deployment and service which does not have namespace in manifest 1676 func TestNamespacedCreateAppWithNoNameSpaceWhenRequired2(t *testing.T) { 1677 Given(t). 1678 SetAppNamespace(fixture.AppNamespace()). 1679 SetTrackingMethod("annotation"). 1680 Path(guestbookWithNamespace). 1681 When(). 1682 CreateWithNoNameSpace(). 1683 Refresh(RefreshTypeNormal). 1684 Then(). 1685 And(func(app *Application) { 1686 updatedApp, err := fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Get(t.Context(), app.Name, metav1.GetOptions{}) 1687 require.NoError(t, err) 1688 1689 assert.Len(t, updatedApp.Status.Conditions, 2) 1690 assert.Equal(t, ApplicationConditionInvalidSpecError, updatedApp.Status.Conditions[0].Type) 1691 assert.Equal(t, ApplicationConditionInvalidSpecError, updatedApp.Status.Conditions[1].Type) 1692 }) 1693 } 1694 1695 func TestNamespacedListResource(t *testing.T) { 1696 fixture.SkipOnEnv(t, "OPENSHIFT") 1697 Given(t). 1698 SetAppNamespace(fixture.AppNamespace()). 1699 SetTrackingMethod("annotation"). 1700 ProjectSpec(AppProjectSpec{ 1701 SourceRepos: []string{"*"}, 1702 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1703 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: ptr.To(true)}, 1704 SourceNamespaces: []string{fixture.AppNamespace()}, 1705 }). 1706 Path(guestbookPath). 1707 When(). 1708 CreateApp(). 1709 Sync(). 1710 Then(). 1711 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1712 Expect(NoConditions()). 1713 When(). 1714 And(func() { 1715 errors.NewHandler(t).FailOnErr(fixture.KubeClientset.CoreV1().ConfigMaps(fixture.DeploymentNamespace()).Create(t.Context(), &corev1.ConfigMap{ 1716 ObjectMeta: metav1.ObjectMeta{ 1717 Name: "orphaned-configmap", 1718 }, 1719 }, metav1.CreateOptions{})) 1720 }). 1721 Refresh(RefreshTypeNormal). 1722 Then(). 1723 Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")). 1724 And(func(app *Application) { 1725 output, err := fixture.RunCli("app", "resources", app.QualifiedName()) 1726 require.NoError(t, err) 1727 assert.Contains(t, output, "orphaned-configmap") 1728 assert.Contains(t, output, "guestbook-ui") 1729 }). 1730 And(func(app *Application) { 1731 output, err := fixture.RunCli("app", "resources", app.QualifiedName(), "--orphaned=true") 1732 require.NoError(t, err) 1733 assert.Contains(t, output, "orphaned-configmap") 1734 assert.NotContains(t, output, "guestbook-ui") 1735 }). 1736 And(func(app *Application) { 1737 output, err := fixture.RunCli("app", "resources", app.QualifiedName(), "--orphaned=false") 1738 require.NoError(t, err) 1739 assert.NotContains(t, output, "orphaned-configmap") 1740 assert.Contains(t, output, "guestbook-ui") 1741 }). 1742 Given(). 1743 ProjectSpec(AppProjectSpec{ 1744 SourceRepos: []string{"*"}, 1745 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1746 OrphanedResources: nil, 1747 SourceNamespaces: []string{fixture.AppNamespace()}, 1748 }). 1749 When(). 1750 Refresh(RefreshTypeNormal). 1751 Then(). 1752 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1753 Expect(NoConditions()) 1754 } 1755 1756 // Given application is set with --sync-option CreateNamespace=true 1757 // 1758 // application --dest-namespace does not exist 1759 // 1760 // Verify application --dest-namespace is created 1761 // 1762 // application sync successful 1763 // when application is deleted, --dest-namespace is not deleted 1764 func TestNamespacedNamespaceAutoCreation(t *testing.T) { 1765 fixture.SkipOnEnv(t, "OPENSHIFT") 1766 updatedNamespace := getNewNamespace(t) 1767 defer func() { 1768 if !t.Skipped() { 1769 _, err := fixture.Run("", "kubectl", "delete", "namespace", updatedNamespace) 1770 require.NoError(t, err) 1771 } 1772 }() 1773 Given(t). 1774 SetAppNamespace(fixture.AppNamespace()). 1775 SetTrackingMethod("annotation"). 1776 Timeout(30). 1777 Path("guestbook"). 1778 When(). 1779 CreateApp("--sync-option", "CreateNamespace=true"). 1780 Then(). 1781 Expect(NoNamespace(updatedNamespace)). 1782 When(). 1783 AppSet("--dest-namespace", updatedNamespace). 1784 Sync(). 1785 Then(). 1786 Expect(Success("")). 1787 Expect(OperationPhaseIs(OperationSucceeded)).Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 1788 Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 1789 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)). 1790 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)). 1791 When(). 1792 Delete(true). 1793 Then(). 1794 Expect(Success("")). 1795 And(func(_ *Application) { 1796 // Verify delete app does not delete the namespace auto created 1797 output, err := fixture.Run("", "kubectl", "get", "namespace", updatedNamespace) 1798 require.NoError(t, err) 1799 assert.Contains(t, output, updatedNamespace) 1800 }) 1801 } 1802 1803 // Given application is set with --sync-option CreateNamespace=true 1804 // 1805 // application --dest-namespace does not exist 1806 // 1807 // Verify application --dest-namespace is created with managedNamespaceMetadata 1808 func TestNamespacedNamespaceAutoCreationWithMetadata(t *testing.T) { 1809 fixture.SkipOnEnv(t, "OPENSHIFT") 1810 updatedNamespace := getNewNamespace(t) 1811 defer func() { 1812 if !t.Skipped() { 1813 _, err := fixture.Run("", "kubectl", "delete", "namespace", updatedNamespace) 1814 require.NoError(t, err) 1815 } 1816 }() 1817 ctx := Given(t) 1818 ctx. 1819 SetAppNamespace(fixture.AppNamespace()). 1820 SetTrackingMethod("annotation"). 1821 Timeout(30). 1822 Path("guestbook"). 1823 When(). 1824 CreateFromFile(func(app *Application) { 1825 app.Spec.SyncPolicy = &SyncPolicy{ 1826 SyncOptions: SyncOptions{"CreateNamespace=true"}, 1827 ManagedNamespaceMetadata: &ManagedNamespaceMetadata{ 1828 Labels: map[string]string{"foo": "bar"}, 1829 Annotations: map[string]string{"bar": "bat"}, 1830 }, 1831 } 1832 }). 1833 Then(). 1834 Expect(NoNamespace(updatedNamespace)). 1835 When(). 1836 AppSet("--dest-namespace", updatedNamespace). 1837 Sync(). 1838 Then(). 1839 Expect(Success("")). 1840 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 1841 assert.Empty(t, app.Status.Conditions) 1842 1843 delete(ns.Labels, "kubernetes.io/metadata.name") 1844 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 1845 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 1846 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 1847 1848 assert.Equal(t, map[string]string{"foo": "bar"}, ns.Labels) 1849 assert.Equal(t, map[string]string{"bar": "bat", "argocd.argoproj.io/sync-options": "ServerSideApply=true"}, ns.Annotations) 1850 assert.Equal(t, map[string]string{"foo": "bar"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Labels) 1851 assert.Equal(t, map[string]string{"bar": "bat"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Annotations) 1852 })). 1853 Expect(OperationPhaseIs(OperationSucceeded)).Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 1854 Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 1855 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)). 1856 When(). 1857 And(func() { 1858 errors.NewHandler(t).FailOnErr(fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Patch(t.Context(), 1859 ctx.GetName(), types.JSONPatchType, []byte(`[{ "op": "replace", "path": "/spec/syncPolicy/managedNamespaceMetadata/labels", "value": {"new":"label"} }]`), metav1.PatchOptions{})) 1860 }). 1861 Sync(). 1862 Then(). 1863 Expect(Success("")). 1864 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 1865 delete(ns.Labels, "kubernetes.io/metadata.name") 1866 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 1867 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 1868 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 1869 1870 assert.Equal(t, map[string]string{"new": "label"}, ns.Labels) 1871 assert.Equal(t, map[string]string{"bar": "bat", "argocd.argoproj.io/sync-options": "ServerSideApply=true"}, ns.Annotations) 1872 assert.Equal(t, map[string]string{"new": "label"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Labels) 1873 assert.Equal(t, map[string]string{"bar": "bat"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Annotations) 1874 })). 1875 When(). 1876 And(func() { 1877 errors.NewHandler(t).FailOnErr(fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Patch(t.Context(), 1878 ctx.GetName(), types.JSONPatchType, []byte(`[{ "op": "replace", "path": "/spec/syncPolicy/managedNamespaceMetadata/annotations", "value": {"new":"custom-annotation"} }]`), metav1.PatchOptions{})) 1879 }). 1880 Sync(). 1881 Then(). 1882 Expect(Success("")). 1883 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 1884 delete(ns.Labels, "kubernetes.io/metadata.name") 1885 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 1886 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 1887 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 1888 1889 assert.Equal(t, map[string]string{"new": "label"}, ns.Labels) 1890 assert.Equal(t, map[string]string{"new": "custom-annotation", "argocd.argoproj.io/sync-options": "ServerSideApply=true"}, ns.Annotations) 1891 assert.Equal(t, map[string]string{"new": "label"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Labels) 1892 assert.Equal(t, map[string]string{"new": "custom-annotation"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Annotations) 1893 })) 1894 } 1895 1896 // Given application is set with --sync-option CreateNamespace=true 1897 // 1898 // application --dest-namespace does not exist 1899 // 1900 // Verify application namespace manifest takes precedence over managedNamespaceMetadata 1901 func TestNamespacedNamespaceAutoCreationWithMetadataAndNsManifest(t *testing.T) { 1902 fixture.SkipOnEnv(t, "OPENSHIFT") 1903 namespace := "guestbook-ui-with-namespace-manifest" 1904 defer func() { 1905 if !t.Skipped() { 1906 _, err := fixture.Run("", "kubectl", "delete", "namespace", namespace) 1907 require.NoError(t, err) 1908 } 1909 }() 1910 1911 ctx := Given(t) 1912 ctx. 1913 SetAppNamespace(fixture.AppNamespace()). 1914 SetTrackingMethod("annotation"). 1915 Timeout(30). 1916 Path("guestbook-with-namespace-manifest"). 1917 When(). 1918 CreateFromFile(func(app *Application) { 1919 app.Spec.SyncPolicy = &SyncPolicy{ 1920 SyncOptions: SyncOptions{"CreateNamespace=true"}, 1921 ManagedNamespaceMetadata: &ManagedNamespaceMetadata{ 1922 Labels: map[string]string{"foo": "bar", "abc": "123"}, 1923 Annotations: map[string]string{"bar": "bat"}, 1924 }, 1925 } 1926 }). 1927 Then(). 1928 Expect(NoNamespace(namespace)). 1929 When(). 1930 AppSet("--dest-namespace", namespace). 1931 Sync(). 1932 Then(). 1933 Expect(Success("")). 1934 Expect(Namespace(namespace, func(_ *Application, ns *corev1.Namespace) { 1935 delete(ns.Labels, "kubernetes.io/metadata.name") 1936 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 1937 delete(ns.Labels, "kubectl.kubernetes.io/last-applied-configuration") 1938 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 1939 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 1940 1941 // The application namespace manifest takes precedence over what is in managedNamespaceMetadata 1942 assert.Equal(t, map[string]string{"test": "true"}, ns.Labels) 1943 assert.Equal(t, map[string]string{"foo": "bar", "something": "else"}, ns.Annotations) 1944 })). 1945 Expect(OperationPhaseIs(OperationSucceeded)).Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", namespace, health.HealthStatusHealthy)). 1946 Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", namespace, health.HealthStatusHealthy)). 1947 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", namespace, SyncStatusCodeSynced)) 1948 } 1949 1950 // Given application is set with --sync-option CreateNamespace=true 1951 // 1952 // application --dest-namespace exists 1953 // 1954 // Verify application --dest-namespace is updated with managedNamespaceMetadata labels and annotations 1955 func TestNamespacedNamespaceAutoCreationWithPreexistingNs(t *testing.T) { 1956 fixture.SkipOnEnv(t, "OPENSHIFT") 1957 updatedNamespace := getNewNamespace(t) 1958 defer func() { 1959 if !t.Skipped() { 1960 _, err := fixture.Run("", "kubectl", "delete", "namespace", updatedNamespace) 1961 require.NoError(t, err) 1962 } 1963 }() 1964 1965 existingNs := ` 1966 apiVersion: v1 1967 kind: Namespace 1968 metadata: 1969 name: %s 1970 labels: 1971 test: "true" 1972 annotations: 1973 something: "whatevs" 1974 ` 1975 s := fmt.Sprintf(existingNs, updatedNamespace) 1976 1977 tmpFile, err := os.CreateTemp(t.TempDir(), "") 1978 require.NoError(t, err) 1979 _, err = tmpFile.WriteString(s) 1980 require.NoError(t, err) 1981 1982 _, err = fixture.Run("", "kubectl", "apply", "-f", tmpFile.Name()) 1983 require.NoError(t, err) 1984 1985 ctx := Given(t) 1986 ctx. 1987 SetAppNamespace(fixture.AppNamespace()). 1988 SetTrackingMethod("annotation"). 1989 Timeout(30). 1990 Path("guestbook"). 1991 When(). 1992 CreateFromFile(func(app *Application) { 1993 app.Spec.SyncPolicy = &SyncPolicy{ 1994 SyncOptions: SyncOptions{"CreateNamespace=true"}, 1995 ManagedNamespaceMetadata: &ManagedNamespaceMetadata{ 1996 Labels: map[string]string{"foo": "bar"}, 1997 Annotations: map[string]string{"bar": "bat"}, 1998 }, 1999 } 2000 }). 2001 Then(). 2002 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 2003 assert.Empty(t, app.Status.Conditions) 2004 2005 delete(ns.Labels, "kubernetes.io/metadata.name") 2006 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 2007 2008 assert.Equal(t, map[string]string{"test": "true"}, ns.Labels) 2009 assert.Equal(t, map[string]string{"something": "whatevs"}, ns.Annotations) 2010 })). 2011 When(). 2012 AppSet("--dest-namespace", updatedNamespace). 2013 Sync(). 2014 Then(). 2015 Expect(Success("")). 2016 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 2017 assert.Empty(t, app.Status.Conditions) 2018 2019 delete(ns.Labels, "kubernetes.io/metadata.name") 2020 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 2021 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 2022 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 2023 2024 assert.Equal(t, map[string]string{"foo": "bar"}, ns.Labels) 2025 assert.Equal(t, map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true", "bar": "bat"}, ns.Annotations) 2026 })). 2027 When(). 2028 And(func() { 2029 errors.NewHandler(t).FailOnErr(fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Patch(t.Context(), 2030 ctx.GetName(), types.JSONPatchType, []byte(`[{ "op": "add", "path": "/spec/syncPolicy/managedNamespaceMetadata/annotations/something", "value": "hmm" }]`), metav1.PatchOptions{})) 2031 }). 2032 Sync(). 2033 Then(). 2034 Expect(Success("")). 2035 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 2036 assert.Empty(t, app.Status.Conditions) 2037 2038 delete(ns.Labels, "kubernetes.io/metadata.name") 2039 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 2040 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 2041 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 2042 2043 assert.Equal(t, map[string]string{"foo": "bar"}, ns.Labels) 2044 assert.Equal(t, map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true", "something": "hmm", "bar": "bat"}, ns.Annotations) 2045 assert.Equal(t, map[string]string{"something": "hmm", "bar": "bat"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Annotations) 2046 })). 2047 When(). 2048 And(func() { 2049 errors.NewHandler(t).FailOnErr(fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Patch(t.Context(), 2050 ctx.GetName(), types.JSONPatchType, []byte(`[{ "op": "remove", "path": "/spec/syncPolicy/managedNamespaceMetadata/annotations/something" }]`), metav1.PatchOptions{})) 2051 }). 2052 Sync(). 2053 Then(). 2054 Expect(Success("")). 2055 Expect(Namespace(updatedNamespace, func(app *Application, ns *corev1.Namespace) { 2056 assert.Empty(t, app.Status.Conditions) 2057 2058 delete(ns.Labels, "kubernetes.io/metadata.name") 2059 delete(ns.Labels, "argocd.argoproj.io/tracking-id") 2060 delete(ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") 2061 delete(ns.Annotations, "argocd.argoproj.io/tracking-id") 2062 2063 assert.Equal(t, map[string]string{"foo": "bar"}, ns.Labels) 2064 assert.Equal(t, map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true", "bar": "bat"}, ns.Annotations) 2065 assert.Equal(t, map[string]string{"bar": "bat"}, app.Spec.SyncPolicy.ManagedNamespaceMetadata.Annotations) 2066 })). 2067 Expect(OperationPhaseIs(OperationSucceeded)).Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 2068 Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 2069 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)) 2070 } 2071 2072 func TestNamespacedFailedSyncWithRetry(t *testing.T) { 2073 Given(t). 2074 SetAppNamespace(fixture.AppNamespace()). 2075 SetTrackingMethod("annotation"). 2076 Path("hook"). 2077 When(). 2078 PatchFile("hook.yaml", `[{"op": "replace", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/hook": "PreSync"}}]`). 2079 // make hook fail 2080 PatchFile("hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/command", "value": ["false"]}]`). 2081 CreateApp(). 2082 IgnoreErrors(). 2083 Sync("--retry-limit=1", "--retry-backoff-duration=1s"). 2084 Then(). 2085 Expect(OperationPhaseIs(OperationFailed)). 2086 Expect(OperationMessageContains("retried 1 times")) 2087 } 2088 2089 func TestNamespacedCreateDisableValidation(t *testing.T) { 2090 Given(t). 2091 SetAppNamespace(fixture.AppNamespace()). 2092 SetTrackingMethod("annotation"). 2093 Path("baddir"). 2094 When(). 2095 CreateApp("--validate=false"). 2096 Then(). 2097 And(func(app *Application) { 2098 _, err := fixture.RunCli("app", "create", app.QualifiedName(), "--upsert", "--validate=false", "--repo", fixture.RepoURL(fixture.RepoURLTypeFile), 2099 "--path", "baddir2", "--project", app.Spec.Project, "--dest-server", KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace()) 2100 require.NoError(t, err) 2101 }). 2102 When(). 2103 AppSet("--path", "baddir3", "--validate=false") 2104 } 2105 2106 func TestNamespacedCreateFromPartialFile(t *testing.T) { 2107 partialApp := `metadata: 2108 labels: 2109 labels.local/from-file: file 2110 labels.local/from-args: file 2111 annotations: 2112 annotations.local/from-file: file 2113 finalizers: 2114 - resources-finalizer.argocd.argoproj.io 2115 spec: 2116 syncPolicy: 2117 automated: 2118 prune: true 2119 ` 2120 2121 path := "helm-values" 2122 Given(t). 2123 SetAppNamespace(fixture.AppNamespace()). 2124 SetTrackingMethod("annotation"). 2125 When(). 2126 // app should be auto-synced once created 2127 CreateFromPartialFile(partialApp, "--path", path, "-l", "labels.local/from-args=args", "--helm-set", "foo=foo"). 2128 Then(). 2129 Expect(Success("")). 2130 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2131 Expect(NoConditions()). 2132 And(func(app *Application) { 2133 assert.Equal(t, map[string]string{"labels.local/from-file": "file", "labels.local/from-args": "args"}, app.Labels) 2134 assert.Equal(t, map[string]string{"annotations.local/from-file": "file"}, app.Annotations) 2135 assert.Equal(t, []string{ResourcesFinalizerName}, app.Finalizers) 2136 assert.Equal(t, path, app.Spec.GetSource().Path) 2137 assert.Equal(t, []HelmParameter{{Name: "foo", Value: "foo"}}, app.Spec.GetSource().Helm.Parameters) 2138 }) 2139 } 2140 2141 // Ensure actions work when using a resource action that modifies status and/or spec 2142 func TestNamespacedCRDStatusSubresourceAction(t *testing.T) { 2143 actions := ` 2144 discovery.lua: | 2145 actions = {} 2146 actions["update-spec"] = {["disabled"] = false} 2147 actions["update-status"] = {["disabled"] = false} 2148 actions["update-both"] = {["disabled"] = false} 2149 return actions 2150 definitions: 2151 - name: update-both 2152 action.lua: | 2153 obj.spec = {} 2154 obj.spec.foo = "update-both" 2155 obj.status = {} 2156 obj.status.bar = "update-both" 2157 return obj 2158 - name: update-spec 2159 action.lua: | 2160 obj.spec = {} 2161 obj.spec.foo = "update-spec" 2162 return obj 2163 - name: update-status 2164 action.lua: | 2165 obj.status = {} 2166 obj.status.bar = "update-status" 2167 return obj 2168 ` 2169 Given(t). 2170 SetAppNamespace(fixture.AppNamespace()). 2171 SetTrackingMethod("annotation"). 2172 Path("crd-subresource"). 2173 And(func() { 2174 require.NoError(t, fixture.SetResourceOverrides(map[string]ResourceOverride{ 2175 "argoproj.io/StatusSubResource": { 2176 Actions: actions, 2177 }, 2178 "argoproj.io/NonStatusSubResource": { 2179 Actions: actions, 2180 }, 2181 })) 2182 }). 2183 When().CreateApp().Sync().Then(). 2184 Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeSynced)). 2185 When(). 2186 Refresh(RefreshTypeNormal). 2187 Then(). 2188 // tests resource actions on a CRD using status subresource 2189 And(func(app *Application) { 2190 _, err := fixture.RunCli("app", "actions", "run", app.QualifiedName(), "--kind", "StatusSubResource", "update-both") 2191 require.NoError(t, err) 2192 text := errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 2193 assert.Equal(t, "update-both", text) 2194 text = errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.status.bar}")).(string) 2195 assert.Equal(t, "update-both", text) 2196 2197 _, err = fixture.RunCli("app", "actions", "run", app.QualifiedName(), "--kind", "StatusSubResource", "update-spec") 2198 require.NoError(t, err) 2199 text = errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 2200 assert.Equal(t, "update-spec", text) 2201 2202 _, err = fixture.RunCli("app", "actions", "run", app.QualifiedName(), "--kind", "StatusSubResource", "update-status") 2203 require.NoError(t, err) 2204 text = errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.status.bar}")).(string) 2205 assert.Equal(t, "update-status", text) 2206 }). 2207 // tests resource actions on a CRD *not* using status subresource 2208 And(func(app *Application) { 2209 _, err := fixture.RunCli("app", "actions", "run", app.QualifiedName(), "--kind", "NonStatusSubResource", "update-both") 2210 require.NoError(t, err) 2211 text := errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 2212 assert.Equal(t, "update-both", text) 2213 text = errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.status.bar}")).(string) 2214 assert.Equal(t, "update-both", text) 2215 2216 _, err = fixture.RunCli("app", "actions", "run", app.QualifiedName(), "--kind", "NonStatusSubResource", "update-spec") 2217 require.NoError(t, err) 2218 text = errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 2219 assert.Equal(t, "update-spec", text) 2220 2221 _, err = fixture.RunCli("app", "actions", "run", app.QualifiedName(), "--kind", "NonStatusSubResource", "update-status") 2222 require.NoError(t, err) 2223 text = errors.NewHandler(t).FailOnErr(fixture.Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.status.bar}")).(string) 2224 assert.Equal(t, "update-status", text) 2225 }) 2226 } 2227 2228 func TestNamespacedAppLogs(t *testing.T) { 2229 t.SkipNow() // Too flaky. https://github.com/argoproj/argo-cd/issues/13834 2230 fixture.SkipOnEnv(t, "OPENSHIFT") 2231 Given(t). 2232 SetAppNamespace(fixture.AppNamespace()). 2233 SetTrackingMethod("annotation"). 2234 Path("guestbook-logs"). 2235 When(). 2236 CreateApp(). 2237 Sync(). 2238 Then(). 2239 Expect(HealthIs(health.HealthStatusHealthy)). 2240 And(func(app *Application) { 2241 out, err := fixture.RunCliWithRetry(5, "app", "logs", app.QualifiedName(), "--kind", "Deployment", "--group", "", "--name", "guestbook-ui") 2242 require.NoError(t, err) 2243 assert.Contains(t, out, "Hi") 2244 }). 2245 And(func(app *Application) { 2246 out, err := fixture.RunCliWithRetry(5, "app", "logs", app.QualifiedName(), "--kind", "Pod") 2247 require.NoError(t, err) 2248 assert.Contains(t, out, "Hi") 2249 }). 2250 And(func(app *Application) { 2251 out, err := fixture.RunCliWithRetry(5, "app", "logs", app.QualifiedName(), "--kind", "Service") 2252 require.NoError(t, err) 2253 assert.NotContains(t, out, "Hi") 2254 }) 2255 } 2256 2257 func TestNamespacedAppWaitOperationInProgress(t *testing.T) { 2258 Given(t). 2259 SetAppNamespace(fixture.AppNamespace()). 2260 SetTrackingMethod("annotation"). 2261 And(func() { 2262 require.NoError(t, fixture.SetResourceOverrides(map[string]ResourceOverride{ 2263 "batch/Job": { 2264 HealthLua: `return { status = 'Running' }`, 2265 }, 2266 "apps/Deployment": { 2267 HealthLua: `return { status = 'Suspended' }`, 2268 }, 2269 })) 2270 }). 2271 Async(true). 2272 Path("hook-and-deployment"). 2273 When(). 2274 CreateApp(). 2275 Sync(). 2276 Then(). 2277 // stuck in running state 2278 Expect(OperationPhaseIs(OperationRunning)). 2279 When(). 2280 Then(). 2281 And(func(app *Application) { 2282 _, err := fixture.RunCli("app", "wait", app.QualifiedName(), "--suspended") 2283 require.NoError(t, err) 2284 }) 2285 } 2286 2287 func TestNamespacedSyncOptionReplace(t *testing.T) { 2288 Given(t). 2289 SetAppNamespace(fixture.AppNamespace()). 2290 SetTrackingMethod("annotation"). 2291 Path("config-map"). 2292 When(). 2293 PatchFile("config-map.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Replace=true"}}]`). 2294 CreateApp(). 2295 Sync(). 2296 Then(). 2297 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2298 And(func(app *Application) { 2299 assert.Equal(t, "configmap/my-map created", app.Status.OperationState.SyncResult.Resources[0].Message) 2300 }). 2301 When(). 2302 Sync(). 2303 Then(). 2304 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2305 And(func(app *Application) { 2306 assert.Equal(t, "configmap/my-map replaced", app.Status.OperationState.SyncResult.Resources[0].Message) 2307 }) 2308 } 2309 2310 func TestNamespacedSyncOptionReplaceFromCLI(t *testing.T) { 2311 Given(t). 2312 SetAppNamespace(fixture.AppNamespace()). 2313 SetTrackingMethod("annotation"). 2314 Path("config-map"). 2315 Replace(). 2316 When(). 2317 CreateApp(). 2318 Sync(). 2319 Then(). 2320 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2321 And(func(app *Application) { 2322 assert.Equal(t, "configmap/my-map created", app.Status.OperationState.SyncResult.Resources[0].Message) 2323 }). 2324 When(). 2325 Sync(). 2326 Then(). 2327 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2328 And(func(app *Application) { 2329 assert.Equal(t, "configmap/my-map replaced", app.Status.OperationState.SyncResult.Resources[0].Message) 2330 }) 2331 } 2332 2333 func TestNamespacedDiscoverNewCommit(t *testing.T) { 2334 var sha string 2335 Given(t). 2336 SetAppNamespace(fixture.AppNamespace()). 2337 SetTrackingMethod("annotation"). 2338 Path("config-map"). 2339 When(). 2340 CreateApp(). 2341 Sync(). 2342 Then(). 2343 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2344 And(func(app *Application) { 2345 sha = app.Status.Sync.Revision 2346 assert.NotEmpty(t, sha) 2347 }). 2348 When(). 2349 PatchFile("config-map.yaml", `[{"op": "replace", "path": "/data/foo", "value": "hello"}]`). 2350 Then(). 2351 Expect(SyncStatusIs(SyncStatusCodeSynced)). 2352 // make sure new commit is not discovered immediately after push 2353 And(func(app *Application) { 2354 assert.Equal(t, sha, app.Status.Sync.Revision) 2355 }). 2356 When(). 2357 // make sure new commit is not discovered after refresh is requested 2358 Refresh(RefreshTypeNormal). 2359 Then(). 2360 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 2361 And(func(app *Application) { 2362 assert.NotEqual(t, sha, app.Status.Sync.Revision) 2363 }) 2364 } 2365 2366 func TestNamespacedDisableManifestGeneration(t *testing.T) { 2367 Given(t). 2368 SetAppNamespace(fixture.AppNamespace()). 2369 SetTrackingMethod("annotation"). 2370 Path("guestbook"). 2371 When(). 2372 CreateApp(). 2373 Refresh(RefreshTypeHard). 2374 Then(). 2375 And(func(app *Application) { 2376 assert.Equal(t, ApplicationSourceTypeKustomize, app.Status.SourceType) 2377 }). 2378 When(). 2379 And(func() { 2380 require.NoError(t, fixture.SetEnableManifestGeneration(map[ApplicationSourceType]bool{ 2381 ApplicationSourceTypeKustomize: false, 2382 })) 2383 }). 2384 Refresh(RefreshTypeHard). 2385 Then(). 2386 And(func(_ *Application) { 2387 // Wait for refresh to complete 2388 time.Sleep(1 * time.Second) 2389 }). 2390 And(func(app *Application) { 2391 assert.Equal(t, ApplicationSourceTypeDirectory, app.Status.SourceType) 2392 }) 2393 } 2394 2395 func TestCreateAppInNotAllowedNamespace(t *testing.T) { 2396 ctx := Given(t) 2397 ctx. 2398 ProjectSpec(AppProjectSpec{ 2399 SourceRepos: []string{"*"}, 2400 SourceNamespaces: []string{"default"}, 2401 Destinations: []ApplicationDestination{ 2402 {Namespace: "*", Server: "*"}, 2403 }, 2404 }). 2405 Path(guestbookPath). 2406 SetTrackingMethod("annotation"). 2407 SetAppNamespace("default"). 2408 When(). 2409 IgnoreErrors(). 2410 CreateApp(). 2411 Then(). 2412 Expect(DoesNotExist()). 2413 Expect(Error("", "namespace 'default' is not permitted")) 2414 }