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