github.com/argoproj/argo-cd@v1.8.7/test/e2e/app_management_test.go (about) 1 package e2e 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "os" 8 "path" 9 "reflect" 10 "regexp" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/argoproj/gitops-engine/pkg/diff" 16 "github.com/argoproj/gitops-engine/pkg/health" 17 . "github.com/argoproj/gitops-engine/pkg/sync/common" 18 "github.com/argoproj/gitops-engine/pkg/utils/kube" 19 "github.com/argoproj/pkg/errors" 20 log "github.com/sirupsen/logrus" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 v1 "k8s.io/api/core/v1" 24 networkingv1beta "k8s.io/api/networking/v1beta1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/apimachinery/pkg/util/intstr" 30 "k8s.io/utils/pointer" 31 32 "github.com/argoproj/argo-cd/common" 33 applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application" 34 repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository" 35 . "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 36 . "github.com/argoproj/argo-cd/test/e2e/fixture" 37 . "github.com/argoproj/argo-cd/test/e2e/fixture/app" 38 . "github.com/argoproj/argo-cd/util/argo" 39 . "github.com/argoproj/argo-cd/util/errors" 40 "github.com/argoproj/argo-cd/util/io" 41 "github.com/argoproj/argo-cd/util/settings" 42 ) 43 44 const ( 45 guestbookPath = "guestbook" 46 guestbookPathLocal = "./testdata/guestbook_local" 47 globalWithNoNameSpace = "global-with-no-namesapce" 48 guestbookWithNamespace = "guestbook-with-namespace" 49 ) 50 51 func TestSyncToUnsignedCommit(t *testing.T) { 52 Given(t). 53 Project("gpg"). 54 Path(guestbookPath). 55 When(). 56 IgnoreErrors(). 57 Create(). 58 Sync(). 59 Then(). 60 Expect(OperationPhaseIs(OperationError)). 61 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 62 Expect(HealthIs(health.HealthStatusMissing)) 63 } 64 65 func TestSyncToSignedCommitWithoutKnownKey(t *testing.T) { 66 Given(t). 67 Project("gpg"). 68 Path(guestbookPath). 69 When(). 70 AddSignedFile("test.yaml", "null"). 71 IgnoreErrors(). 72 Create(). 73 Sync(). 74 Then(). 75 Expect(OperationPhaseIs(OperationError)). 76 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 77 Expect(HealthIs(health.HealthStatusMissing)) 78 } 79 80 func TestSyncToSignedCommitKeyWithKnownKey(t *testing.T) { 81 Given(t). 82 Project("gpg"). 83 Path(guestbookPath). 84 GPGPublicKeyAdded(). 85 Sleep(2). 86 When(). 87 AddSignedFile("test.yaml", "null"). 88 IgnoreErrors(). 89 Create(). 90 Sync(). 91 Then(). 92 Expect(OperationPhaseIs(OperationSucceeded)). 93 Expect(SyncStatusIs(SyncStatusCodeSynced)). 94 Expect(HealthIs(health.HealthStatusHealthy)) 95 } 96 97 func TestAppCreation(t *testing.T) { 98 ctx := Given(t) 99 100 ctx. 101 Path(guestbookPath). 102 When(). 103 Create(). 104 Then(). 105 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 106 And(func(app *Application) { 107 assert.Equal(t, Name(), app.Name) 108 assert.Equal(t, RepoURL(RepoURLTypeFile), app.Spec.Source.RepoURL) 109 assert.Equal(t, guestbookPath, app.Spec.Source.Path) 110 assert.Equal(t, DeploymentNamespace(), app.Spec.Destination.Namespace) 111 assert.Equal(t, common.KubernetesInternalAPIServerAddr, app.Spec.Destination.Server) 112 }). 113 Expect(Event(EventReasonResourceCreated, "create")). 114 And(func(_ *Application) { 115 // app should be listed 116 output, err := RunCli("app", "list") 117 assert.NoError(t, err) 118 assert.Contains(t, output, Name()) 119 }). 120 When(). 121 // ensure that create is idempotent 122 Create(). 123 Then(). 124 Given(). 125 Revision("master"). 126 When(). 127 // ensure that update replaces spec and merge labels and annotations 128 And(func() { 129 FailOnErr(AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Patch(context.Background(), 130 ctx.GetName(), types.MergePatchType, []byte(`{"metadata": {"labels": { "test": "label" }, "annotations": { "test": "annotation" }}}`), metav1.PatchOptions{})) 131 }). 132 Create("--upsert"). 133 Then(). 134 And(func(app *Application) { 135 assert.Equal(t, "label", app.Labels["test"]) 136 assert.Equal(t, "annotation", app.Annotations["test"]) 137 assert.Equal(t, "master", app.Spec.Source.TargetRevision) 138 }) 139 } 140 141 // demonstrate that we cannot use a standard sync when an immutable field is changed, we must use "force" 142 func TestImmutableChange(t *testing.T) { 143 text := FailOnErr(Run(".", "kubectl", "get", "service", "-n", "kube-system", "kube-dns", "-o", "jsonpath={.spec.clusterIP}")).(string) 144 parts := strings.Split(text, ".") 145 n := rand.Intn(254) 146 ip1 := fmt.Sprintf("%s.%s.%s.%d", parts[0], parts[1], parts[2], n) 147 ip2 := fmt.Sprintf("%s.%s.%s.%d", parts[0], parts[1], parts[2], n+1) 148 Given(t). 149 Path("service"). 150 When(). 151 Create(). 152 PatchFile("service.yaml", fmt.Sprintf(`[{"op": "add", "path": "/spec/clusterIP", "value": "%s"}]`, ip1)). 153 Sync(). 154 Then(). 155 Expect(OperationPhaseIs(OperationSucceeded)). 156 Expect(SyncStatusIs(SyncStatusCodeSynced)). 157 Expect(HealthIs(health.HealthStatusHealthy)). 158 When(). 159 PatchFile("service.yaml", fmt.Sprintf(`[{"op": "add", "path": "/spec/clusterIP", "value": "%s"}]`, ip2)). 160 IgnoreErrors(). 161 Sync(). 162 DoNotIgnoreErrors(). 163 Then(). 164 Expect(OperationPhaseIs(OperationFailed)). 165 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 166 Expect(ResourceResultNumbering(1)). 167 Expect(ResourceResultIs(ResourceResult{ 168 Kind: "Service", 169 Version: "v1", 170 Namespace: DeploymentNamespace(), 171 Name: "my-service", 172 SyncPhase: "Sync", 173 Status: "SyncFailed", 174 HookPhase: "Failed", 175 Message: fmt.Sprintf(`Service "my-service" is invalid: spec.clusterIP: Invalid value: "%s": field is immutable`, ip2), 176 })). 177 // now we can do this will a force 178 Given(). 179 Force(). 180 When(). 181 Sync(). 182 Then(). 183 Expect(OperationPhaseIs(OperationSucceeded)). 184 Expect(SyncStatusIs(SyncStatusCodeSynced)). 185 Expect(HealthIs(health.HealthStatusHealthy)) 186 } 187 188 func TestInvalidAppProject(t *testing.T) { 189 Given(t). 190 Path(guestbookPath). 191 Project("does-not-exist"). 192 When(). 193 IgnoreErrors(). 194 Create(). 195 Then(). 196 Expect(Error("", "application references project does-not-exist which does not exist")) 197 } 198 199 func TestAppDeletion(t *testing.T) { 200 Given(t). 201 Path(guestbookPath). 202 When(). 203 Create(). 204 Then(). 205 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 206 When(). 207 Delete(true). 208 Then(). 209 Expect(DoesNotExist()). 210 Expect(Event(EventReasonResourceDeleted, "delete")) 211 212 output, err := RunCli("app", "list") 213 assert.NoError(t, err) 214 assert.NotContains(t, output, Name()) 215 } 216 217 func TestAppLabels(t *testing.T) { 218 Given(t). 219 Path("config-map"). 220 When(). 221 Create("-l", "foo=bar"). 222 Then(). 223 And(func(app *Application) { 224 assert.Contains(t, FailOnErr(RunCli("app", "list")), Name()) 225 assert.Contains(t, FailOnErr(RunCli("app", "list", "-l", "foo=bar")), Name()) 226 assert.NotContains(t, FailOnErr(RunCli("app", "list", "-l", "foo=rubbish")), Name()) 227 }). 228 Given(). 229 // remove both name and replace labels means nothing will sync 230 Name(""). 231 When(). 232 IgnoreErrors(). 233 Sync("-l", "foo=rubbish"). 234 DoNotIgnoreErrors(). 235 Then(). 236 Expect(Error("", "no apps match selector foo=rubbish")). 237 // check we can update the app and it is then sync'd 238 Given(). 239 When(). 240 Sync("-l", "foo=bar") 241 } 242 243 func TestTrackAppStateAndSyncApp(t *testing.T) { 244 Given(t). 245 Path(guestbookPath). 246 When(). 247 Create(). 248 Sync(). 249 Then(). 250 Expect(OperationPhaseIs(OperationSucceeded)). 251 Expect(SyncStatusIs(SyncStatusCodeSynced)). 252 Expect(HealthIs(health.HealthStatusHealthy)). 253 Expect(Success(fmt.Sprintf("Service %s guestbook-ui Synced ", DeploymentNamespace()))). 254 Expect(Success(fmt.Sprintf("apps Deployment %s guestbook-ui Synced", DeploymentNamespace()))). 255 Expect(Event(EventReasonResourceUpdated, "sync")). 256 And(func(app *Application) { 257 assert.NotNil(t, app.Status.OperationState.SyncResult) 258 }) 259 } 260 261 func TestAppRollbackSuccessful(t *testing.T) { 262 Given(t). 263 Path(guestbookPath). 264 When(). 265 Create(). 266 Sync(). 267 Then(). 268 Expect(SyncStatusIs(SyncStatusCodeSynced)). 269 And(func(app *Application) { 270 assert.NotEmpty(t, app.Status.Sync.Revision) 271 }). 272 And(func(app *Application) { 273 appWithHistory := app.DeepCopy() 274 appWithHistory.Status.History = []RevisionHistory{{ 275 ID: 1, 276 Revision: app.Status.Sync.Revision, 277 DeployedAt: metav1.Time{Time: metav1.Now().UTC().Add(-1 * time.Minute)}, 278 Source: app.Spec.Source, 279 }, { 280 ID: 2, 281 Revision: "cdb", 282 DeployedAt: metav1.Time{Time: metav1.Now().UTC().Add(-2 * time.Minute)}, 283 Source: app.Spec.Source, 284 }} 285 patch, _, err := diff.CreateTwoWayMergePatch(app, appWithHistory, &Application{}) 286 assert.NoError(t, err) 287 288 app, err = AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{}) 289 assert.NoError(t, err) 290 291 // sync app and make sure it reaches InSync state 292 _, err = RunCli("app", "rollback", app.Name, "1") 293 assert.NoError(t, err) 294 295 }). 296 Expect(Event(EventReasonOperationStarted, "rollback")). 297 Expect(SyncStatusIs(SyncStatusCodeSynced)). 298 And(func(app *Application) { 299 assert.Equal(t, SyncStatusCodeSynced, app.Status.Sync.Status) 300 assert.NotNil(t, app.Status.OperationState.SyncResult) 301 assert.Equal(t, 2, len(app.Status.OperationState.SyncResult.Resources)) 302 assert.Equal(t, OperationSucceeded, app.Status.OperationState.Phase) 303 assert.Equal(t, 3, len(app.Status.History)) 304 }) 305 } 306 307 func TestComparisonFailsIfClusterNotAdded(t *testing.T) { 308 Given(t). 309 Path(guestbookPath). 310 DestServer("https://not-registered-cluster/api"). 311 When(). 312 IgnoreErrors(). 313 Create(). 314 Then(). 315 Expect(DoesNotExist()) 316 } 317 318 func TestCannotSetInvalidPath(t *testing.T) { 319 Given(t). 320 Path(guestbookPath). 321 When(). 322 Create(). 323 IgnoreErrors(). 324 AppSet("--path", "garbage"). 325 Then(). 326 Expect(Error("", "app path does not exist")) 327 } 328 329 func TestManipulateApplicationResources(t *testing.T) { 330 Given(t). 331 Path(guestbookPath). 332 When(). 333 Create(). 334 Sync(). 335 Then(). 336 Expect(SyncStatusIs(SyncStatusCodeSynced)). 337 And(func(app *Application) { 338 manifests, err := RunCli("app", "manifests", app.Name, "--source", "live") 339 assert.NoError(t, err) 340 resources, err := kube.SplitYAML([]byte(manifests)) 341 assert.NoError(t, err) 342 343 index := -1 344 for i := range resources { 345 if resources[i].GetKind() == kube.DeploymentKind { 346 index = i 347 break 348 } 349 } 350 351 assert.True(t, index > -1) 352 353 deployment := resources[index] 354 355 closer, client, err := ArgoCDClientset.NewApplicationClient() 356 assert.NoError(t, err) 357 defer io.Close(closer) 358 359 _, err = client.DeleteResource(context.Background(), &applicationpkg.ApplicationResourceDeleteRequest{ 360 Name: &app.Name, 361 Group: deployment.GroupVersionKind().Group, 362 Kind: deployment.GroupVersionKind().Kind, 363 Version: deployment.GroupVersionKind().Version, 364 Namespace: deployment.GetNamespace(), 365 ResourceName: deployment.GetName(), 366 }) 367 assert.NoError(t, err) 368 }). 369 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)) 370 } 371 372 func assetSecretDataHidden(t *testing.T, manifest string) { 373 secret, err := UnmarshalToUnstructured(manifest) 374 assert.NoError(t, err) 375 376 _, hasStringData, err := unstructured.NestedMap(secret.Object, "stringData") 377 assert.NoError(t, err) 378 assert.False(t, hasStringData) 379 380 secretData, hasData, err := unstructured.NestedMap(secret.Object, "data") 381 assert.NoError(t, err) 382 assert.True(t, hasData) 383 for _, v := range secretData { 384 assert.Regexp(t, regexp.MustCompile(`[*]*`), v) 385 } 386 var lastAppliedConfigAnnotation string 387 annotations := secret.GetAnnotations() 388 if annotations != nil { 389 lastAppliedConfigAnnotation = annotations[v1.LastAppliedConfigAnnotation] 390 } 391 if lastAppliedConfigAnnotation != "" { 392 assetSecretDataHidden(t, lastAppliedConfigAnnotation) 393 } 394 } 395 396 func TestAppWithSecrets(t *testing.T) { 397 closer, client, err := ArgoCDClientset.NewApplicationClient() 398 assert.NoError(t, err) 399 defer io.Close(closer) 400 401 Given(t). 402 Path("secrets"). 403 When(). 404 Create(). 405 Sync(). 406 Then(). 407 Expect(SyncStatusIs(SyncStatusCodeSynced)). 408 And(func(app *Application) { 409 res := FailOnErr(client.GetResource(context.Background(), &applicationpkg.ApplicationResourceRequest{ 410 Namespace: app.Spec.Destination.Namespace, 411 Kind: kube.SecretKind, 412 Group: "", 413 Name: &app.Name, 414 Version: "v1", 415 ResourceName: "test-secret", 416 })).(*applicationpkg.ApplicationResourceResponse) 417 assetSecretDataHidden(t, res.Manifest) 418 419 manifests, err := client.GetManifests(context.Background(), &applicationpkg.ApplicationManifestQuery{Name: &app.Name}) 420 errors.CheckError(err) 421 422 for _, manifest := range manifests.Manifests { 423 assetSecretDataHidden(t, manifest) 424 } 425 426 diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string) 427 assert.Empty(t, diffOutput) 428 429 // make sure resource update error does not print secret details 430 _, err = RunCli("app", "patch-resource", "test-app-with-secrets", "--resource-name", "test-secret", 431 "--kind", "Secret", "--patch", `{"op": "add", "path": "/data", "value": "hello"}'`, 432 "--patch-type", "application/json-patch+json") 433 require.Error(t, err) 434 assert.Contains(t, err.Error(), fmt.Sprintf("failed to patch Secret %s/test-secret", DeploymentNamespace())) 435 assert.NotContains(t, err.Error(), "username") 436 assert.NotContains(t, err.Error(), "password") 437 438 // patch secret and make sure app is out of sync and diff detects the change 439 FailOnErr(KubeClientset.CoreV1().Secrets(DeploymentNamespace()).Patch(context.Background(), 440 "test-secret", types.JSONPatchType, []byte(`[ 441 {"op": "remove", "path": "/data/username"}, 442 {"op": "add", "path": "/stringData", "value": {"password": "foo"}} 443 ]`), metav1.PatchOptions{})) 444 }). 445 When(). 446 Refresh(RefreshTypeNormal). 447 Then(). 448 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 449 And(func(app *Application) { 450 diffOutput, err := RunCli("app", "diff", app.Name) 451 assert.Error(t, err) 452 assert.Contains(t, diffOutput, "username: ++++++++") 453 assert.Contains(t, diffOutput, "password: ++++++++++++") 454 455 // local diff should ignore secrets 456 diffOutput = FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata/secrets")).(string) 457 assert.Empty(t, diffOutput) 458 459 // ignore missing field and make sure diff shows no difference 460 app.Spec.IgnoreDifferences = []ResourceIgnoreDifferences{{ 461 Kind: kube.SecretKind, JSONPointers: []string{"/data"}, 462 }} 463 FailOnErr(client.UpdateSpec(context.Background(), &applicationpkg.ApplicationUpdateSpecRequest{Name: &app.Name, Spec: app.Spec})) 464 }). 465 When(). 466 Refresh(RefreshTypeNormal). 467 Then(). 468 Expect(OperationPhaseIs(OperationSucceeded)). 469 Expect(SyncStatusIs(SyncStatusCodeSynced)). 470 And(func(app *Application) { 471 diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string) 472 assert.Empty(t, diffOutput) 473 }). 474 // verify not committed secret also ignore during diffing 475 When(). 476 WriteFile("secret3.yaml", ` 477 apiVersion: v1 478 kind: Secret 479 metadata: 480 name: test-secret3 481 stringData: 482 username: test-username`). 483 Then(). 484 And(func(app *Application) { 485 diffOutput := FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata/secrets")).(string) 486 assert.Empty(t, diffOutput) 487 }) 488 } 489 490 func TestResourceDiffing(t *testing.T) { 491 Given(t). 492 Path(guestbookPath). 493 When(). 494 Create(). 495 Sync(). 496 Then(). 497 Expect(SyncStatusIs(SyncStatusCodeSynced)). 498 And(func(app *Application) { 499 // Patch deployment 500 _, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Patch(context.Background(), 501 "guestbook-ui", types.JSONPatchType, []byte(`[{ "op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "test" }]`), metav1.PatchOptions{}) 502 assert.NoError(t, err) 503 }). 504 When(). 505 Refresh(RefreshTypeNormal). 506 Then(). 507 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 508 And(func(app *Application) { 509 diffOutput, err := RunCli("app", "diff", app.Name, "--local", "testdata/guestbook") 510 assert.Error(t, err) 511 assert.Contains(t, diffOutput, fmt.Sprintf("===== apps/Deployment %s/guestbook-ui ======", DeploymentNamespace())) 512 }). 513 Given(). 514 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": { 515 IgnoreDifferences: OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers/0/image"}}, 516 }}). 517 When(). 518 Refresh(RefreshTypeNormal). 519 Then(). 520 Expect(SyncStatusIs(SyncStatusCodeSynced)). 521 And(func(app *Application) { 522 diffOutput, err := RunCli("app", "diff", app.Name, "--local", "testdata/guestbook") 523 assert.NoError(t, err) 524 assert.Empty(t, diffOutput) 525 }) 526 } 527 528 func TestCRDs(t *testing.T) { 529 testEdgeCasesApplicationResources(t, "crd-creation", health.HealthStatusHealthy) 530 } 531 532 func TestKnownTypesInCRDDiffing(t *testing.T) { 533 dummiesGVR := schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "dummies"} 534 535 Given(t). 536 Path("crd-creation"). 537 When().Create().Sync().Then(). 538 Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeSynced)). 539 When(). 540 And(func() { 541 dummyResIf := DynamicClientset.Resource(dummiesGVR).Namespace(DeploymentNamespace()) 542 patchData := []byte(`{"spec":{"requests": {"cpu": "2"}}}`) 543 FailOnErr(dummyResIf.Patch(context.Background(), "dummy-crd-instance", types.MergePatchType, patchData, metav1.PatchOptions{})) 544 }).Refresh(RefreshTypeNormal). 545 Then(). 546 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 547 When(). 548 And(func() { 549 SetResourceOverrides(map[string]ResourceOverride{ 550 "argoproj.io/Dummy": { 551 KnownTypeFields: []KnownTypeField{{ 552 Field: "spec.requests", 553 Type: "core/v1/ResourceList", 554 }}, 555 }, 556 }) 557 }). 558 Refresh(RefreshTypeNormal). 559 Then(). 560 Expect(SyncStatusIs(SyncStatusCodeSynced)) 561 } 562 563 func TestDuplicatedResources(t *testing.T) { 564 testEdgeCasesApplicationResources(t, "duplicated-resources", health.HealthStatusHealthy) 565 } 566 567 func TestConfigMap(t *testing.T) { 568 testEdgeCasesApplicationResources(t, "config-map", health.HealthStatusHealthy, "my-map Synced configmap/my-map created") 569 } 570 571 func TestFailedConversion(t *testing.T) { 572 if os.Getenv("ARGOCD_E2E_K3S") == "true" { 573 t.SkipNow() 574 } 575 defer func() { 576 FailOnErr(Run("", "kubectl", "delete", "apiservice", "v1beta1.metrics.k8s.io")) 577 }() 578 579 testEdgeCasesApplicationResources(t, "failed-conversion", health.HealthStatusProgressing) 580 } 581 582 func testEdgeCasesApplicationResources(t *testing.T, appPath string, statusCode health.HealthStatusCode, message ...string) { 583 expect := Given(t). 584 Path(appPath). 585 When(). 586 Create(). 587 Sync(). 588 Then(). 589 Expect(OperationPhaseIs(OperationSucceeded)). 590 Expect(SyncStatusIs(SyncStatusCodeSynced)) 591 for i := range message { 592 expect = expect.Expect(Success(message[i])) 593 } 594 expect. 595 Expect(HealthIs(statusCode)). 596 And(func(app *Application) { 597 diffOutput, err := RunCli("app", "diff", app.Name, "--local", path.Join("testdata", appPath)) 598 assert.Empty(t, diffOutput) 599 assert.NoError(t, err) 600 }) 601 } 602 603 func TestKsonnetApp(t *testing.T) { 604 Given(t). 605 Path("ksonnet"). 606 Env("prod"). 607 // Null out dest server to verify that destination is inferred from ksonnet app 608 Parameter("guestbook-ui=image=gcr.io/heptio-images/ks-guestbook-demo:0.1"). 609 DestServer(""). 610 When(). 611 Create(). 612 Sync(). 613 Then(). 614 And(func(app *Application) { 615 closer, client, err := ArgoCDClientset.NewRepoClient() 616 assert.NoError(t, err) 617 defer io.Close(closer) 618 619 details, err := client.GetAppDetails(context.Background(), &repositorypkg.RepoAppDetailsQuery{ 620 Source: &app.Spec.Source, 621 }) 622 assert.NoError(t, err) 623 624 serviceType := "" 625 for _, param := range details.Ksonnet.Parameters { 626 if param.Name == "type" && param.Component == "guestbook-ui" { 627 serviceType = param.Value 628 } 629 } 630 assert.Equal(t, serviceType, "LoadBalancer") 631 }) 632 } 633 634 const actionsConfig = `discovery.lua: return { sample = {} } 635 definitions: 636 - name: sample 637 action.lua: | 638 obj.metadata.labels.sample = 'test' 639 return obj` 640 641 func TestResourceAction(t *testing.T) { 642 Given(t). 643 Path(guestbookPath). 644 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {Actions: actionsConfig}}). 645 When(). 646 Create(). 647 Sync(). 648 Then(). 649 And(func(app *Application) { 650 651 closer, client, err := ArgoCDClientset.NewApplicationClient() 652 assert.NoError(t, err) 653 defer io.Close(closer) 654 655 actions, err := client.ListResourceActions(context.Background(), &applicationpkg.ApplicationResourceRequest{ 656 Name: &app.Name, 657 Group: "apps", 658 Kind: "Deployment", 659 Version: "v1", 660 Namespace: DeploymentNamespace(), 661 ResourceName: "guestbook-ui", 662 }) 663 assert.NoError(t, err) 664 assert.Equal(t, []ResourceAction{{Name: "sample", Disabled: false}}, actions.Actions) 665 666 _, err = client.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{Name: &app.Name, 667 Group: "apps", 668 Kind: "Deployment", 669 Version: "v1", 670 Namespace: DeploymentNamespace(), 671 ResourceName: "guestbook-ui", 672 Action: "sample", 673 }) 674 assert.NoError(t, err) 675 676 deployment, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{}) 677 assert.NoError(t, err) 678 679 assert.Equal(t, "test", deployment.Labels["sample"]) 680 }) 681 } 682 683 func TestSyncResourceByLabel(t *testing.T) { 684 Given(t). 685 Path(guestbookPath). 686 When(). 687 Create(). 688 Sync(). 689 Then(). 690 And(func(app *Application) { 691 _, _ = RunCli("app", "sync", app.Name, "--label", fmt.Sprintf("app.kubernetes.io/instance=%s", app.Name)) 692 }). 693 Expect(SyncStatusIs(SyncStatusCodeSynced)). 694 And(func(app *Application) { 695 _, err := RunCli("app", "sync", app.Name, "--label", "this-label=does-not-exist") 696 assert.Error(t, err) 697 assert.Contains(t, err.Error(), "level=fatal") 698 }) 699 } 700 701 func TestLocalManifestSync(t *testing.T) { 702 Given(t). 703 Path(guestbookPath). 704 When(). 705 Create(). 706 Sync(). 707 Then(). 708 And(func(app *Application) { 709 res, _ := RunCli("app", "manifests", app.Name) 710 assert.Contains(t, res, "containerPort: 80") 711 assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.2") 712 }). 713 Given(). 714 LocalPath(guestbookPathLocal). 715 When(). 716 Sync(). 717 Then(). 718 Expect(SyncStatusIs(SyncStatusCodeSynced)). 719 And(func(app *Application) { 720 res, _ := RunCli("app", "manifests", app.Name) 721 assert.Contains(t, res, "containerPort: 81") 722 assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.3") 723 }). 724 Given(). 725 LocalPath(""). 726 When(). 727 Sync(). 728 Then(). 729 Expect(SyncStatusIs(SyncStatusCodeSynced)). 730 And(func(app *Application) { 731 res, _ := RunCli("app", "manifests", app.Name) 732 assert.Contains(t, res, "containerPort: 80") 733 assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.2") 734 }) 735 } 736 737 func TestLocalSync(t *testing.T) { 738 Given(t). 739 // we've got to use Helm as this uses kubeVersion 740 Path("helm"). 741 When(). 742 Create(). 743 Then(). 744 And(func(app *Application) { 745 FailOnErr(RunCli("app", "sync", app.Name, "--local", "testdata/helm")) 746 }) 747 } 748 749 func TestNoLocalSyncWithAutosyncEnabled(t *testing.T) { 750 Given(t). 751 Path(guestbookPath). 752 When(). 753 Create(). 754 Sync(). 755 Then(). 756 And(func(app *Application) { 757 _, err := RunCli("app", "set", app.Name, "--sync-policy", "automated") 758 assert.NoError(t, err) 759 760 _, err = RunCli("app", "sync", app.Name, "--local", guestbookPathLocal) 761 assert.Error(t, err) 762 }) 763 } 764 765 func TestLocalSyncDryRunWithAutosyncEnabled(t *testing.T) { 766 Given(t). 767 Path(guestbookPath). 768 When(). 769 Create(). 770 Sync(). 771 Then(). 772 And(func(app *Application) { 773 _, err := RunCli("app", "set", app.Name, "--sync-policy", "automated") 774 assert.NoError(t, err) 775 776 appBefore := app.DeepCopy() 777 _, err = RunCli("app", "sync", app.Name, "--dry-run", "--local", guestbookPathLocal) 778 assert.NoError(t, err) 779 780 appAfter := app.DeepCopy() 781 assert.True(t, reflect.DeepEqual(appBefore, appAfter)) 782 }) 783 } 784 785 func TestSyncAsync(t *testing.T) { 786 Given(t). 787 Path(guestbookPath). 788 Async(true). 789 When(). 790 Create(). 791 Sync(). 792 Then(). 793 Expect(Success("")). 794 Expect(OperationPhaseIs(OperationSucceeded)). 795 Expect(SyncStatusIs(SyncStatusCodeSynced)) 796 } 797 798 func TestPermissions(t *testing.T) { 799 EnsureCleanState(t) 800 appName := Name() 801 _, err := RunCli("proj", "create", "test") 802 assert.NoError(t, err) 803 804 // make sure app cannot be created without permissions in project 805 _, err = RunCli("app", "create", appName, "--repo", RepoURL(RepoURLTypeFile), 806 "--path", guestbookPath, "--project", "test", "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace()) 807 assert.Error(t, err) 808 sourceError := fmt.Sprintf("application repo %s is not permitted in project 'test'", RepoURL(RepoURLTypeFile)) 809 destinationError := fmt.Sprintf("application destination {%s %s} is not permitted in project 'test'", common.KubernetesInternalAPIServerAddr, DeploymentNamespace()) 810 811 assert.Contains(t, err.Error(), sourceError) 812 assert.Contains(t, err.Error(), destinationError) 813 814 proj, err := AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Get(context.Background(), "test", metav1.GetOptions{}) 815 assert.NoError(t, err) 816 817 proj.Spec.Destinations = []ApplicationDestination{{Server: "*", Namespace: "*"}} 818 proj.Spec.SourceRepos = []string{"*"} 819 proj, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{}) 820 assert.NoError(t, err) 821 822 // make sure controller report permissions issues in conditions 823 _, err = RunCli("app", "create", appName, "--repo", RepoURL(RepoURLTypeFile), 824 "--path", guestbookPath, "--project", "test", "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace()) 825 assert.NoError(t, err) 826 defer func() { 827 err = AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Delete(context.Background(), appName, metav1.DeleteOptions{}) 828 assert.NoError(t, err) 829 }() 830 831 proj.Spec.Destinations = []ApplicationDestination{} 832 proj.Spec.SourceRepos = []string{} 833 _, err = AppClientset.ArgoprojV1alpha1().AppProjects(ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{}) 834 assert.NoError(t, err) 835 time.Sleep(1 * time.Second) 836 closer, client, err := ArgoCDClientset.NewApplicationClient() 837 assert.NoError(t, err) 838 defer io.Close(closer) 839 840 refresh := string(RefreshTypeNormal) 841 app, err := client.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName, Refresh: &refresh}) 842 assert.NoError(t, err) 843 844 destinationErrorExist := false 845 sourceErrorExist := false 846 for i := range app.Status.Conditions { 847 if strings.Contains(app.Status.Conditions[i].Message, destinationError) { 848 destinationErrorExist = true 849 } 850 if strings.Contains(app.Status.Conditions[i].Message, sourceError) { 851 sourceErrorExist = true 852 } 853 } 854 assert.True(t, destinationErrorExist) 855 assert.True(t, sourceErrorExist) 856 } 857 858 // make sure that if we deleted a resource from the app, it is not pruned if annotated with Prune=false 859 func TestSyncOptionPruneFalse(t *testing.T) { 860 Given(t). 861 Path("two-nice-pods"). 862 When(). 863 PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`). 864 Create(). 865 Sync(). 866 Then(). 867 Expect(OperationPhaseIs(OperationSucceeded)). 868 Expect(SyncStatusIs(SyncStatusCodeSynced)). 869 When(). 870 DeleteFile("pod-1.yaml"). 871 Refresh(RefreshTypeHard). 872 IgnoreErrors(). 873 Sync(). 874 Then(). 875 Expect(OperationPhaseIs(OperationSucceeded)). 876 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 877 Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync)) 878 } 879 880 // 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 881 func TestSyncOptionValidateFalse(t *testing.T) { 882 883 // k3s does not validate at all, so this test does not work 884 if os.Getenv("ARGOCD_E2E_K3S") == "true" { 885 t.SkipNow() 886 } 887 888 Given(t). 889 Path("crd-validation"). 890 When(). 891 Create(). 892 Then(). 893 Expect(Success("")). 894 When(). 895 IgnoreErrors(). 896 Sync(). 897 Then(). 898 // client error 899 Expect(Error("error validating data", "")). 900 When(). 901 PatchFile("deployment.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Validate=false"}}]`). 902 Sync(). 903 Then(). 904 // server error 905 Expect(Error("Error from server", "")) 906 } 907 908 // make sure that, if we have a resource that needs pruning, but we're ignoring it, the app is in-sync 909 func TestCompareOptionIgnoreExtraneous(t *testing.T) { 910 Given(t). 911 Prune(false). 912 Path("two-nice-pods"). 913 When(). 914 PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/compare-options": "IgnoreExtraneous"}}]`). 915 Create(). 916 Sync(). 917 Then(). 918 Expect(OperationPhaseIs(OperationSucceeded)). 919 Expect(SyncStatusIs(SyncStatusCodeSynced)). 920 When(). 921 DeleteFile("pod-1.yaml"). 922 Refresh(RefreshTypeHard). 923 Then(). 924 Expect(SyncStatusIs(SyncStatusCodeSynced)). 925 And(func(app *Application) { 926 assert.Len(t, app.Status.Resources, 2) 927 statusByName := map[string]SyncStatusCode{} 928 for _, r := range app.Status.Resources { 929 statusByName[r.Name] = r.Status 930 } 931 assert.Equal(t, SyncStatusCodeOutOfSync, statusByName["pod-1"]) 932 assert.Equal(t, SyncStatusCodeSynced, statusByName["pod-2"]) 933 }). 934 When(). 935 Sync(). 936 Then(). 937 Expect(OperationPhaseIs(OperationSucceeded)). 938 Expect(SyncStatusIs(SyncStatusCodeSynced)) 939 } 940 941 func TestSelfManagedApps(t *testing.T) { 942 943 Given(t). 944 Path("self-managed-app"). 945 When(). 946 PatchFile("resources.yaml", fmt.Sprintf(`[{"op": "replace", "path": "/spec/source/repoURL", "value": "%s"}]`, RepoURL(RepoURLTypeFile))). 947 Create(). 948 Sync(). 949 Then(). 950 Expect(OperationPhaseIs(OperationSucceeded)). 951 Expect(SyncStatusIs(SyncStatusCodeSynced)). 952 And(func(a *Application) { 953 ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) 954 defer cancel() 955 956 reconciledCount := 0 957 var lastReconciledAt *metav1.Time 958 for event := range ArgoCDClientset.WatchApplicationWithRetry(ctx, a.Name, a.ResourceVersion) { 959 reconciledAt := event.Application.Status.ReconciledAt 960 if reconciledAt == nil { 961 reconciledAt = &metav1.Time{} 962 } 963 if lastReconciledAt != nil && !lastReconciledAt.Equal(reconciledAt) { 964 reconciledCount = reconciledCount + 1 965 } 966 lastReconciledAt = reconciledAt 967 } 968 969 assert.True(t, reconciledCount < 3, "Application was reconciled too many times") 970 }) 971 } 972 973 func TestExcludedResource(t *testing.T) { 974 Given(t). 975 ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {Actions: actionsConfig}}). 976 Path(guestbookPath). 977 ResourceFilter(settings.ResourcesFilter{ 978 ResourceExclusions: []settings.FilteredResource{{Kinds: []string{kube.DeploymentKind}}}, 979 }). 980 When(). 981 Create(). 982 Sync(). 983 Refresh(RefreshTypeNormal). 984 Then(). 985 Expect(Condition(ApplicationConditionExcludedResourceWarning, "Resource apps/Deployment guestbook-ui is excluded in the settings")) 986 } 987 988 func TestRevisionHistoryLimit(t *testing.T) { 989 Given(t). 990 Path("config-map"). 991 When(). 992 Create(). 993 Sync(). 994 Then(). 995 Expect(OperationPhaseIs(OperationSucceeded)). 996 Expect(SyncStatusIs(SyncStatusCodeSynced)). 997 And(func(app *Application) { 998 assert.Len(t, app.Status.History, 1) 999 }). 1000 When(). 1001 AppSet("--revision-history-limit", "1"). 1002 Sync(). 1003 Then(). 1004 Expect(OperationPhaseIs(OperationSucceeded)). 1005 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1006 And(func(app *Application) { 1007 assert.Len(t, app.Status.History, 1) 1008 }) 1009 } 1010 1011 func TestOrphanedResource(t *testing.T) { 1012 Given(t). 1013 ProjectSpec(AppProjectSpec{ 1014 SourceRepos: []string{"*"}, 1015 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1016 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.BoolPtr(true)}, 1017 }). 1018 Path(guestbookPath). 1019 When(). 1020 Create(). 1021 Sync(). 1022 Then(). 1023 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1024 Expect(NoConditions()). 1025 When(). 1026 And(func() { 1027 FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ 1028 ObjectMeta: metav1.ObjectMeta{ 1029 Name: "orphaned-configmap", 1030 }, 1031 }, metav1.CreateOptions{})) 1032 }). 1033 Refresh(RefreshTypeNormal). 1034 Then(). 1035 Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")). 1036 And(func(app *Application) { 1037 output, err := RunCli("app", "resources", app.Name) 1038 assert.NoError(t, err) 1039 assert.Contains(t, output, "orphaned-configmap") 1040 }). 1041 Given(). 1042 ProjectSpec(AppProjectSpec{ 1043 SourceRepos: []string{"*"}, 1044 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1045 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.BoolPtr(true), Ignore: []OrphanedResourceKey{{Group: "Test", Kind: "ConfigMap"}}}, 1046 }). 1047 When(). 1048 Refresh(RefreshTypeNormal). 1049 Then(). 1050 Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")). 1051 And(func(app *Application) { 1052 output, err := RunCli("app", "resources", app.Name) 1053 assert.NoError(t, err) 1054 assert.Contains(t, output, "orphaned-configmap") 1055 }). 1056 Given(). 1057 ProjectSpec(AppProjectSpec{ 1058 SourceRepos: []string{"*"}, 1059 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1060 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.BoolPtr(true), Ignore: []OrphanedResourceKey{{Kind: "ConfigMap"}}}, 1061 }). 1062 When(). 1063 Refresh(RefreshTypeNormal). 1064 Then(). 1065 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1066 Expect(NoConditions()). 1067 And(func(app *Application) { 1068 output, err := RunCli("app", "resources", app.Name) 1069 assert.NoError(t, err) 1070 assert.NotContains(t, output, "orphaned-configmap") 1071 }). 1072 Given(). 1073 ProjectSpec(AppProjectSpec{ 1074 SourceRepos: []string{"*"}, 1075 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1076 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.BoolPtr(true), Ignore: []OrphanedResourceKey{{Kind: "ConfigMap", Name: "orphaned-configmap"}}}, 1077 }). 1078 When(). 1079 Refresh(RefreshTypeNormal). 1080 Then(). 1081 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1082 Expect(NoConditions()). 1083 And(func(app *Application) { 1084 output, err := RunCli("app", "resources", app.Name) 1085 assert.NoError(t, err) 1086 assert.NotContains(t, output, "orphaned-configmap") 1087 }). 1088 Given(). 1089 ProjectSpec(AppProjectSpec{ 1090 SourceRepos: []string{"*"}, 1091 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1092 OrphanedResources: nil, 1093 }). 1094 When(). 1095 Refresh(RefreshTypeNormal). 1096 Then(). 1097 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1098 Expect(NoConditions()) 1099 } 1100 1101 func TestNotPermittedResources(t *testing.T) { 1102 ctx := Given(t) 1103 1104 ingress := &networkingv1beta.Ingress{ 1105 ObjectMeta: metav1.ObjectMeta{ 1106 Name: "sample-ingress", 1107 Labels: map[string]string{ 1108 common.LabelKeyAppInstance: ctx.GetName(), 1109 }, 1110 }, 1111 Spec: networkingv1beta.IngressSpec{ 1112 Rules: []networkingv1beta.IngressRule{{ 1113 IngressRuleValue: networkingv1beta.IngressRuleValue{ 1114 HTTP: &networkingv1beta.HTTPIngressRuleValue{ 1115 Paths: []networkingv1beta.HTTPIngressPath{{ 1116 Path: "/", 1117 Backend: networkingv1beta.IngressBackend{ 1118 ServiceName: "guestbook-ui", 1119 ServicePort: intstr.IntOrString{Type: intstr.Int, IntVal: 80}, 1120 }, 1121 }}, 1122 }, 1123 }, 1124 }}, 1125 }, 1126 } 1127 defer func() { 1128 log.Infof("Ingress 'sample-ingress' deleted from %s", ArgoCDNamespace) 1129 CheckError(KubeClientset.NetworkingV1beta1().Ingresses(ArgoCDNamespace).Delete(context.Background(), "sample-ingress", metav1.DeleteOptions{})) 1130 }() 1131 1132 svc := &v1.Service{ 1133 ObjectMeta: metav1.ObjectMeta{ 1134 Name: "guestbook-ui", 1135 Labels: map[string]string{ 1136 common.LabelKeyAppInstance: ctx.GetName(), 1137 }, 1138 }, 1139 Spec: v1.ServiceSpec{ 1140 Ports: []v1.ServicePort{{ 1141 Port: 80, 1142 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 80}, 1143 }}, 1144 Selector: map[string]string{ 1145 "app": "guestbook-ui", 1146 }, 1147 }, 1148 } 1149 1150 ctx.ProjectSpec(AppProjectSpec{ 1151 SourceRepos: []string{"*"}, 1152 Destinations: []ApplicationDestination{{Namespace: DeploymentNamespace(), Server: "*"}}, 1153 NamespaceResourceBlacklist: []metav1.GroupKind{ 1154 {Group: "", Kind: "Service"}, 1155 }}). 1156 And(func() { 1157 FailOnErr(KubeClientset.NetworkingV1beta1().Ingresses(ArgoCDNamespace).Create(context.Background(), ingress, metav1.CreateOptions{})) 1158 FailOnErr(KubeClientset.CoreV1().Services(DeploymentNamespace()).Create(context.Background(), svc, metav1.CreateOptions{})) 1159 }). 1160 Path(guestbookPath). 1161 When(). 1162 Create(). 1163 Then(). 1164 Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). 1165 And(func(app *Application) { 1166 statusByKind := make(map[string]ResourceStatus) 1167 for _, res := range app.Status.Resources { 1168 statusByKind[res.Kind] = res 1169 } 1170 _, hasIngress := statusByKind[kube.IngressKind] 1171 assert.False(t, hasIngress, "Ingress is prohibited not managed object and should be even visible to user") 1172 serviceStatus := statusByKind[kube.ServiceKind] 1173 assert.Equal(t, serviceStatus.Status, SyncStatusCodeUnknown, "Service is prohibited managed resource so should be set to Unknown") 1174 deploymentStatus := statusByKind[kube.DeploymentKind] 1175 assert.Equal(t, deploymentStatus.Status, SyncStatusCodeOutOfSync) 1176 }). 1177 When(). 1178 Delete(true). 1179 Then(). 1180 Expect(DoesNotExist()) 1181 1182 // Make sure prohibited resources are not deleted during application deletion 1183 FailOnErr(KubeClientset.NetworkingV1beta1().Ingresses(ArgoCDNamespace).Get(context.Background(), "sample-ingress", metav1.GetOptions{})) 1184 FailOnErr(KubeClientset.CoreV1().Services(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})) 1185 } 1186 1187 func TestSyncWithInfos(t *testing.T) { 1188 expectedInfo := make([]*Info, 2) 1189 expectedInfo[0] = &Info{Name: "name1", Value: "val1"} 1190 expectedInfo[1] = &Info{Name: "name2", Value: "val2"} 1191 1192 Given(t). 1193 Path(guestbookPath). 1194 When(). 1195 Create(). 1196 Then(). 1197 And(func(app *Application) { 1198 _, err := RunCli("app", "sync", app.Name, 1199 "--info", fmt.Sprintf("%s=%s", expectedInfo[0].Name, expectedInfo[0].Value), 1200 "--info", fmt.Sprintf("%s=%s", expectedInfo[1].Name, expectedInfo[1].Value)) 1201 assert.NoError(t, err) 1202 }). 1203 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1204 And(func(app *Application) { 1205 assert.ElementsMatch(t, app.Status.OperationState.Operation.Info, expectedInfo) 1206 }) 1207 } 1208 1209 //Given: argocd app create does not provide --dest-namespace 1210 // Manifest contains resource console which does not require namespace 1211 //Expect: no app.Status.Conditions 1212 func TestCreateAppWithNoNameSpaceForGlobalResource(t *testing.T) { 1213 Given(t). 1214 Path(globalWithNoNameSpace). 1215 When(). 1216 CreateWithNoNameSpace(). 1217 Then(). 1218 And(func(app *Application) { 1219 time.Sleep(500 * time.Millisecond) 1220 app, err := AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Get(context.Background(), app.Name, metav1.GetOptions{}) 1221 assert.NoError(t, err) 1222 assert.Len(t, app.Status.Conditions, 0) 1223 }) 1224 } 1225 1226 //Given: argocd app create does not provide --dest-namespace 1227 // Manifest contains resource deployment, and service which requires namespace 1228 // Deployment and service do not have namespace in manifest 1229 //Expect: app.Status.Conditions for deployment ans service which does not have namespace in manifest 1230 func TestCreateAppWithNoNameSpaceWhenRequired(t *testing.T) { 1231 Given(t). 1232 Path(guestbookPath). 1233 When(). 1234 CreateWithNoNameSpace(). 1235 Then(). 1236 And(func(app *Application) { 1237 var updatedApp *Application 1238 for i := 0; i < 3; i++ { 1239 obj, err := AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Get(context.Background(), app.Name, metav1.GetOptions{}) 1240 assert.NoError(t, err) 1241 updatedApp = obj 1242 if len(updatedApp.Status.Conditions) > 0 { 1243 break 1244 } 1245 time.Sleep(500 * time.Millisecond) 1246 } 1247 assert.Len(t, updatedApp.Status.Conditions, 2) 1248 assert.Equal(t, updatedApp.Status.Conditions[0].Type, ApplicationConditionInvalidSpecError) 1249 assert.Equal(t, updatedApp.Status.Conditions[1].Type, ApplicationConditionInvalidSpecError) 1250 }) 1251 } 1252 1253 //Given: argocd app create does not provide --dest-namespace 1254 // Manifest contains resource deployment, and service which requires namespace 1255 // Some deployment and service has namespace in manifest 1256 // Some deployment and service does not have namespace in manifest 1257 //Expect: app.Status.Conditions for deployment and service which does not have namespace in manifest 1258 func TestCreateAppWithNoNameSpaceWhenRequired2(t *testing.T) { 1259 Given(t). 1260 Path(guestbookWithNamespace). 1261 When(). 1262 CreateWithNoNameSpace(). 1263 Then(). 1264 And(func(app *Application) { 1265 var updatedApp *Application 1266 for i := 0; i < 3; i++ { 1267 obj, err := AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Get(context.Background(), app.Name, metav1.GetOptions{}) 1268 assert.NoError(t, err) 1269 updatedApp = obj 1270 if len(updatedApp.Status.Conditions) > 0 { 1271 break 1272 } 1273 time.Sleep(500 * time.Millisecond) 1274 } 1275 assert.Len(t, updatedApp.Status.Conditions, 2) 1276 assert.Equal(t, updatedApp.Status.Conditions[0].Type, ApplicationConditionInvalidSpecError) 1277 assert.Equal(t, updatedApp.Status.Conditions[1].Type, ApplicationConditionInvalidSpecError) 1278 }) 1279 } 1280 1281 func TestListResource(t *testing.T) { 1282 Given(t). 1283 ProjectSpec(AppProjectSpec{ 1284 SourceRepos: []string{"*"}, 1285 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1286 OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.BoolPtr(true)}, 1287 }). 1288 Path(guestbookPath). 1289 When(). 1290 Create(). 1291 Sync(). 1292 Then(). 1293 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1294 Expect(NoConditions()). 1295 When(). 1296 And(func() { 1297 FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{ 1298 ObjectMeta: metav1.ObjectMeta{ 1299 Name: "orphaned-configmap", 1300 }, 1301 }, metav1.CreateOptions{})) 1302 }). 1303 Refresh(RefreshTypeNormal). 1304 Then(). 1305 Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")). 1306 And(func(app *Application) { 1307 output, err := RunCli("app", "resources", app.Name) 1308 assert.NoError(t, err) 1309 assert.Contains(t, output, "orphaned-configmap") 1310 assert.Contains(t, output, "guestbook-ui") 1311 }). 1312 And(func(app *Application) { 1313 output, err := RunCli("app", "resources", app.Name, "--orphaned=true") 1314 assert.NoError(t, err) 1315 assert.Contains(t, output, "orphaned-configmap") 1316 assert.NotContains(t, output, "guestbook-ui") 1317 }). 1318 And(func(app *Application) { 1319 output, err := RunCli("app", "resources", app.Name, "--orphaned=false") 1320 assert.NoError(t, err) 1321 assert.NotContains(t, output, "orphaned-configmap") 1322 assert.Contains(t, output, "guestbook-ui") 1323 }). 1324 Given(). 1325 ProjectSpec(AppProjectSpec{ 1326 SourceRepos: []string{"*"}, 1327 Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}}, 1328 OrphanedResources: nil, 1329 }). 1330 When(). 1331 Refresh(RefreshTypeNormal). 1332 Then(). 1333 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1334 Expect(NoConditions()) 1335 } 1336 1337 // Given application is set with --sync-option CreateNamespace=true 1338 // application --dest-namespace does not exist 1339 // Verity application --dest-namespace is created 1340 // application sync successful 1341 // when application is deleted, --dest-namespace is not deleted 1342 func TestNamespaceAutoCreation(t *testing.T) { 1343 updatedNamespace := getNewNamespace(t) 1344 defer func() { 1345 _, err := Run("", "kubectl", "delete", "namespace", updatedNamespace) 1346 assert.NoError(t, err) 1347 }() 1348 Given(t). 1349 Timeout(30). 1350 Path("guestbook"). 1351 When(). 1352 Create("--sync-option", "CreateNamespace=true"). 1353 Then(). 1354 And(func(app *Application) { 1355 //Make sure the namespace we are about to update to does not exist 1356 _, err := Run("", "kubectl", "get", "namespace", updatedNamespace) 1357 assert.Error(t, err) 1358 assert.Contains(t, err.Error(), "not found") 1359 }). 1360 When(). 1361 AppSet("--dest-namespace", updatedNamespace). 1362 Sync(). 1363 Then(). 1364 Expect(Success("")). 1365 Expect(OperationPhaseIs(OperationSucceeded)).Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 1366 Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)). 1367 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)). 1368 Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)). 1369 When(). 1370 Delete(true). 1371 Then(). 1372 Expect(Success("")). 1373 And(func(app *Application) { 1374 //Verify delete app does not delete the namespace auto created 1375 output, err := Run("", "kubectl", "get", "namespace", updatedNamespace) 1376 assert.NoError(t, err) 1377 assert.Contains(t, output, updatedNamespace) 1378 }) 1379 } 1380 1381 func TestFailedSyncWithRetry(t *testing.T) { 1382 Given(t). 1383 Path("hook"). 1384 When(). 1385 PatchFile("hook.yaml", `[{"op": "replace", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/hook": "PreSync"}}]`). 1386 // make hook fail 1387 PatchFile("hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/command", "value": ["false"]}]`). 1388 Create(). 1389 IgnoreErrors(). 1390 Sync("--retry-limit=1", "--retry-backoff-duration=1s"). 1391 Then(). 1392 Expect(OperationPhaseIs(OperationFailed)). 1393 Expect(OperationMessageContains("retried 1 times")) 1394 } 1395 1396 func TestCreateDisableValidation(t *testing.T) { 1397 Given(t). 1398 Path("baddir"). 1399 When(). 1400 Create("--validate=false"). 1401 Then(). 1402 And(func(app *Application) { 1403 _, err := RunCli("app", "create", app.Name, "--upsert", "--validate=false", "--repo", RepoURL(RepoURLTypeFile), 1404 "--path", "baddir2", "--project", app.Spec.Project, "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace()) 1405 assert.NoError(t, err) 1406 }). 1407 When(). 1408 AppSet("--path", "baddir3", "--validate=false") 1409 1410 } 1411 1412 func TestCreateFromPartialFile(t *testing.T) { 1413 partialApp := 1414 `spec: 1415 syncPolicy: 1416 automated: {prune: true }` 1417 1418 path := "helm-values" 1419 Given(t). 1420 When(). 1421 // app should be auto-synced once created 1422 CreateFromPartialFile(partialApp, "--path", path, "--helm-set", "foo=foo"). 1423 Then(). 1424 Expect(Success("")). 1425 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1426 Expect(NoConditions()). 1427 And(func(app *Application) { 1428 assert.Equal(t, path, app.Spec.Source.Path) 1429 assert.Equal(t, []HelmParameter{{Name: "foo", Value: "foo"}}, app.Spec.Source.Helm.Parameters) 1430 }) 1431 } 1432 func TestAppCreationWithExclude(t *testing.T) { 1433 Given(t). 1434 Path("app-exclusions"). 1435 When(). 1436 Create("--directory-exclude", "**/.*", "--directory-recurse"). 1437 Sync(). 1438 Then(). 1439 Expect(OperationPhaseIs(OperationSucceeded)). 1440 Expect(SyncStatusIs(SyncStatusCodeSynced)). 1441 Expect(HealthIs(health.HealthStatusHealthy)) 1442 } 1443 1444 // Ensure actions work when using a resource action that modifies status and/or spec 1445 func TestCRDStatusSubresourceAction(t *testing.T) { 1446 actions := ` 1447 discovery.lua: | 1448 actions = {} 1449 actions["update-spec"] = {["disabled"] = false} 1450 actions["update-status"] = {["disabled"] = false} 1451 actions["update-both"] = {["disabled"] = false} 1452 return actions 1453 definitions: 1454 - name: update-both 1455 action.lua: | 1456 obj.spec = {} 1457 obj.spec.foo = "update-both" 1458 obj.status = {} 1459 obj.status.bar = "update-both" 1460 return obj 1461 - name: update-spec 1462 action.lua: | 1463 obj.spec = {} 1464 obj.spec.foo = "update-spec" 1465 return obj 1466 - name: update-status 1467 action.lua: | 1468 obj.status = {} 1469 obj.status.bar = "update-status" 1470 return obj 1471 ` 1472 Given(t). 1473 Path("crd-subresource"). 1474 And(func() { 1475 SetResourceOverrides(map[string]ResourceOverride{ 1476 "argoproj.io/StatusSubResource": { 1477 Actions: actions, 1478 }, 1479 "argoproj.io/NonStatusSubResource": { 1480 Actions: actions, 1481 }, 1482 }) 1483 }). 1484 When().Create().Sync().Then(). 1485 Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeSynced)). 1486 When(). 1487 Then(). 1488 // tests resource actions on a CRD using status subresource 1489 And(func(app *Application) { 1490 _, err := RunCli("app", "actions", "run", app.Name, "--kind", "StatusSubResource", "update-both") 1491 assert.NoError(t, err) 1492 text := FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 1493 assert.Equal(t, "update-both", text) 1494 text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.status.bar}")).(string) 1495 assert.Equal(t, "update-both", text) 1496 1497 _, err = RunCli("app", "actions", "run", app.Name, "--kind", "StatusSubResource", "update-spec") 1498 assert.NoError(t, err) 1499 text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 1500 assert.Equal(t, "update-spec", text) 1501 1502 _, err = RunCli("app", "actions", "run", app.Name, "--kind", "StatusSubResource", "update-status") 1503 assert.NoError(t, err) 1504 text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.status.bar}")).(string) 1505 assert.Equal(t, "update-status", text) 1506 }). 1507 // tests resource actions on a CRD *not* using status subresource 1508 And(func(app *Application) { 1509 _, err := RunCli("app", "actions", "run", app.Name, "--kind", "NonStatusSubResource", "update-both") 1510 assert.NoError(t, err) 1511 text := FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 1512 assert.Equal(t, "update-both", text) 1513 text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.status.bar}")).(string) 1514 assert.Equal(t, "update-both", text) 1515 1516 _, err = RunCli("app", "actions", "run", app.Name, "--kind", "NonStatusSubResource", "update-spec") 1517 assert.NoError(t, err) 1518 text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.spec.foo}")).(string) 1519 assert.Equal(t, "update-spec", text) 1520 1521 _, err = RunCli("app", "actions", "run", app.Name, "--kind", "NonStatusSubResource", "update-status") 1522 assert.NoError(t, err) 1523 text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.status.bar}")).(string) 1524 assert.Equal(t, "update-status", text) 1525 }) 1526 } 1527 1528 func TestAppWaitOperationInProgress(t *testing.T) { 1529 Given(t). 1530 And(func() { 1531 SetResourceOverrides(map[string]ResourceOverride{ 1532 "batch/Job": { 1533 HealthLua: `return { status = 'Running' }`, 1534 }, 1535 "apps/Deployment": { 1536 HealthLua: `return { status = 'Suspended' }`, 1537 }, 1538 }) 1539 }). 1540 Async(true). 1541 Path("hook-and-deployment"). 1542 When(). 1543 Create(). 1544 Sync(). 1545 Then(). 1546 // stuck in running state 1547 Expect(OperationPhaseIs(OperationRunning)). 1548 When(). 1549 And(func() { 1550 _, err := RunCli("app", "wait", Name(), "--suspended") 1551 errors.CheckError(err) 1552 }) 1553 }