github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/controllers/applicationset_controller_test.go (about) 1 package controllers 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strconv" 8 "testing" 9 "time" 10 11 log "github.com/sirupsen/logrus" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 corev1 "k8s.io/api/core/v1" 16 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/runtime" 19 "k8s.io/apimachinery/pkg/types" 20 "k8s.io/apimachinery/pkg/util/intstr" 21 kubefake "k8s.io/client-go/kubernetes/fake" 22 "k8s.io/client-go/tools/record" 23 ctrl "sigs.k8s.io/controller-runtime" 24 crtclient "sigs.k8s.io/controller-runtime/pkg/client" 25 "sigs.k8s.io/controller-runtime/pkg/client/fake" 26 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 27 "sigs.k8s.io/controller-runtime/pkg/event" 28 29 "github.com/argoproj/gitops-engine/pkg/health" 30 "github.com/argoproj/gitops-engine/pkg/sync/common" 31 32 "github.com/argoproj/argo-cd/v3/applicationset/generators" 33 "github.com/argoproj/argo-cd/v3/applicationset/generators/mocks" 34 appsetmetrics "github.com/argoproj/argo-cd/v3/applicationset/metrics" 35 "github.com/argoproj/argo-cd/v3/applicationset/utils" 36 argocommon "github.com/argoproj/argo-cd/v3/common" 37 "github.com/argoproj/argo-cd/v3/pkg/apis/application" 38 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 39 applog "github.com/argoproj/argo-cd/v3/util/app/log" 40 "github.com/argoproj/argo-cd/v3/util/db" 41 "github.com/argoproj/argo-cd/v3/util/settings" 42 ) 43 44 // getDefaultTestClientSet creates a Clientset with the default argo objects 45 // and objects specified in parameters 46 func getDefaultTestClientSet(obj ...runtime.Object) *kubefake.Clientset { 47 argoCDSecret := &corev1.Secret{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: argocommon.ArgoCDSecretName, 50 Namespace: "argocd", 51 Labels: map[string]string{ 52 "app.kubernetes.io/part-of": "argocd", 53 }, 54 }, 55 Data: map[string][]byte{ 56 "admin.password": nil, 57 "server.secretkey": nil, 58 }, 59 } 60 61 emptyArgoCDConfigMap := &corev1.ConfigMap{ 62 ObjectMeta: metav1.ObjectMeta{ 63 Name: argocommon.ArgoCDConfigMapName, 64 Namespace: "argocd", 65 Labels: map[string]string{ 66 "app.kubernetes.io/part-of": "argocd", 67 }, 68 }, 69 Data: map[string]string{}, 70 } 71 72 objects := append(obj, emptyArgoCDConfigMap, argoCDSecret) 73 kubeclientset := kubefake.NewClientset(objects...) 74 return kubeclientset 75 } 76 77 func TestCreateOrUpdateInCluster(t *testing.T) { 78 scheme := runtime.NewScheme() 79 err := v1alpha1.AddToScheme(scheme) 80 require.NoError(t, err) 81 82 for _, c := range []struct { 83 // name is human-readable test name 84 name string 85 // appSet is the ApplicationSet we are generating resources for 86 appSet v1alpha1.ApplicationSet 87 // existingApps are the apps that already exist on the cluster 88 existingApps []v1alpha1.Application 89 // desiredApps are the generated apps to create/update 90 desiredApps []v1alpha1.Application 91 // expected is what we expect the cluster Applications to look like, after createOrUpdateInCluster 92 expected []v1alpha1.Application 93 }{ 94 { 95 name: "Create an app that doesn't exist", 96 appSet: v1alpha1.ApplicationSet{ 97 ObjectMeta: metav1.ObjectMeta{ 98 Name: "name", 99 Namespace: "namespace", 100 }, 101 }, 102 existingApps: nil, 103 desiredApps: []v1alpha1.Application{ 104 { 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: "app1", 107 Namespace: "namespace", 108 }, 109 Spec: v1alpha1.ApplicationSpec{Project: "default"}, 110 }, 111 }, 112 expected: []v1alpha1.Application{ 113 { 114 TypeMeta: metav1.TypeMeta{ 115 Kind: application.ApplicationKind, 116 APIVersion: "argoproj.io/v1alpha1", 117 }, 118 ObjectMeta: metav1.ObjectMeta{ 119 Name: "app1", 120 Namespace: "namespace", 121 ResourceVersion: "1", 122 }, 123 Spec: v1alpha1.ApplicationSpec{Project: "default"}, 124 }, 125 }, 126 }, 127 { 128 name: "Update an existing app with a different project name", 129 appSet: v1alpha1.ApplicationSet{ 130 ObjectMeta: metav1.ObjectMeta{ 131 Name: "name", 132 Namespace: "namespace", 133 }, 134 Spec: v1alpha1.ApplicationSetSpec{ 135 Template: v1alpha1.ApplicationSetTemplate{ 136 Spec: v1alpha1.ApplicationSpec{ 137 Project: "project", 138 }, 139 }, 140 }, 141 }, 142 existingApps: []v1alpha1.Application{ 143 { 144 TypeMeta: metav1.TypeMeta{ 145 Kind: application.ApplicationKind, 146 APIVersion: "argoproj.io/v1alpha1", 147 }, 148 ObjectMeta: metav1.ObjectMeta{ 149 Name: "app1", 150 Namespace: "namespace", 151 ResourceVersion: "2", 152 }, 153 Spec: v1alpha1.ApplicationSpec{ 154 Project: "test", 155 }, 156 }, 157 }, 158 desiredApps: []v1alpha1.Application{ 159 { 160 ObjectMeta: metav1.ObjectMeta{ 161 Name: "app1", 162 Namespace: "namespace", 163 }, 164 Spec: v1alpha1.ApplicationSpec{ 165 Project: "project", 166 }, 167 }, 168 }, 169 expected: []v1alpha1.Application{ 170 { 171 TypeMeta: metav1.TypeMeta{ 172 Kind: application.ApplicationKind, 173 APIVersion: "argoproj.io/v1alpha1", 174 }, 175 ObjectMeta: metav1.ObjectMeta{ 176 Name: "app1", 177 Namespace: "namespace", 178 ResourceVersion: "3", 179 }, 180 Spec: v1alpha1.ApplicationSpec{ 181 Project: "project", 182 }, 183 }, 184 }, 185 }, 186 { 187 name: "Create a new app and check it doesn't replace the existing app", 188 appSet: v1alpha1.ApplicationSet{ 189 ObjectMeta: metav1.ObjectMeta{ 190 Name: "name", 191 Namespace: "namespace", 192 }, 193 Spec: v1alpha1.ApplicationSetSpec{ 194 Template: v1alpha1.ApplicationSetTemplate{ 195 Spec: v1alpha1.ApplicationSpec{ 196 Project: "project", 197 }, 198 }, 199 }, 200 }, 201 existingApps: []v1alpha1.Application{ 202 { 203 TypeMeta: metav1.TypeMeta{ 204 Kind: application.ApplicationKind, 205 APIVersion: "argoproj.io/v1alpha1", 206 }, 207 ObjectMeta: metav1.ObjectMeta{ 208 Name: "app1", 209 Namespace: "namespace", 210 ResourceVersion: "2", 211 }, 212 Spec: v1alpha1.ApplicationSpec{ 213 Project: "test", 214 }, 215 }, 216 }, 217 desiredApps: []v1alpha1.Application{ 218 { 219 ObjectMeta: metav1.ObjectMeta{ 220 Name: "app2", 221 Namespace: "namespace", 222 }, 223 Spec: v1alpha1.ApplicationSpec{ 224 Project: "project", 225 }, 226 }, 227 }, 228 expected: []v1alpha1.Application{ 229 { 230 TypeMeta: metav1.TypeMeta{ 231 Kind: application.ApplicationKind, 232 APIVersion: "argoproj.io/v1alpha1", 233 }, 234 ObjectMeta: metav1.ObjectMeta{ 235 Name: "app2", 236 Namespace: "namespace", 237 ResourceVersion: "1", 238 }, 239 Spec: v1alpha1.ApplicationSpec{ 240 Project: "project", 241 }, 242 }, 243 }, 244 }, 245 { 246 name: "Ensure that labels and annotations are added (via update) into an exiting application", 247 appSet: v1alpha1.ApplicationSet{ 248 ObjectMeta: metav1.ObjectMeta{ 249 Name: "name", 250 Namespace: "namespace", 251 }, 252 Spec: v1alpha1.ApplicationSetSpec{ 253 Template: v1alpha1.ApplicationSetTemplate{ 254 Spec: v1alpha1.ApplicationSpec{ 255 Project: "project", 256 }, 257 }, 258 }, 259 }, 260 existingApps: []v1alpha1.Application{ 261 { 262 TypeMeta: metav1.TypeMeta{ 263 Kind: application.ApplicationKind, 264 APIVersion: "argoproj.io/v1alpha1", 265 }, 266 ObjectMeta: metav1.ObjectMeta{ 267 Name: "app1", 268 Namespace: "namespace", 269 ResourceVersion: "2", 270 }, 271 Spec: v1alpha1.ApplicationSpec{ 272 Project: "project", 273 }, 274 }, 275 }, 276 desiredApps: []v1alpha1.Application{ 277 { 278 ObjectMeta: metav1.ObjectMeta{ 279 Name: "app1", 280 Namespace: "namespace", 281 Labels: map[string]string{"label-key": "label-value"}, 282 Annotations: map[string]string{"annot-key": "annot-value"}, 283 }, 284 Spec: v1alpha1.ApplicationSpec{ 285 Project: "project", 286 }, 287 }, 288 }, 289 expected: []v1alpha1.Application{ 290 { 291 TypeMeta: metav1.TypeMeta{ 292 Kind: application.ApplicationKind, 293 APIVersion: "argoproj.io/v1alpha1", 294 }, 295 ObjectMeta: metav1.ObjectMeta{ 296 Name: "app1", 297 Namespace: "namespace", 298 Labels: map[string]string{"label-key": "label-value"}, 299 Annotations: map[string]string{"annot-key": "annot-value"}, 300 ResourceVersion: "3", 301 }, 302 Spec: v1alpha1.ApplicationSpec{ 303 Project: "project", 304 }, 305 }, 306 }, 307 }, 308 { 309 name: "Ensure that labels and annotations are removed from an existing app", 310 appSet: v1alpha1.ApplicationSet{ 311 ObjectMeta: metav1.ObjectMeta{ 312 Name: "name", 313 Namespace: "namespace", 314 }, 315 Spec: v1alpha1.ApplicationSetSpec{ 316 Template: v1alpha1.ApplicationSetTemplate{ 317 Spec: v1alpha1.ApplicationSpec{ 318 Project: "project", 319 }, 320 }, 321 }, 322 }, 323 existingApps: []v1alpha1.Application{ 324 { 325 TypeMeta: metav1.TypeMeta{ 326 Kind: application.ApplicationKind, 327 APIVersion: "argoproj.io/v1alpha1", 328 }, 329 ObjectMeta: metav1.ObjectMeta{ 330 Name: "app1", 331 Namespace: "namespace", 332 ResourceVersion: "2", 333 Labels: map[string]string{"label-key": "label-value"}, 334 Annotations: map[string]string{"annot-key": "annot-value"}, 335 }, 336 Spec: v1alpha1.ApplicationSpec{ 337 Project: "project", 338 }, 339 }, 340 }, 341 desiredApps: []v1alpha1.Application{ 342 { 343 ObjectMeta: metav1.ObjectMeta{ 344 Name: "app1", 345 Namespace: "namespace", 346 }, 347 Spec: v1alpha1.ApplicationSpec{ 348 Project: "project", 349 }, 350 }, 351 }, 352 expected: []v1alpha1.Application{ 353 { 354 TypeMeta: metav1.TypeMeta{ 355 Kind: application.ApplicationKind, 356 APIVersion: "argoproj.io/v1alpha1", 357 }, 358 ObjectMeta: metav1.ObjectMeta{ 359 Name: "app1", 360 Namespace: "namespace", 361 ResourceVersion: "3", 362 }, 363 Spec: v1alpha1.ApplicationSpec{ 364 Project: "project", 365 }, 366 }, 367 }, 368 }, 369 { 370 name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations", 371 appSet: v1alpha1.ApplicationSet{ 372 ObjectMeta: metav1.ObjectMeta{ 373 Name: "name", 374 Namespace: "namespace", 375 }, 376 Spec: v1alpha1.ApplicationSetSpec{ 377 Template: v1alpha1.ApplicationSetTemplate{ 378 Spec: v1alpha1.ApplicationSpec{ 379 Project: "project", 380 }, 381 }, 382 }, 383 }, 384 existingApps: []v1alpha1.Application{ 385 { 386 TypeMeta: metav1.TypeMeta{ 387 Kind: application.ApplicationKind, 388 APIVersion: "argoproj.io/v1alpha1", 389 }, 390 ObjectMeta: metav1.ObjectMeta{ 391 Name: "app1", 392 Namespace: "namespace", 393 ResourceVersion: "2", 394 Labels: map[string]string{"label-key": "label-value"}, 395 Annotations: map[string]string{"annot-key": "annot-value"}, 396 }, 397 Spec: v1alpha1.ApplicationSpec{ 398 Project: "project", 399 }, 400 Status: v1alpha1.ApplicationStatus{ 401 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 402 }, 403 Operation: &v1alpha1.Operation{ 404 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 405 }, 406 }, 407 }, 408 desiredApps: []v1alpha1.Application{ 409 { 410 ObjectMeta: metav1.ObjectMeta{ 411 Name: "app1", 412 Namespace: "namespace", 413 }, 414 Spec: v1alpha1.ApplicationSpec{ 415 Project: "project", 416 }, 417 }, 418 }, 419 expected: []v1alpha1.Application{ 420 { 421 TypeMeta: metav1.TypeMeta{ 422 Kind: application.ApplicationKind, 423 APIVersion: "argoproj.io/v1alpha1", 424 }, 425 ObjectMeta: metav1.ObjectMeta{ 426 Name: "app1", 427 Namespace: "namespace", 428 ResourceVersion: "3", 429 }, 430 Spec: v1alpha1.ApplicationSpec{ 431 Project: "project", 432 }, 433 Status: v1alpha1.ApplicationStatus{ 434 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 435 }, 436 Operation: &v1alpha1.Operation{ 437 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 438 }, 439 }, 440 }, 441 }, 442 { 443 name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations and adding other fields", 444 appSet: v1alpha1.ApplicationSet{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Name: "name", 447 Namespace: "namespace", 448 }, 449 Spec: v1alpha1.ApplicationSetSpec{ 450 Template: v1alpha1.ApplicationSetTemplate{ 451 Spec: v1alpha1.ApplicationSpec{ 452 Project: "project", 453 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 454 Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"}, 455 }, 456 }, 457 }, 458 }, 459 existingApps: []v1alpha1.Application{ 460 { 461 TypeMeta: metav1.TypeMeta{ 462 Kind: application.ApplicationKind, 463 APIVersion: "argoproj.io/v1alpha1", 464 }, 465 ObjectMeta: metav1.ObjectMeta{ 466 Name: "app1", 467 Namespace: "namespace", 468 ResourceVersion: "2", 469 }, 470 Spec: v1alpha1.ApplicationSpec{ 471 Project: "project", 472 }, 473 Status: v1alpha1.ApplicationStatus{ 474 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 475 }, 476 Operation: &v1alpha1.Operation{ 477 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 478 }, 479 }, 480 }, 481 desiredApps: []v1alpha1.Application{ 482 { 483 ObjectMeta: metav1.ObjectMeta{ 484 Name: "app1", 485 Namespace: "namespace", 486 Labels: map[string]string{"label-key": "label-value"}, 487 Annotations: map[string]string{"annot-key": "annot-value"}, 488 }, 489 Spec: v1alpha1.ApplicationSpec{ 490 Project: "project", 491 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 492 Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"}, 493 }, 494 }, 495 }, 496 expected: []v1alpha1.Application{ 497 { 498 TypeMeta: metav1.TypeMeta{ 499 Kind: application.ApplicationKind, 500 APIVersion: "argoproj.io/v1alpha1", 501 }, 502 ObjectMeta: metav1.ObjectMeta{ 503 Name: "app1", 504 Namespace: "namespace", 505 Labels: map[string]string{"label-key": "label-value"}, 506 Annotations: map[string]string{"annot-key": "annot-value"}, 507 ResourceVersion: "3", 508 }, 509 Spec: v1alpha1.ApplicationSpec{ 510 Project: "project", 511 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 512 Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"}, 513 }, 514 Status: v1alpha1.ApplicationStatus{ 515 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 516 }, 517 Operation: &v1alpha1.Operation{ 518 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 519 }, 520 }, 521 }, 522 }, 523 { 524 name: "Ensure that argocd notifications state and refresh annotation is preserved from an existing app", 525 appSet: v1alpha1.ApplicationSet{ 526 ObjectMeta: metav1.ObjectMeta{ 527 Name: "name", 528 Namespace: "namespace", 529 }, 530 Spec: v1alpha1.ApplicationSetSpec{ 531 Template: v1alpha1.ApplicationSetTemplate{ 532 Spec: v1alpha1.ApplicationSpec{ 533 Project: "project", 534 }, 535 }, 536 }, 537 }, 538 existingApps: []v1alpha1.Application{ 539 { 540 TypeMeta: metav1.TypeMeta{ 541 Kind: application.ApplicationKind, 542 APIVersion: "argoproj.io/v1alpha1", 543 }, 544 ObjectMeta: metav1.ObjectMeta{ 545 Name: "app1", 546 Namespace: "namespace", 547 ResourceVersion: "2", 548 Labels: map[string]string{"label-key": "label-value"}, 549 Annotations: map[string]string{ 550 "annot-key": "annot-value", 551 NotifiedAnnotationKey: `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`, 552 v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal), 553 }, 554 }, 555 Spec: v1alpha1.ApplicationSpec{ 556 Project: "project", 557 }, 558 }, 559 }, 560 desiredApps: []v1alpha1.Application{ 561 { 562 ObjectMeta: metav1.ObjectMeta{ 563 Name: "app1", 564 Namespace: "namespace", 565 }, 566 Spec: v1alpha1.ApplicationSpec{ 567 Project: "project", 568 }, 569 }, 570 }, 571 expected: []v1alpha1.Application{ 572 { 573 TypeMeta: metav1.TypeMeta{ 574 Kind: application.ApplicationKind, 575 APIVersion: "argoproj.io/v1alpha1", 576 }, 577 ObjectMeta: metav1.ObjectMeta{ 578 Name: "app1", 579 Namespace: "namespace", 580 ResourceVersion: "3", 581 Annotations: map[string]string{ 582 NotifiedAnnotationKey: `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`, 583 v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal), 584 }, 585 }, 586 Spec: v1alpha1.ApplicationSpec{ 587 Project: "project", 588 }, 589 }, 590 }, 591 }, 592 { 593 name: "Ensure that configured preserved annotations are preserved from an existing app", 594 appSet: v1alpha1.ApplicationSet{ 595 ObjectMeta: metav1.ObjectMeta{ 596 Name: "name", 597 Namespace: "namespace", 598 }, 599 Spec: v1alpha1.ApplicationSetSpec{ 600 Template: v1alpha1.ApplicationSetTemplate{ 601 Spec: v1alpha1.ApplicationSpec{ 602 Project: "project", 603 }, 604 }, 605 PreservedFields: &v1alpha1.ApplicationPreservedFields{ 606 Annotations: []string{"preserved-annot-key"}, 607 }, 608 }, 609 }, 610 existingApps: []v1alpha1.Application{ 611 { 612 TypeMeta: metav1.TypeMeta{ 613 Kind: "Application", 614 APIVersion: "argoproj.io/v1alpha1", 615 }, 616 ObjectMeta: metav1.ObjectMeta{ 617 Name: "app1", 618 Namespace: "namespace", 619 ResourceVersion: "2", 620 Annotations: map[string]string{ 621 "annot-key": "annot-value", 622 "preserved-annot-key": "preserved-annot-value", 623 }, 624 }, 625 Spec: v1alpha1.ApplicationSpec{ 626 Project: "project", 627 }, 628 }, 629 }, 630 desiredApps: []v1alpha1.Application{ 631 { 632 ObjectMeta: metav1.ObjectMeta{ 633 Name: "app1", 634 Namespace: "namespace", 635 }, 636 Spec: v1alpha1.ApplicationSpec{ 637 Project: "project", 638 }, 639 }, 640 }, 641 expected: []v1alpha1.Application{ 642 { 643 TypeMeta: metav1.TypeMeta{ 644 Kind: "Application", 645 APIVersion: "argoproj.io/v1alpha1", 646 }, 647 ObjectMeta: metav1.ObjectMeta{ 648 Name: "app1", 649 Namespace: "namespace", 650 ResourceVersion: "3", 651 Annotations: map[string]string{ 652 "preserved-annot-key": "preserved-annot-value", 653 }, 654 }, 655 Spec: v1alpha1.ApplicationSpec{ 656 Project: "project", 657 }, 658 }, 659 }, 660 }, 661 { 662 name: "Ensure that the app spec is normalized before applying", 663 appSet: v1alpha1.ApplicationSet{ 664 ObjectMeta: metav1.ObjectMeta{ 665 Name: "name", 666 Namespace: "namespace", 667 }, 668 Spec: v1alpha1.ApplicationSetSpec{ 669 Template: v1alpha1.ApplicationSetTemplate{ 670 Spec: v1alpha1.ApplicationSpec{ 671 Project: "project", 672 Source: &v1alpha1.ApplicationSource{ 673 Directory: &v1alpha1.ApplicationSourceDirectory{ 674 Jsonnet: v1alpha1.ApplicationSourceJsonnet{}, 675 }, 676 }, 677 }, 678 }, 679 }, 680 }, 681 desiredApps: []v1alpha1.Application{ 682 { 683 ObjectMeta: metav1.ObjectMeta{ 684 Name: "app1", 685 Namespace: "namespace", 686 }, 687 Spec: v1alpha1.ApplicationSpec{ 688 Project: "project", 689 Source: &v1alpha1.ApplicationSource{ 690 Directory: &v1alpha1.ApplicationSourceDirectory{ 691 Jsonnet: v1alpha1.ApplicationSourceJsonnet{}, 692 }, 693 }, 694 }, 695 }, 696 }, 697 expected: []v1alpha1.Application{ 698 { 699 TypeMeta: metav1.TypeMeta{ 700 Kind: "Application", 701 APIVersion: "argoproj.io/v1alpha1", 702 }, 703 ObjectMeta: metav1.ObjectMeta{ 704 Name: "app1", 705 Namespace: "namespace", 706 ResourceVersion: "1", 707 }, 708 Spec: v1alpha1.ApplicationSpec{ 709 Project: "project", 710 Source: &v1alpha1.ApplicationSource{ 711 // Directory and jsonnet block are removed 712 }, 713 }, 714 }, 715 }, 716 }, 717 { 718 // For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278 719 name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes", 720 appSet: v1alpha1.ApplicationSet{ 721 ObjectMeta: metav1.ObjectMeta{ 722 Name: "name", 723 Namespace: "namespace", 724 }, 725 Spec: v1alpha1.ApplicationSetSpec{ 726 IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{ 727 {JQPathExpressions: []string{".spec.source.targetRevision"}}, 728 }, 729 Template: v1alpha1.ApplicationSetTemplate{ 730 Spec: v1alpha1.ApplicationSpec{ 731 Project: "project", 732 Source: &v1alpha1.ApplicationSource{ 733 RepoURL: "https://git.example.com/test-org/test-repo.git", 734 TargetRevision: "foo", 735 }, 736 }, 737 }, 738 }, 739 }, 740 existingApps: []v1alpha1.Application{ 741 { 742 TypeMeta: metav1.TypeMeta{ 743 Kind: "Application", 744 APIVersion: "argoproj.io/v1alpha1", 745 }, 746 ObjectMeta: metav1.ObjectMeta{ 747 Name: "app1", 748 Namespace: "namespace", 749 ResourceVersion: "2", 750 }, 751 Spec: v1alpha1.ApplicationSpec{ 752 Project: "project", 753 Source: &v1alpha1.ApplicationSource{ 754 RepoURL: "https://git.example.com/test-org/test-repo.git", 755 TargetRevision: "bar", 756 }, 757 }, 758 }, 759 }, 760 desiredApps: []v1alpha1.Application{ 761 { 762 ObjectMeta: metav1.ObjectMeta{ 763 Name: "app1", 764 Namespace: "namespace", 765 }, 766 Spec: v1alpha1.ApplicationSpec{ 767 Project: "project", 768 Source: &v1alpha1.ApplicationSource{ 769 RepoURL: "https://git.example.com/test-org/test-repo.git", 770 // The targetRevision is ignored, so this should not be updated. 771 TargetRevision: "foo", 772 // This should be updated. 773 Helm: &v1alpha1.ApplicationSourceHelm{ 774 Parameters: []v1alpha1.HelmParameter{ 775 {Name: "hi", Value: "there"}, 776 }, 777 }, 778 }, 779 }, 780 }, 781 }, 782 expected: []v1alpha1.Application{ 783 { 784 TypeMeta: metav1.TypeMeta{ 785 Kind: "Application", 786 APIVersion: "argoproj.io/v1alpha1", 787 }, 788 ObjectMeta: metav1.ObjectMeta{ 789 Name: "app1", 790 Namespace: "namespace", 791 ResourceVersion: "3", 792 }, 793 Spec: v1alpha1.ApplicationSpec{ 794 Project: "project", 795 Source: &v1alpha1.ApplicationSource{ 796 RepoURL: "https://git.example.com/test-org/test-repo.git", 797 // This is the existing value from the cluster, which should not be updated because the field is ignored. 798 TargetRevision: "bar", 799 // This was missing on the cluster, so it should be added. 800 Helm: &v1alpha1.ApplicationSourceHelm{ 801 Parameters: []v1alpha1.HelmParameter{ 802 {Name: "hi", Value: "there"}, 803 }, 804 }, 805 }, 806 }, 807 }, 808 }, 809 }, 810 { 811 // For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799 812 name: "ignore parameters added to a multi-source app in the cluster", 813 appSet: v1alpha1.ApplicationSet{ 814 ObjectMeta: metav1.ObjectMeta{ 815 Name: "name", 816 Namespace: "namespace", 817 }, 818 Spec: v1alpha1.ApplicationSetSpec{ 819 IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{ 820 {JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}}, 821 }, 822 Template: v1alpha1.ApplicationSetTemplate{ 823 Spec: v1alpha1.ApplicationSpec{ 824 Project: "project", 825 Sources: []v1alpha1.ApplicationSource{ 826 { 827 RepoURL: "https://git.example.com/test-org/test-repo.git", 828 Helm: &v1alpha1.ApplicationSourceHelm{ 829 Values: "foo: bar", 830 }, 831 }, 832 }, 833 }, 834 }, 835 }, 836 }, 837 existingApps: []v1alpha1.Application{ 838 { 839 TypeMeta: metav1.TypeMeta{ 840 Kind: "Application", 841 APIVersion: "argoproj.io/v1alpha1", 842 }, 843 ObjectMeta: metav1.ObjectMeta{ 844 Name: "app1", 845 Namespace: "namespace", 846 ResourceVersion: "2", 847 }, 848 Spec: v1alpha1.ApplicationSpec{ 849 Project: "project", 850 Sources: []v1alpha1.ApplicationSource{ 851 { 852 RepoURL: "https://git.example.com/test-org/test-repo.git", 853 Helm: &v1alpha1.ApplicationSourceHelm{ 854 Values: "foo: bar", 855 Parameters: []v1alpha1.HelmParameter{ 856 {Name: "hi", Value: "there"}, 857 }, 858 }, 859 }, 860 }, 861 }, 862 }, 863 }, 864 desiredApps: []v1alpha1.Application{ 865 { 866 ObjectMeta: metav1.ObjectMeta{ 867 Name: "app1", 868 Namespace: "namespace", 869 }, 870 Spec: v1alpha1.ApplicationSpec{ 871 Project: "project", 872 Sources: []v1alpha1.ApplicationSource{ 873 { 874 RepoURL: "https://git.example.com/test-org/test-repo.git", 875 Helm: &v1alpha1.ApplicationSourceHelm{ 876 Values: "foo: bar", 877 }, 878 }, 879 }, 880 }, 881 }, 882 }, 883 expected: []v1alpha1.Application{ 884 { 885 TypeMeta: metav1.TypeMeta{ 886 Kind: "Application", 887 APIVersion: "argoproj.io/v1alpha1", 888 }, 889 ObjectMeta: metav1.ObjectMeta{ 890 Name: "app1", 891 Namespace: "namespace", 892 // This should not be updated, because reconciliation shouldn't modify the App. 893 ResourceVersion: "2", 894 }, 895 Spec: v1alpha1.ApplicationSpec{ 896 Project: "project", 897 Sources: []v1alpha1.ApplicationSource{ 898 { 899 RepoURL: "https://git.example.com/test-org/test-repo.git", 900 Helm: &v1alpha1.ApplicationSourceHelm{ 901 Values: "foo: bar", 902 Parameters: []v1alpha1.HelmParameter{ 903 // This existed only in the cluster, but it shouldn't be removed, because the field is ignored. 904 {Name: "hi", Value: "there"}, 905 }, 906 }, 907 }, 908 }, 909 }, 910 }, 911 }, 912 }, 913 { 914 name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975 915 appSet: v1alpha1.ApplicationSet{ 916 ObjectMeta: metav1.ObjectMeta{ 917 Name: "name", 918 Namespace: "namespace", 919 }, 920 Spec: v1alpha1.ApplicationSetSpec{ 921 IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{ 922 {JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}}, 923 }, 924 Template: v1alpha1.ApplicationSetTemplate{ 925 Spec: v1alpha1.ApplicationSpec{ 926 Project: "project", 927 Sources: []v1alpha1.ApplicationSource{ 928 { 929 RepoURL: "https://git.example.com/test-org/test-repo.git", 930 Helm: &v1alpha1.ApplicationSourceHelm{ 931 Values: "new: values", 932 }, 933 }, 934 }, 935 }, 936 }, 937 }, 938 }, 939 existingApps: []v1alpha1.Application{ 940 { 941 TypeMeta: metav1.TypeMeta{ 942 Kind: "Application", 943 APIVersion: "argoproj.io/v1alpha1", 944 }, 945 ObjectMeta: metav1.ObjectMeta{ 946 Name: "app1", 947 Namespace: "namespace", 948 ResourceVersion: "2", 949 }, 950 Spec: v1alpha1.ApplicationSpec{ 951 Project: "project", 952 Sources: []v1alpha1.ApplicationSource{ 953 { 954 RepoURL: "https://git.example.com/test-org/test-repo.git", 955 Helm: &v1alpha1.ApplicationSourceHelm{ 956 Values: "foo: bar", 957 Parameters: []v1alpha1.HelmParameter{ 958 {Name: "hi", Value: "there"}, 959 }, 960 }, 961 }, 962 }, 963 }, 964 }, 965 }, 966 desiredApps: []v1alpha1.Application{ 967 { 968 ObjectMeta: metav1.ObjectMeta{ 969 Name: "app1", 970 Namespace: "namespace", 971 }, 972 Spec: v1alpha1.ApplicationSpec{ 973 Project: "project", 974 Sources: []v1alpha1.ApplicationSource{ 975 { 976 RepoURL: "https://git.example.com/test-org/test-repo.git", 977 Helm: &v1alpha1.ApplicationSourceHelm{ 978 Values: "new: values", 979 }, 980 }, 981 }, 982 }, 983 }, 984 }, 985 expected: []v1alpha1.Application{ 986 { 987 TypeMeta: metav1.TypeMeta{ 988 Kind: "Application", 989 APIVersion: "argoproj.io/v1alpha1", 990 }, 991 ObjectMeta: metav1.ObjectMeta{ 992 Name: "app1", 993 Namespace: "namespace", 994 ResourceVersion: "3", 995 }, 996 Spec: v1alpha1.ApplicationSpec{ 997 Project: "project", 998 Sources: []v1alpha1.ApplicationSource{ 999 { 1000 RepoURL: "https://git.example.com/test-org/test-repo.git", 1001 Helm: &v1alpha1.ApplicationSourceHelm{ 1002 Values: "new: values", 1003 // The Parameters field got blown away, because the values field changed. MergePatch 1004 // doesn't merge list items, it replaces the whole list if an item changes. 1005 // If we eventually add a `name` field to Sources, we can use StrategicMergePatch. 1006 }, 1007 }, 1008 }, 1009 }, 1010 }, 1011 }, 1012 }, 1013 { 1014 name: "Ensure that argocd post-delete finalizers are preserved from an existing app", 1015 appSet: v1alpha1.ApplicationSet{ 1016 ObjectMeta: metav1.ObjectMeta{ 1017 Name: "name", 1018 Namespace: "namespace", 1019 }, 1020 Spec: v1alpha1.ApplicationSetSpec{ 1021 Template: v1alpha1.ApplicationSetTemplate{ 1022 Spec: v1alpha1.ApplicationSpec{ 1023 Project: "project", 1024 }, 1025 }, 1026 }, 1027 }, 1028 existingApps: []v1alpha1.Application{ 1029 { 1030 TypeMeta: metav1.TypeMeta{ 1031 Kind: application.ApplicationKind, 1032 APIVersion: "argoproj.io/v1alpha1", 1033 }, 1034 ObjectMeta: metav1.ObjectMeta{ 1035 Name: "app1", 1036 Namespace: "namespace", 1037 ResourceVersion: "2", 1038 Finalizers: []string{ 1039 v1alpha1.PostDeleteFinalizerName, 1040 v1alpha1.PostDeleteFinalizerName + "/mystage", 1041 }, 1042 }, 1043 Spec: v1alpha1.ApplicationSpec{ 1044 Project: "project", 1045 }, 1046 }, 1047 }, 1048 desiredApps: []v1alpha1.Application{ 1049 { 1050 ObjectMeta: metav1.ObjectMeta{ 1051 Name: "app1", 1052 Namespace: "namespace", 1053 }, 1054 Spec: v1alpha1.ApplicationSpec{ 1055 Project: "project", 1056 }, 1057 }, 1058 }, 1059 expected: []v1alpha1.Application{ 1060 { 1061 TypeMeta: metav1.TypeMeta{ 1062 Kind: application.ApplicationKind, 1063 APIVersion: "argoproj.io/v1alpha1", 1064 }, 1065 ObjectMeta: metav1.ObjectMeta{ 1066 Name: "app1", 1067 Namespace: "namespace", 1068 ResourceVersion: "2", 1069 Finalizers: []string{ 1070 v1alpha1.PostDeleteFinalizerName, 1071 v1alpha1.PostDeleteFinalizerName + "/mystage", 1072 }, 1073 }, 1074 Spec: v1alpha1.ApplicationSpec{ 1075 Project: "project", 1076 }, 1077 }, 1078 }, 1079 }, 1080 } { 1081 t.Run(c.name, func(t *testing.T) { 1082 initObjs := []crtclient.Object{&c.appSet} 1083 1084 for _, a := range c.existingApps { 1085 err = controllerutil.SetControllerReference(&c.appSet, &a, scheme) 1086 require.NoError(t, err) 1087 initObjs = append(initObjs, &a) 1088 } 1089 1090 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1091 metrics := appsetmetrics.NewFakeAppsetMetrics() 1092 1093 r := ApplicationSetReconciler{ 1094 Client: client, 1095 Scheme: scheme, 1096 Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)), 1097 Metrics: metrics, 1098 } 1099 1100 err = r.createOrUpdateInCluster(t.Context(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps) 1101 require.NoError(t, err) 1102 1103 for _, obj := range c.expected { 1104 got := &v1alpha1.Application{} 1105 _ = client.Get(t.Context(), crtclient.ObjectKey{ 1106 Namespace: obj.Namespace, 1107 Name: obj.Name, 1108 }, got) 1109 1110 err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme) 1111 assert.Equal(t, obj, *got) 1112 } 1113 }) 1114 } 1115 } 1116 1117 func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) { 1118 scheme := runtime.NewScheme() 1119 err := v1alpha1.AddToScheme(scheme) 1120 require.NoError(t, err) 1121 1122 for _, c := range []struct { 1123 // name is human-readable test name 1124 name string 1125 existingFinalizers []string 1126 expectedFinalizers []string 1127 }{ 1128 { 1129 name: "no finalizers", 1130 existingFinalizers: []string{}, 1131 expectedFinalizers: nil, 1132 }, 1133 { 1134 name: "contains only argo finalizer", 1135 existingFinalizers: []string{v1alpha1.ResourcesFinalizerName}, 1136 expectedFinalizers: nil, 1137 }, 1138 { 1139 name: "contains only non-argo finalizer", 1140 existingFinalizers: []string{"non-argo-finalizer"}, 1141 expectedFinalizers: []string{"non-argo-finalizer"}, 1142 }, 1143 { 1144 name: "contains both argo and non-argo finalizer", 1145 existingFinalizers: []string{"non-argo-finalizer", v1alpha1.ResourcesFinalizerName}, 1146 expectedFinalizers: []string{"non-argo-finalizer"}, 1147 }, 1148 } { 1149 t.Run(c.name, func(t *testing.T) { 1150 appSet := v1alpha1.ApplicationSet{ 1151 ObjectMeta: metav1.ObjectMeta{ 1152 Name: "name", 1153 Namespace: "namespace", 1154 }, 1155 Spec: v1alpha1.ApplicationSetSpec{ 1156 Template: v1alpha1.ApplicationSetTemplate{ 1157 Spec: v1alpha1.ApplicationSpec{ 1158 Project: "project", 1159 }, 1160 }, 1161 }, 1162 } 1163 1164 app := v1alpha1.Application{ 1165 ObjectMeta: metav1.ObjectMeta{ 1166 Name: "app1", 1167 Finalizers: c.existingFinalizers, 1168 }, 1169 Spec: v1alpha1.ApplicationSpec{ 1170 Project: "project", 1171 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 1172 // Destination is always invalid, for this test: 1173 Destination: v1alpha1.ApplicationDestination{Name: "my-cluster", Namespace: "namespace"}, 1174 }, 1175 } 1176 1177 initObjs := []crtclient.Object{&app, &appSet} 1178 1179 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1180 secret := &corev1.Secret{ 1181 ObjectMeta: metav1.ObjectMeta{ 1182 Name: "my-secret", 1183 Namespace: "namespace", 1184 Labels: map[string]string{ 1185 argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, 1186 }, 1187 }, 1188 Data: map[string][]byte{ 1189 // Since this test requires the cluster to be an invalid destination, we 1190 // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) 1191 "name": []byte("mycluster2"), 1192 "server": []byte("https://kubernetes.default.svc"), 1193 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 1194 }, 1195 } 1196 1197 objects := append([]runtime.Object{}, secret) 1198 kubeclientset := kubefake.NewSimpleClientset(objects...) 1199 metrics := appsetmetrics.NewFakeAppsetMetrics() 1200 1201 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 1202 1203 r := ApplicationSetReconciler{ 1204 Client: client, 1205 Scheme: scheme, 1206 Recorder: record.NewFakeRecorder(10), 1207 KubeClientset: kubeclientset, 1208 Metrics: metrics, 1209 ArgoDB: argodb, 1210 } 1211 clusterList, err := utils.ListClusters(t.Context(), kubeclientset, "namespace") 1212 require.NoError(t, err) 1213 1214 appLog := log.WithFields(applog.GetAppLogFields(&app)).WithField("appSet", "") 1215 1216 appInputParam := app.DeepCopy() 1217 1218 err = r.removeFinalizerOnInvalidDestination(t.Context(), appSet, appInputParam, clusterList, appLog) 1219 require.NoError(t, err) 1220 1221 retrievedApp := v1alpha1.Application{} 1222 err = client.Get(t.Context(), crtclient.ObjectKeyFromObject(&app), &retrievedApp) 1223 require.NoError(t, err) 1224 1225 // App on the cluster should have the expected finalizers 1226 assert.ElementsMatch(t, c.expectedFinalizers, retrievedApp.Finalizers) 1227 1228 // App object passed in as a parameter should have the expected finaliers 1229 assert.ElementsMatch(t, c.expectedFinalizers, appInputParam.Finalizers) 1230 1231 bytes, _ := json.MarshalIndent(retrievedApp, "", " ") 1232 t.Log("Contents of app after call:", string(bytes)) 1233 }) 1234 } 1235 } 1236 1237 func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) { 1238 scheme := runtime.NewScheme() 1239 err := v1alpha1.AddToScheme(scheme) 1240 require.NoError(t, err) 1241 1242 for _, c := range []struct { 1243 // name is human-readable test name 1244 name string 1245 destinationField v1alpha1.ApplicationDestination 1246 expectFinalizerRemoved bool 1247 }{ 1248 { 1249 name: "invalid cluster: empty destination", 1250 destinationField: v1alpha1.ApplicationDestination{ 1251 Namespace: "namespace", 1252 }, 1253 expectFinalizerRemoved: true, 1254 }, 1255 { 1256 name: "invalid cluster: invalid server url", 1257 destinationField: v1alpha1.ApplicationDestination{ 1258 Namespace: "namespace", 1259 Server: "https://1.2.3.4", 1260 }, 1261 expectFinalizerRemoved: true, 1262 }, 1263 { 1264 name: "invalid cluster: invalid cluster name", 1265 destinationField: v1alpha1.ApplicationDestination{ 1266 Namespace: "namespace", 1267 Name: "invalid-cluster", 1268 }, 1269 expectFinalizerRemoved: true, 1270 }, 1271 { 1272 name: "invalid cluster by both valid", 1273 destinationField: v1alpha1.ApplicationDestination{ 1274 Namespace: "namespace", 1275 Name: "mycluster2", 1276 Server: "https://kubernetes.default.svc", 1277 }, 1278 expectFinalizerRemoved: true, 1279 }, 1280 { 1281 name: "invalid cluster by both invalid", 1282 destinationField: v1alpha1.ApplicationDestination{ 1283 Namespace: "namespace", 1284 Name: "mycluster3", 1285 Server: "https://4.5.6.7", 1286 }, 1287 expectFinalizerRemoved: true, 1288 }, 1289 { 1290 name: "valid cluster by name", 1291 destinationField: v1alpha1.ApplicationDestination{ 1292 Namespace: "namespace", 1293 Name: "mycluster2", 1294 }, 1295 expectFinalizerRemoved: false, 1296 }, 1297 { 1298 name: "valid cluster by server", 1299 destinationField: v1alpha1.ApplicationDestination{ 1300 Namespace: "namespace", 1301 Server: "https://kubernetes.default.svc", 1302 }, 1303 expectFinalizerRemoved: false, 1304 }, 1305 } { 1306 t.Run(c.name, func(t *testing.T) { 1307 appSet := v1alpha1.ApplicationSet{ 1308 ObjectMeta: metav1.ObjectMeta{ 1309 Name: "name", 1310 Namespace: "namespace", 1311 }, 1312 Spec: v1alpha1.ApplicationSetSpec{ 1313 Template: v1alpha1.ApplicationSetTemplate{ 1314 Spec: v1alpha1.ApplicationSpec{ 1315 Project: "project", 1316 }, 1317 }, 1318 }, 1319 } 1320 1321 app := v1alpha1.Application{ 1322 ObjectMeta: metav1.ObjectMeta{ 1323 Name: "app1", 1324 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 1325 }, 1326 Spec: v1alpha1.ApplicationSpec{ 1327 Project: "project", 1328 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 1329 Destination: c.destinationField, 1330 }, 1331 } 1332 1333 initObjs := []crtclient.Object{&app, &appSet} 1334 1335 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1336 secret := &corev1.Secret{ 1337 ObjectMeta: metav1.ObjectMeta{ 1338 Name: "my-secret", 1339 Namespace: "argocd", 1340 Labels: map[string]string{ 1341 argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, 1342 }, 1343 }, 1344 Data: map[string][]byte{ 1345 // Since this test requires the cluster to be an invalid destination, we 1346 // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) 1347 "name": []byte("mycluster2"), 1348 "server": []byte("https://kubernetes.default.svc"), 1349 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 1350 }, 1351 } 1352 1353 kubeclientset := getDefaultTestClientSet(secret) 1354 metrics := appsetmetrics.NewFakeAppsetMetrics() 1355 1356 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 1357 1358 r := ApplicationSetReconciler{ 1359 Client: client, 1360 Scheme: scheme, 1361 Recorder: record.NewFakeRecorder(10), 1362 KubeClientset: kubeclientset, 1363 Metrics: metrics, 1364 ArgoDB: argodb, 1365 } 1366 1367 clusterList, err := utils.ListClusters(t.Context(), kubeclientset, "argocd") 1368 require.NoError(t, err) 1369 1370 appLog := log.WithFields(applog.GetAppLogFields(&app)).WithField("appSet", "") 1371 1372 appInputParam := app.DeepCopy() 1373 1374 err = r.removeFinalizerOnInvalidDestination(t.Context(), appSet, appInputParam, clusterList, appLog) 1375 require.NoError(t, err) 1376 1377 retrievedApp := v1alpha1.Application{} 1378 err = client.Get(t.Context(), crtclient.ObjectKeyFromObject(&app), &retrievedApp) 1379 require.NoError(t, err) 1380 1381 finalizerRemoved := len(retrievedApp.Finalizers) == 0 1382 1383 assert.Equal(t, c.expectFinalizerRemoved, finalizerRemoved) 1384 1385 bytes, _ := json.MarshalIndent(retrievedApp, "", " ") 1386 t.Log("Contents of app after call:", string(bytes)) 1387 }) 1388 } 1389 } 1390 1391 func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) { 1392 scheme := runtime.NewScheme() 1393 err := v1alpha1.AddToScheme(scheme) 1394 require.NoError(t, err) 1395 1396 for _, c := range []struct { 1397 // name is human-readable test name 1398 name string 1399 }{ 1400 { 1401 name: "ownerReferences cleared", 1402 }, 1403 } { 1404 t.Run(c.name, func(t *testing.T) { 1405 appSet := v1alpha1.ApplicationSet{ 1406 ObjectMeta: metav1.ObjectMeta{ 1407 Name: "name", 1408 Namespace: "namespace", 1409 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 1410 }, 1411 Spec: v1alpha1.ApplicationSetSpec{ 1412 Template: v1alpha1.ApplicationSetTemplate{ 1413 Spec: v1alpha1.ApplicationSpec{ 1414 Project: "project", 1415 }, 1416 }, 1417 }, 1418 } 1419 1420 app := v1alpha1.Application{ 1421 ObjectMeta: metav1.ObjectMeta{ 1422 Name: "app1", 1423 Namespace: "namespace", 1424 }, 1425 Spec: v1alpha1.ApplicationSpec{ 1426 Project: "project", 1427 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 1428 Destination: v1alpha1.ApplicationDestination{ 1429 Namespace: "namespace", 1430 Server: "https://kubernetes.default.svc", 1431 }, 1432 }, 1433 } 1434 1435 err := controllerutil.SetControllerReference(&appSet, &app, scheme) 1436 require.NoError(t, err) 1437 1438 initObjs := []crtclient.Object{&app, &appSet} 1439 1440 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1441 metrics := appsetmetrics.NewFakeAppsetMetrics() 1442 1443 r := ApplicationSetReconciler{ 1444 Client: client, 1445 Scheme: scheme, 1446 Recorder: record.NewFakeRecorder(10), 1447 KubeClientset: nil, 1448 Metrics: metrics, 1449 } 1450 1451 err = r.removeOwnerReferencesOnDeleteAppSet(t.Context(), appSet) 1452 require.NoError(t, err) 1453 1454 retrievedApp := v1alpha1.Application{} 1455 err = client.Get(t.Context(), crtclient.ObjectKeyFromObject(&app), &retrievedApp) 1456 require.NoError(t, err) 1457 1458 ownerReferencesRemoved := len(retrievedApp.OwnerReferences) == 0 1459 assert.True(t, ownerReferencesRemoved) 1460 }) 1461 } 1462 } 1463 1464 func TestCreateApplications(t *testing.T) { 1465 scheme := runtime.NewScheme() 1466 err := v1alpha1.AddToScheme(scheme) 1467 require.NoError(t, err) 1468 1469 testCases := []struct { 1470 name string 1471 appSet v1alpha1.ApplicationSet 1472 existsApps []v1alpha1.Application 1473 apps []v1alpha1.Application 1474 expected []v1alpha1.Application 1475 }{ 1476 { 1477 name: "no existing apps", 1478 appSet: v1alpha1.ApplicationSet{ 1479 ObjectMeta: metav1.ObjectMeta{ 1480 Name: "name", 1481 Namespace: "namespace", 1482 }, 1483 }, 1484 existsApps: nil, 1485 apps: []v1alpha1.Application{ 1486 { 1487 ObjectMeta: metav1.ObjectMeta{ 1488 Name: "app1", 1489 Namespace: "namespace", 1490 }, 1491 }, 1492 }, 1493 expected: []v1alpha1.Application{ 1494 { 1495 TypeMeta: metav1.TypeMeta{ 1496 Kind: application.ApplicationKind, 1497 APIVersion: "argoproj.io/v1alpha1", 1498 }, 1499 ObjectMeta: metav1.ObjectMeta{ 1500 Name: "app1", 1501 Namespace: "namespace", 1502 ResourceVersion: "1", 1503 }, 1504 Spec: v1alpha1.ApplicationSpec{ 1505 Project: "default", 1506 }, 1507 }, 1508 }, 1509 }, 1510 { 1511 name: "existing apps", 1512 appSet: v1alpha1.ApplicationSet{ 1513 ObjectMeta: metav1.ObjectMeta{ 1514 Name: "name", 1515 Namespace: "namespace", 1516 }, 1517 Spec: v1alpha1.ApplicationSetSpec{ 1518 Template: v1alpha1.ApplicationSetTemplate{ 1519 Spec: v1alpha1.ApplicationSpec{ 1520 Project: "project", 1521 }, 1522 }, 1523 }, 1524 }, 1525 existsApps: []v1alpha1.Application{ 1526 { 1527 TypeMeta: metav1.TypeMeta{ 1528 Kind: application.ApplicationKind, 1529 APIVersion: "argoproj.io/v1alpha1", 1530 }, 1531 ObjectMeta: metav1.ObjectMeta{ 1532 Name: "app1", 1533 Namespace: "namespace", 1534 ResourceVersion: "2", 1535 }, 1536 Spec: v1alpha1.ApplicationSpec{ 1537 Project: "test", 1538 }, 1539 }, 1540 }, 1541 apps: []v1alpha1.Application{ 1542 { 1543 ObjectMeta: metav1.ObjectMeta{ 1544 Name: "app1", 1545 Namespace: "namespace", 1546 }, 1547 Spec: v1alpha1.ApplicationSpec{ 1548 Project: "project", 1549 }, 1550 }, 1551 }, 1552 expected: []v1alpha1.Application{ 1553 { 1554 TypeMeta: metav1.TypeMeta{ 1555 Kind: application.ApplicationKind, 1556 APIVersion: "argoproj.io/v1alpha1", 1557 }, 1558 ObjectMeta: metav1.ObjectMeta{ 1559 Name: "app1", 1560 Namespace: "namespace", 1561 ResourceVersion: "2", 1562 }, 1563 Spec: v1alpha1.ApplicationSpec{ 1564 Project: "test", 1565 }, 1566 }, 1567 }, 1568 }, 1569 { 1570 name: "existing apps with different project", 1571 appSet: v1alpha1.ApplicationSet{ 1572 ObjectMeta: metav1.ObjectMeta{ 1573 Name: "name", 1574 Namespace: "namespace", 1575 }, 1576 Spec: v1alpha1.ApplicationSetSpec{ 1577 Template: v1alpha1.ApplicationSetTemplate{ 1578 Spec: v1alpha1.ApplicationSpec{ 1579 Project: "project", 1580 }, 1581 }, 1582 }, 1583 }, 1584 existsApps: []v1alpha1.Application{ 1585 { 1586 TypeMeta: metav1.TypeMeta{ 1587 Kind: application.ApplicationKind, 1588 APIVersion: "argoproj.io/v1alpha1", 1589 }, 1590 ObjectMeta: metav1.ObjectMeta{ 1591 Name: "app1", 1592 Namespace: "namespace", 1593 ResourceVersion: "2", 1594 }, 1595 Spec: v1alpha1.ApplicationSpec{ 1596 Project: "test", 1597 }, 1598 }, 1599 }, 1600 apps: []v1alpha1.Application{ 1601 { 1602 ObjectMeta: metav1.ObjectMeta{ 1603 Name: "app2", 1604 Namespace: "namespace", 1605 }, 1606 Spec: v1alpha1.ApplicationSpec{ 1607 Project: "project", 1608 }, 1609 }, 1610 }, 1611 expected: []v1alpha1.Application{ 1612 { 1613 TypeMeta: metav1.TypeMeta{ 1614 Kind: application.ApplicationKind, 1615 APIVersion: "argoproj.io/v1alpha1", 1616 }, 1617 ObjectMeta: metav1.ObjectMeta{ 1618 Name: "app2", 1619 Namespace: "namespace", 1620 ResourceVersion: "1", 1621 }, 1622 Spec: v1alpha1.ApplicationSpec{ 1623 Project: "project", 1624 }, 1625 }, 1626 }, 1627 }, 1628 } 1629 1630 for _, c := range testCases { 1631 t.Run(c.name, func(t *testing.T) { 1632 initObjs := []crtclient.Object{&c.appSet} 1633 for _, a := range c.existsApps { 1634 err = controllerutil.SetControllerReference(&c.appSet, &a, scheme) 1635 require.NoError(t, err) 1636 initObjs = append(initObjs, &a) 1637 } 1638 1639 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1640 metrics := appsetmetrics.NewFakeAppsetMetrics() 1641 1642 r := ApplicationSetReconciler{ 1643 Client: client, 1644 Scheme: scheme, 1645 Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)), 1646 Metrics: metrics, 1647 } 1648 1649 err = r.createInCluster(t.Context(), log.NewEntry(log.StandardLogger()), c.appSet, c.apps) 1650 require.NoError(t, err) 1651 1652 for _, obj := range c.expected { 1653 got := &v1alpha1.Application{} 1654 _ = client.Get(t.Context(), crtclient.ObjectKey{ 1655 Namespace: obj.Namespace, 1656 Name: obj.Name, 1657 }, got) 1658 1659 err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme) 1660 require.NoError(t, err) 1661 1662 assert.Equal(t, obj, *got) 1663 } 1664 }) 1665 } 1666 } 1667 1668 func TestDeleteInCluster(t *testing.T) { 1669 scheme := runtime.NewScheme() 1670 err := v1alpha1.AddToScheme(scheme) 1671 require.NoError(t, err) 1672 1673 for _, c := range []struct { 1674 // appSet is the application set on which the delete function is called 1675 appSet v1alpha1.ApplicationSet 1676 // existingApps is the current state of Applications on the cluster 1677 existingApps []v1alpha1.Application 1678 // desireApps is the apps generated by the generator that we wish to keep alive 1679 desiredApps []v1alpha1.Application 1680 // expected is the list of applications that we expect to exist after calling delete 1681 expected []v1alpha1.Application 1682 // notExpected is the list of applications that we expect not to exist after calling delete 1683 notExpected []v1alpha1.Application 1684 }{ 1685 { 1686 appSet: v1alpha1.ApplicationSet{ 1687 ObjectMeta: metav1.ObjectMeta{ 1688 Name: "name", 1689 Namespace: "namespace", 1690 }, 1691 Spec: v1alpha1.ApplicationSetSpec{ 1692 Template: v1alpha1.ApplicationSetTemplate{ 1693 Spec: v1alpha1.ApplicationSpec{ 1694 Project: "project", 1695 }, 1696 }, 1697 }, 1698 }, 1699 existingApps: []v1alpha1.Application{ 1700 { 1701 TypeMeta: metav1.TypeMeta{ 1702 Kind: application.ApplicationKind, 1703 APIVersion: "argoproj.io/v1alpha1", 1704 }, 1705 ObjectMeta: metav1.ObjectMeta{ 1706 Name: "delete", 1707 Namespace: "namespace", 1708 ResourceVersion: "2", 1709 }, 1710 Spec: v1alpha1.ApplicationSpec{ 1711 Project: "project", 1712 }, 1713 }, 1714 { 1715 TypeMeta: metav1.TypeMeta{ 1716 Kind: application.ApplicationKind, 1717 APIVersion: "argoproj.io/v1alpha1", 1718 }, 1719 ObjectMeta: metav1.ObjectMeta{ 1720 Name: "keep", 1721 Namespace: "namespace", 1722 ResourceVersion: "2", 1723 }, 1724 Spec: v1alpha1.ApplicationSpec{ 1725 Project: "project", 1726 }, 1727 }, 1728 }, 1729 desiredApps: []v1alpha1.Application{ 1730 { 1731 ObjectMeta: metav1.ObjectMeta{ 1732 Name: "keep", 1733 }, 1734 Spec: v1alpha1.ApplicationSpec{ 1735 Project: "project", 1736 }, 1737 }, 1738 }, 1739 expected: []v1alpha1.Application{ 1740 { 1741 TypeMeta: metav1.TypeMeta{ 1742 Kind: application.ApplicationKind, 1743 APIVersion: "argoproj.io/v1alpha1", 1744 }, 1745 ObjectMeta: metav1.ObjectMeta{ 1746 Name: "keep", 1747 Namespace: "namespace", 1748 ResourceVersion: "2", 1749 }, 1750 Spec: v1alpha1.ApplicationSpec{ 1751 Project: "project", 1752 }, 1753 }, 1754 }, 1755 notExpected: []v1alpha1.Application{ 1756 { 1757 TypeMeta: metav1.TypeMeta{ 1758 Kind: application.ApplicationKind, 1759 APIVersion: "argoproj.io/v1alpha1", 1760 }, 1761 ObjectMeta: metav1.ObjectMeta{ 1762 Name: "delete", 1763 Namespace: "namespace", 1764 ResourceVersion: "1", 1765 }, 1766 Spec: v1alpha1.ApplicationSpec{ 1767 Project: "project", 1768 }, 1769 }, 1770 }, 1771 }, 1772 } { 1773 initObjs := []crtclient.Object{&c.appSet} 1774 for _, a := range c.existingApps { 1775 temp := a 1776 err = controllerutil.SetControllerReference(&c.appSet, &temp, scheme) 1777 require.NoError(t, err) 1778 initObjs = append(initObjs, &temp) 1779 } 1780 1781 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1782 metrics := appsetmetrics.NewFakeAppsetMetrics() 1783 1784 r := ApplicationSetReconciler{ 1785 Client: client, 1786 Scheme: scheme, 1787 Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)), 1788 KubeClientset: kubefake.NewSimpleClientset(), 1789 Metrics: metrics, 1790 } 1791 1792 err = r.deleteInCluster(t.Context(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps) 1793 require.NoError(t, err) 1794 1795 // For each of the expected objects, verify they exist on the cluster 1796 for _, obj := range c.expected { 1797 got := &v1alpha1.Application{} 1798 _ = client.Get(t.Context(), crtclient.ObjectKey{ 1799 Namespace: obj.Namespace, 1800 Name: obj.Name, 1801 }, got) 1802 1803 err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme) 1804 require.NoError(t, err) 1805 1806 assert.Equal(t, obj, *got) 1807 } 1808 1809 // Verify each of the unexpected objs cannot be found 1810 for _, obj := range c.notExpected { 1811 got := &v1alpha1.Application{} 1812 err := client.Get(t.Context(), crtclient.ObjectKey{ 1813 Namespace: obj.Namespace, 1814 Name: obj.Name, 1815 }, got) 1816 1817 assert.EqualError(t, err, fmt.Sprintf("applications.argoproj.io %q not found", obj.Name)) 1818 } 1819 } 1820 } 1821 1822 func TestGetMinRequeueAfter(t *testing.T) { 1823 scheme := runtime.NewScheme() 1824 err := v1alpha1.AddToScheme(scheme) 1825 require.NoError(t, err) 1826 1827 client := fake.NewClientBuilder().WithScheme(scheme).Build() 1828 metrics := appsetmetrics.NewFakeAppsetMetrics() 1829 1830 generator := v1alpha1.ApplicationSetGenerator{ 1831 List: &v1alpha1.ListGenerator{}, 1832 Git: &v1alpha1.GitGenerator{}, 1833 Clusters: &v1alpha1.ClusterGenerator{}, 1834 } 1835 1836 generatorMock0 := mocks.Generator{} 1837 generatorMock0.On("GetRequeueAfter", &generator). 1838 Return(generators.NoRequeueAfter) 1839 1840 generatorMock1 := mocks.Generator{} 1841 generatorMock1.On("GetRequeueAfter", &generator). 1842 Return(time.Duration(1) * time.Second) 1843 1844 generatorMock10 := mocks.Generator{} 1845 generatorMock10.On("GetRequeueAfter", &generator). 1846 Return(time.Duration(10) * time.Second) 1847 1848 r := ApplicationSetReconciler{ 1849 Client: client, 1850 Scheme: scheme, 1851 Recorder: record.NewFakeRecorder(0), 1852 Metrics: metrics, 1853 Generators: map[string]generators.Generator{ 1854 "List": &generatorMock10, 1855 "Git": &generatorMock1, 1856 "Clusters": &generatorMock1, 1857 }, 1858 } 1859 1860 got := r.getMinRequeueAfter(&v1alpha1.ApplicationSet{ 1861 Spec: v1alpha1.ApplicationSetSpec{ 1862 Generators: []v1alpha1.ApplicationSetGenerator{generator}, 1863 }, 1864 }) 1865 1866 assert.Equal(t, time.Duration(1)*time.Second, got) 1867 } 1868 1869 func TestRequeueGeneratorFails(t *testing.T) { 1870 scheme := runtime.NewScheme() 1871 err := v1alpha1.AddToScheme(scheme) 1872 require.NoError(t, err) 1873 err = v1alpha1.AddToScheme(scheme) 1874 require.NoError(t, err) 1875 1876 appSet := v1alpha1.ApplicationSet{ 1877 ObjectMeta: metav1.ObjectMeta{ 1878 Name: "name", 1879 Namespace: "argocd", 1880 }, 1881 Spec: v1alpha1.ApplicationSetSpec{ 1882 Generators: []v1alpha1.ApplicationSetGenerator{{ 1883 PullRequest: &v1alpha1.PullRequestGenerator{}, 1884 }}, 1885 }, 1886 } 1887 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build() 1888 1889 generator := v1alpha1.ApplicationSetGenerator{ 1890 PullRequest: &v1alpha1.PullRequestGenerator{}, 1891 } 1892 1893 generatorMock := mocks.Generator{} 1894 generatorMock.On("GetTemplate", &generator). 1895 Return(&v1alpha1.ApplicationSetTemplate{}) 1896 generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything). 1897 Return([]map[string]any{}, errors.New("Simulated error generating params that could be related to an external service/API call")) 1898 1899 metrics := appsetmetrics.NewFakeAppsetMetrics() 1900 1901 r := ApplicationSetReconciler{ 1902 Client: client, 1903 Scheme: scheme, 1904 Recorder: record.NewFakeRecorder(0), 1905 Generators: map[string]generators.Generator{ 1906 "PullRequest": &generatorMock, 1907 }, 1908 Metrics: metrics, 1909 } 1910 1911 req := ctrl.Request{ 1912 NamespacedName: types.NamespacedName{ 1913 Namespace: "argocd", 1914 Name: "name", 1915 }, 1916 } 1917 1918 res, err := r.Reconcile(t.Context(), req) 1919 require.NoError(t, err) 1920 assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter) 1921 } 1922 1923 func TestValidateGeneratedApplications(t *testing.T) { 1924 t.Parallel() 1925 1926 scheme := runtime.NewScheme() 1927 err := v1alpha1.AddToScheme(scheme) 1928 require.NoError(t, err) 1929 1930 // Valid project 1931 myProject := &v1alpha1.AppProject{ 1932 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "namespace"}, 1933 Spec: v1alpha1.AppProjectSpec{ 1934 SourceRepos: []string{"*"}, 1935 Destinations: []v1alpha1.ApplicationDestination{ 1936 { 1937 Namespace: "*", 1938 Server: "*", 1939 }, 1940 }, 1941 ClusterResourceWhitelist: []metav1.GroupKind{ 1942 { 1943 Group: "*", 1944 Kind: "*", 1945 }, 1946 }, 1947 }, 1948 } 1949 1950 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(myProject).Build() 1951 metrics := appsetmetrics.NewFakeAppsetMetrics() 1952 1953 // Test a subset of the validations that 'validateGeneratedApplications' performs 1954 for _, cc := range []struct { 1955 name string 1956 apps []v1alpha1.Application 1957 validationErrors map[string]error 1958 }{ 1959 { 1960 name: "valid app should return true", 1961 apps: []v1alpha1.Application{ 1962 { 1963 ObjectMeta: metav1.ObjectMeta{ 1964 Name: "app", 1965 }, 1966 Spec: v1alpha1.ApplicationSpec{ 1967 Project: "default", 1968 Source: &v1alpha1.ApplicationSource{ 1969 RepoURL: "https://url", 1970 Path: "/", 1971 TargetRevision: "HEAD", 1972 }, 1973 Destination: v1alpha1.ApplicationDestination{ 1974 Namespace: "namespace", 1975 Name: "my-cluster", 1976 }, 1977 }, 1978 }, 1979 }, 1980 validationErrors: map[string]error{}, 1981 }, 1982 { 1983 name: "can't have both name and server defined", 1984 apps: []v1alpha1.Application{ 1985 { 1986 ObjectMeta: metav1.ObjectMeta{ 1987 Name: "app", 1988 }, 1989 Spec: v1alpha1.ApplicationSpec{ 1990 Project: "default", 1991 Source: &v1alpha1.ApplicationSource{ 1992 RepoURL: "https://url", 1993 Path: "/", 1994 TargetRevision: "HEAD", 1995 }, 1996 Destination: v1alpha1.ApplicationDestination{ 1997 Namespace: "namespace", 1998 Server: "my-server", 1999 Name: "my-cluster", 2000 }, 2001 }, 2002 }, 2003 }, 2004 validationErrors: map[string]error{"app": errors.New("application destination spec is invalid: application destination can't have both name and server defined: my-cluster my-server")}, 2005 }, 2006 { 2007 name: "project mismatch should return error", 2008 apps: []v1alpha1.Application{ 2009 { 2010 ObjectMeta: metav1.ObjectMeta{ 2011 Name: "app", 2012 }, 2013 Spec: v1alpha1.ApplicationSpec{ 2014 Project: "DOES-NOT-EXIST", 2015 Source: &v1alpha1.ApplicationSource{ 2016 RepoURL: "https://url", 2017 Path: "/", 2018 TargetRevision: "HEAD", 2019 }, 2020 Destination: v1alpha1.ApplicationDestination{ 2021 Namespace: "namespace", 2022 Name: "my-cluster", 2023 }, 2024 }, 2025 }, 2026 }, 2027 validationErrors: map[string]error{"app": errors.New("application references project DOES-NOT-EXIST which does not exist")}, 2028 }, 2029 { 2030 name: "valid app should return true", 2031 apps: []v1alpha1.Application{ 2032 { 2033 ObjectMeta: metav1.ObjectMeta{ 2034 Name: "app", 2035 }, 2036 Spec: v1alpha1.ApplicationSpec{ 2037 Project: "default", 2038 Source: &v1alpha1.ApplicationSource{ 2039 RepoURL: "https://url", 2040 Path: "/", 2041 TargetRevision: "HEAD", 2042 }, 2043 Destination: v1alpha1.ApplicationDestination{ 2044 Namespace: "namespace", 2045 Name: "my-cluster", 2046 }, 2047 }, 2048 }, 2049 }, 2050 validationErrors: map[string]error{}, 2051 }, 2052 { 2053 name: "cluster should match", 2054 apps: []v1alpha1.Application{ 2055 { 2056 ObjectMeta: metav1.ObjectMeta{ 2057 Name: "app", 2058 }, 2059 Spec: v1alpha1.ApplicationSpec{ 2060 Project: "default", 2061 Source: &v1alpha1.ApplicationSource{ 2062 RepoURL: "https://url", 2063 Path: "/", 2064 TargetRevision: "HEAD", 2065 }, 2066 Destination: v1alpha1.ApplicationDestination{ 2067 Namespace: "namespace", 2068 Name: "nonexistent-cluster", 2069 }, 2070 }, 2071 }, 2072 }, 2073 validationErrors: map[string]error{"app": errors.New("application destination spec is invalid: there are no clusters with this name: nonexistent-cluster")}, 2074 }, 2075 } { 2076 t.Run(cc.name, func(t *testing.T) { 2077 t.Parallel() 2078 2079 secret := &corev1.Secret{ 2080 ObjectMeta: metav1.ObjectMeta{ 2081 Name: "my-secret", 2082 Namespace: "argocd", 2083 Labels: map[string]string{ 2084 argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, 2085 }, 2086 }, 2087 Data: map[string][]byte{ 2088 "name": []byte("my-cluster"), 2089 "server": []byte("https://kubernetes.default.svc"), 2090 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 2091 }, 2092 } 2093 2094 kubeclientset := getDefaultTestClientSet(secret) 2095 2096 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 2097 2098 r := ApplicationSetReconciler{ 2099 Client: client, 2100 Scheme: scheme, 2101 Recorder: record.NewFakeRecorder(1), 2102 Generators: map[string]generators.Generator{}, 2103 ArgoDB: argodb, 2104 ArgoCDNamespace: "namespace", 2105 KubeClientset: kubeclientset, 2106 Metrics: metrics, 2107 } 2108 2109 appSetInfo := v1alpha1.ApplicationSet{} 2110 validationErrors, _ := r.validateGeneratedApplications(t.Context(), cc.apps, appSetInfo) 2111 assert.Equal(t, cc.validationErrors, validationErrors) 2112 }) 2113 } 2114 } 2115 2116 func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) { 2117 scheme := runtime.NewScheme() 2118 err := v1alpha1.AddToScheme(scheme) 2119 require.NoError(t, err) 2120 2121 project := v1alpha1.AppProject{ 2122 ObjectMeta: metav1.ObjectMeta{Name: "good-project", Namespace: "argocd"}, 2123 } 2124 appSet := v1alpha1.ApplicationSet{ 2125 ObjectMeta: metav1.ObjectMeta{ 2126 Name: "name", 2127 Namespace: "argocd", 2128 }, 2129 Spec: v1alpha1.ApplicationSetSpec{ 2130 GoTemplate: true, 2131 Generators: []v1alpha1.ApplicationSetGenerator{ 2132 { 2133 List: &v1alpha1.ListGenerator{ 2134 Elements: []apiextensionsv1.JSON{{ 2135 Raw: []byte(`{"project": "good-project"}`), 2136 }, { 2137 Raw: []byte(`{"project": "bad-project"}`), 2138 }}, 2139 }, 2140 }, 2141 }, 2142 Template: v1alpha1.ApplicationSetTemplate{ 2143 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2144 Name: "{{.project}}", 2145 Namespace: "argocd", 2146 }, 2147 Spec: v1alpha1.ApplicationSpec{ 2148 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 2149 Project: "{{.project}}", 2150 Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"}, 2151 }, 2152 }, 2153 }, 2154 } 2155 2156 kubeclientset := getDefaultTestClientSet() 2157 2158 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &project).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2159 metrics := appsetmetrics.NewFakeAppsetMetrics() 2160 2161 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 2162 2163 r := ApplicationSetReconciler{ 2164 Client: client, 2165 Scheme: scheme, 2166 Renderer: &utils.Render{}, 2167 Recorder: record.NewFakeRecorder(1), 2168 Generators: map[string]generators.Generator{ 2169 "List": generators.NewListGenerator(), 2170 }, 2171 ArgoDB: argodb, 2172 KubeClientset: kubeclientset, 2173 Policy: v1alpha1.ApplicationsSyncPolicySync, 2174 ArgoCDNamespace: "argocd", 2175 Metrics: metrics, 2176 } 2177 2178 req := ctrl.Request{ 2179 NamespacedName: types.NamespacedName{ 2180 Namespace: "argocd", 2181 Name: "name", 2182 }, 2183 } 2184 2185 // Verify that on validation error, no error is returned, but the object is requeued 2186 res, err := r.Reconcile(t.Context(), req) 2187 require.NoError(t, err) 2188 assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter) 2189 2190 var app v1alpha1.Application 2191 2192 // make sure good app got created 2193 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-project"}, &app) 2194 require.NoError(t, err) 2195 assert.Equal(t, "good-project", app.Name) 2196 2197 // make sure bad app was not created 2198 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "bad-project"}, &app) 2199 require.Error(t, err) 2200 } 2201 2202 func TestSetApplicationSetStatusCondition(t *testing.T) { 2203 scheme := runtime.NewScheme() 2204 err := v1alpha1.AddToScheme(scheme) 2205 require.NoError(t, err) 2206 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 2207 someTime := &metav1.Time{Time: time.Now().Add(-5 * time.Minute)} 2208 existingParameterGeneratedCondition := getParametersGeneratedCondition(true, "") 2209 existingParameterGeneratedCondition.LastTransitionTime = someTime 2210 2211 for _, c := range []struct { 2212 name string 2213 appset v1alpha1.ApplicationSet 2214 condition v1alpha1.ApplicationSetCondition 2215 parametersGenerated bool 2216 testfunc func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) 2217 }{ 2218 { 2219 name: "has parameters generated condition when false", 2220 appset: v1alpha1.ApplicationSet{ 2221 ObjectMeta: metav1.ObjectMeta{ 2222 Name: "name", 2223 Namespace: "argocd", 2224 }, 2225 Spec: v1alpha1.ApplicationSetSpec{ 2226 Generators: []v1alpha1.ApplicationSetGenerator{ 2227 {List: &v1alpha1.ListGenerator{ 2228 Elements: []apiextensionsv1.JSON{{ 2229 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2230 }}, 2231 }}, 2232 }, 2233 Template: v1alpha1.ApplicationSetTemplate{}, 2234 }, 2235 }, 2236 condition: v1alpha1.ApplicationSetCondition{ 2237 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2238 Message: "This is a message", 2239 Reason: "test", 2240 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2241 }, 2242 parametersGenerated: false, 2243 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2244 t.Helper() 2245 require.Len(t, conditions, 2) 2246 2247 // Conditions are ordered by type, so the order is deterministic 2248 assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[0].Type) 2249 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[0].Status) 2250 2251 assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[1].Type) 2252 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[1].Status) 2253 assert.Equal(t, "test", conditions[1].Reason) 2254 }, 2255 }, 2256 { 2257 name: "parameters generated condition is used when specified", 2258 appset: v1alpha1.ApplicationSet{ 2259 ObjectMeta: metav1.ObjectMeta{ 2260 Name: "name", 2261 Namespace: "argocd", 2262 }, 2263 Spec: v1alpha1.ApplicationSetSpec{ 2264 Generators: []v1alpha1.ApplicationSetGenerator{ 2265 {List: &v1alpha1.ListGenerator{ 2266 Elements: []apiextensionsv1.JSON{{ 2267 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2268 }}, 2269 }}, 2270 }, 2271 Template: v1alpha1.ApplicationSetTemplate{}, 2272 }, 2273 }, 2274 condition: v1alpha1.ApplicationSetCondition{ 2275 Type: v1alpha1.ApplicationSetConditionParametersGenerated, 2276 Message: "This is a message", 2277 Reason: "test", 2278 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2279 }, 2280 parametersGenerated: true, 2281 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2282 t.Helper() 2283 require.Len(t, conditions, 1) 2284 2285 assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[0].Type) 2286 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[0].Status) 2287 assert.Equal(t, "test", conditions[0].Reason) 2288 }, 2289 }, 2290 { 2291 name: "has parameter conditions when true", 2292 appset: v1alpha1.ApplicationSet{ 2293 ObjectMeta: metav1.ObjectMeta{ 2294 Name: "name", 2295 Namespace: "argocd", 2296 }, 2297 Spec: v1alpha1.ApplicationSetSpec{ 2298 Generators: []v1alpha1.ApplicationSetGenerator{ 2299 {List: &v1alpha1.ListGenerator{ 2300 Elements: []apiextensionsv1.JSON{{ 2301 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2302 }}, 2303 }}, 2304 }, 2305 Template: v1alpha1.ApplicationSetTemplate{}, 2306 }, 2307 }, 2308 condition: v1alpha1.ApplicationSetCondition{ 2309 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2310 Message: "This is a message", 2311 Reason: "test", 2312 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2313 }, 2314 parametersGenerated: true, 2315 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2316 t.Helper() 2317 require.Len(t, conditions, 2) 2318 2319 // Conditions are ordered by type, so the order is deterministic 2320 assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[0].Type) 2321 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusTrue, conditions[0].Status) 2322 2323 assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[1].Type) 2324 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[1].Status) 2325 assert.Equal(t, "test", conditions[1].Reason) 2326 }, 2327 }, 2328 { 2329 name: "resource up to date sets error condition to false", 2330 appset: v1alpha1.ApplicationSet{ 2331 ObjectMeta: metav1.ObjectMeta{ 2332 Name: "name", 2333 Namespace: "argocd", 2334 }, 2335 Spec: v1alpha1.ApplicationSetSpec{ 2336 Generators: []v1alpha1.ApplicationSetGenerator{ 2337 {List: &v1alpha1.ListGenerator{ 2338 Elements: []apiextensionsv1.JSON{{ 2339 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2340 }}, 2341 }}, 2342 }, 2343 Template: v1alpha1.ApplicationSetTemplate{}, 2344 }, 2345 }, 2346 condition: v1alpha1.ApplicationSetCondition{ 2347 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2348 Message: "Completed", 2349 Reason: "test", 2350 Status: v1alpha1.ApplicationSetConditionStatusTrue, 2351 }, 2352 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2353 t.Helper() 2354 require.Len(t, conditions, 3) 2355 2356 assert.Equal(t, v1alpha1.ApplicationSetConditionErrorOccurred, conditions[0].Type) 2357 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[0].Status) 2358 assert.Equal(t, "test", conditions[0].Reason) 2359 assert.Equal(t, "Completed", conditions[0].Message) 2360 2361 assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[1].Type) 2362 2363 assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[2].Type) 2364 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusTrue, conditions[2].Status) 2365 assert.Equal(t, "test", conditions[2].Reason) 2366 assert.Equal(t, "Completed", conditions[2].Message) 2367 }, 2368 }, 2369 { 2370 name: "error condition sets resource up to date to false", 2371 appset: v1alpha1.ApplicationSet{ 2372 ObjectMeta: metav1.ObjectMeta{ 2373 Name: "name", 2374 Namespace: "argocd", 2375 }, 2376 Spec: v1alpha1.ApplicationSetSpec{ 2377 Generators: []v1alpha1.ApplicationSetGenerator{ 2378 {List: &v1alpha1.ListGenerator{ 2379 Elements: []apiextensionsv1.JSON{{ 2380 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2381 }}, 2382 }}, 2383 }, 2384 Template: v1alpha1.ApplicationSetTemplate{}, 2385 }, 2386 }, 2387 condition: v1alpha1.ApplicationSetCondition{ 2388 Type: v1alpha1.ApplicationSetConditionErrorOccurred, 2389 Message: "Error", 2390 Reason: "test", 2391 Status: v1alpha1.ApplicationSetConditionStatusTrue, 2392 }, 2393 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2394 t.Helper() 2395 require.Len(t, conditions, 3) 2396 2397 assert.Equal(t, v1alpha1.ApplicationSetConditionErrorOccurred, conditions[0].Type) 2398 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusTrue, conditions[0].Status) 2399 assert.Equal(t, "test", conditions[0].Reason) 2400 assert.Equal(t, "Error", conditions[0].Message) 2401 2402 assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[1].Type) 2403 2404 assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[2].Type) 2405 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[2].Status) 2406 assert.Equal(t, v1alpha1.ApplicationSetReasonErrorOccurred, conditions[2].Reason) 2407 assert.Equal(t, "Error", conditions[2].Message) 2408 }, 2409 }, 2410 { 2411 name: "updating an unchanged condition does not mutate existing conditions", 2412 appset: v1alpha1.ApplicationSet{ 2413 ObjectMeta: metav1.ObjectMeta{ 2414 Name: "name", 2415 Namespace: "argocd", 2416 }, 2417 Spec: v1alpha1.ApplicationSetSpec{ 2418 Generators: []v1alpha1.ApplicationSetGenerator{ 2419 {List: &v1alpha1.ListGenerator{ 2420 Elements: []apiextensionsv1.JSON{{ 2421 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2422 }}, 2423 }}, 2424 }, 2425 Strategy: &v1alpha1.ApplicationSetStrategy{ 2426 Type: "RollingSync", 2427 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 2428 }, 2429 Template: v1alpha1.ApplicationSetTemplate{}, 2430 }, 2431 Status: v1alpha1.ApplicationSetStatus{ 2432 Conditions: []v1alpha1.ApplicationSetCondition{ 2433 { 2434 Type: v1alpha1.ApplicationSetConditionErrorOccurred, 2435 Message: "existing", 2436 LastTransitionTime: someTime, 2437 }, 2438 existingParameterGeneratedCondition, 2439 { 2440 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2441 Message: "existing", 2442 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2443 LastTransitionTime: someTime, 2444 }, 2445 { 2446 Type: v1alpha1.ApplicationSetConditionRolloutProgressing, 2447 Message: "existing", 2448 LastTransitionTime: someTime, 2449 }, 2450 }, 2451 }, 2452 }, 2453 condition: v1alpha1.ApplicationSetCondition{ 2454 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2455 Message: "existing", 2456 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2457 }, 2458 parametersGenerated: true, 2459 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2460 t.Helper() 2461 require.Len(t, conditions, 4) 2462 2463 assert.Equal(t, v1alpha1.ApplicationSetConditionErrorOccurred, conditions[0].Type) 2464 assert.Equal(t, someTime, conditions[0].LastTransitionTime) 2465 2466 assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[1].Type) 2467 assert.Equal(t, someTime, conditions[1].LastTransitionTime) 2468 2469 assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[2].Type) 2470 assert.Equal(t, someTime, conditions[2].LastTransitionTime) 2471 2472 assert.Equal(t, v1alpha1.ApplicationSetConditionRolloutProgressing, conditions[3].Type) 2473 assert.Equal(t, someTime, conditions[3].LastTransitionTime) 2474 }, 2475 }, 2476 { 2477 name: "progressing conditions is removed when AppSet is not configured", 2478 appset: v1alpha1.ApplicationSet{ 2479 ObjectMeta: metav1.ObjectMeta{ 2480 Name: "name", 2481 Namespace: "argocd", 2482 }, 2483 Spec: v1alpha1.ApplicationSetSpec{ 2484 Generators: []v1alpha1.ApplicationSetGenerator{ 2485 {List: &v1alpha1.ListGenerator{ 2486 Elements: []apiextensionsv1.JSON{{ 2487 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2488 }}, 2489 }}, 2490 }, 2491 // Strategy removed 2492 // Strategy: &v1alpha1.ApplicationSetStrategy{ 2493 // Type: "RollingSync", 2494 // RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 2495 // }, 2496 Template: v1alpha1.ApplicationSetTemplate{}, 2497 }, 2498 Status: v1alpha1.ApplicationSetStatus{ 2499 Conditions: []v1alpha1.ApplicationSetCondition{ 2500 { 2501 Type: v1alpha1.ApplicationSetConditionErrorOccurred, 2502 Message: "existing", 2503 LastTransitionTime: someTime, 2504 }, 2505 existingParameterGeneratedCondition, 2506 { 2507 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2508 Message: "existing", 2509 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2510 LastTransitionTime: someTime, 2511 }, 2512 { 2513 Type: v1alpha1.ApplicationSetConditionRolloutProgressing, 2514 Message: "existing", 2515 LastTransitionTime: someTime, 2516 }, 2517 }, 2518 }, 2519 }, 2520 condition: v1alpha1.ApplicationSetCondition{ 2521 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2522 Message: "existing", 2523 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2524 }, 2525 parametersGenerated: true, 2526 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2527 t.Helper() 2528 require.Len(t, conditions, 3) 2529 for _, c := range conditions { 2530 assert.NotEqual(t, v1alpha1.ApplicationSetConditionRolloutProgressing, c.Type) 2531 } 2532 }, 2533 }, 2534 { 2535 name: "progressing conditions is ignored when AppSet is not configured", 2536 appset: v1alpha1.ApplicationSet{ 2537 ObjectMeta: metav1.ObjectMeta{ 2538 Name: "name", 2539 Namespace: "argocd", 2540 }, 2541 Spec: v1alpha1.ApplicationSetSpec{ 2542 Generators: []v1alpha1.ApplicationSetGenerator{ 2543 {List: &v1alpha1.ListGenerator{ 2544 Elements: []apiextensionsv1.JSON{{ 2545 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2546 }}, 2547 }}, 2548 }, 2549 // Strategy removed 2550 // Strategy: &v1alpha1.ApplicationSetStrategy{ 2551 // Type: "RollingSync", 2552 // RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 2553 // }, 2554 Template: v1alpha1.ApplicationSetTemplate{}, 2555 }, 2556 Status: v1alpha1.ApplicationSetStatus{ 2557 Conditions: []v1alpha1.ApplicationSetCondition{ 2558 { 2559 Type: v1alpha1.ApplicationSetConditionErrorOccurred, 2560 Message: "existing", 2561 LastTransitionTime: someTime, 2562 }, 2563 existingParameterGeneratedCondition, 2564 { 2565 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2566 Message: "existing", 2567 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2568 LastTransitionTime: someTime, 2569 }, 2570 }, 2571 }, 2572 }, 2573 condition: v1alpha1.ApplicationSetCondition{ 2574 Type: v1alpha1.ApplicationSetConditionRolloutProgressing, 2575 Message: "do not add me", 2576 Status: v1alpha1.ApplicationSetConditionStatusTrue, 2577 }, 2578 parametersGenerated: true, 2579 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2580 t.Helper() 2581 require.Len(t, conditions, 3) 2582 for _, c := range conditions { 2583 assert.NotEqual(t, v1alpha1.ApplicationSetConditionRolloutProgressing, c.Type) 2584 } 2585 }, 2586 }, 2587 { 2588 name: "progressing conditions is updated correctly when configured", 2589 appset: v1alpha1.ApplicationSet{ 2590 ObjectMeta: metav1.ObjectMeta{ 2591 Name: "name", 2592 Namespace: "argocd", 2593 }, 2594 Spec: v1alpha1.ApplicationSetSpec{ 2595 Generators: []v1alpha1.ApplicationSetGenerator{ 2596 {List: &v1alpha1.ListGenerator{ 2597 Elements: []apiextensionsv1.JSON{{ 2598 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2599 }}, 2600 }}, 2601 }, 2602 Strategy: &v1alpha1.ApplicationSetStrategy{ 2603 Type: "RollingSync", 2604 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 2605 }, 2606 Template: v1alpha1.ApplicationSetTemplate{}, 2607 }, 2608 Status: v1alpha1.ApplicationSetStatus{ 2609 Conditions: []v1alpha1.ApplicationSetCondition{ 2610 { 2611 Type: v1alpha1.ApplicationSetConditionErrorOccurred, 2612 Message: "existing", 2613 LastTransitionTime: someTime, 2614 }, 2615 existingParameterGeneratedCondition, 2616 { 2617 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2618 Message: "existing", 2619 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2620 LastTransitionTime: someTime, 2621 }, 2622 { 2623 Type: v1alpha1.ApplicationSetConditionRolloutProgressing, 2624 Message: "old value", 2625 Status: v1alpha1.ApplicationSetConditionStatusTrue, 2626 }, 2627 }, 2628 }, 2629 }, 2630 condition: v1alpha1.ApplicationSetCondition{ 2631 Type: v1alpha1.ApplicationSetConditionRolloutProgressing, 2632 Message: "new value", 2633 Status: v1alpha1.ApplicationSetConditionStatusFalse, 2634 }, 2635 parametersGenerated: true, 2636 testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) { 2637 t.Helper() 2638 require.Len(t, conditions, 4) 2639 2640 assert.Equal(t, v1alpha1.ApplicationSetConditionRolloutProgressing, conditions[3].Type) 2641 assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[3].Status) 2642 assert.Equal(t, "new value", conditions[3].Message) 2643 }, 2644 }, 2645 } { 2646 t.Run(c.name, func(t *testing.T) { 2647 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&c.appset).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).WithStatusSubresource(&c.appset).Build() 2648 metrics := appsetmetrics.NewFakeAppsetMetrics() 2649 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 2650 2651 r := ApplicationSetReconciler{ 2652 Client: client, 2653 Scheme: scheme, 2654 Renderer: &utils.Render{}, 2655 Recorder: record.NewFakeRecorder(1), 2656 Generators: map[string]generators.Generator{ 2657 "List": generators.NewListGenerator(), 2658 }, 2659 ArgoDB: argodb, 2660 KubeClientset: kubeclientset, 2661 Metrics: metrics, 2662 } 2663 2664 err = r.setApplicationSetStatusCondition(t.Context(), &c.appset, c.condition, c.parametersGenerated) 2665 require.NoError(t, err) 2666 2667 c.testfunc(t, c.appset.Status.Conditions) 2668 }) 2669 } 2670 } 2671 2672 func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.Application { 2673 t.Helper() 2674 scheme := runtime.NewScheme() 2675 err := v1alpha1.AddToScheme(scheme) 2676 require.NoError(t, err) 2677 2678 defaultProject := v1alpha1.AppProject{ 2679 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, 2680 Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}}, 2681 } 2682 appSet := v1alpha1.ApplicationSet{ 2683 ObjectMeta: metav1.ObjectMeta{ 2684 Name: "name", 2685 Namespace: "argocd", 2686 }, 2687 Spec: v1alpha1.ApplicationSetSpec{ 2688 Generators: []v1alpha1.ApplicationSetGenerator{ 2689 { 2690 List: &v1alpha1.ListGenerator{ 2691 Elements: []apiextensionsv1.JSON{{ 2692 Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`), 2693 }}, 2694 }, 2695 }, 2696 }, 2697 SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{ 2698 ApplicationsSync: &applicationsSyncPolicy, 2699 }, 2700 Template: v1alpha1.ApplicationSetTemplate{ 2701 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2702 Name: "{{cluster}}", 2703 Namespace: "argocd", 2704 }, 2705 Spec: v1alpha1.ApplicationSpec{ 2706 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 2707 Project: "default", 2708 Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"}, 2709 }, 2710 }, 2711 }, 2712 } 2713 2714 secret := &corev1.Secret{ 2715 ObjectMeta: metav1.ObjectMeta{ 2716 Name: "my-cluster", 2717 Namespace: "argocd", 2718 Labels: map[string]string{ 2719 argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, 2720 }, 2721 }, 2722 Data: map[string][]byte{ 2723 // Since this test requires the cluster to be an invalid destination, we 2724 // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) 2725 "name": []byte("good-cluster"), 2726 "server": []byte("https://good-cluster"), 2727 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 2728 }, 2729 } 2730 2731 kubeclientset := getDefaultTestClientSet(secret) 2732 2733 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2734 metrics := appsetmetrics.NewFakeAppsetMetrics() 2735 2736 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 2737 2738 r := ApplicationSetReconciler{ 2739 Client: client, 2740 Scheme: scheme, 2741 Renderer: &utils.Render{}, 2742 Recorder: record.NewFakeRecorder(recordBuffer), 2743 Generators: map[string]generators.Generator{ 2744 "List": generators.NewListGenerator(), 2745 }, 2746 ArgoDB: argodb, 2747 ArgoCDNamespace: "argocd", 2748 KubeClientset: kubeclientset, 2749 Policy: v1alpha1.ApplicationsSyncPolicySync, 2750 EnablePolicyOverride: allowPolicyOverride, 2751 Metrics: metrics, 2752 } 2753 2754 req := ctrl.Request{ 2755 NamespacedName: types.NamespacedName{ 2756 Namespace: "argocd", 2757 Name: "name", 2758 }, 2759 } 2760 2761 // Verify that on validation error, no error is returned, but the object is requeued 2762 resCreate, err := r.Reconcile(t.Context(), req) 2763 require.NoErrorf(t, err, "Reconcile failed with error: %v", err) 2764 assert.Equal(t, time.Duration(0), resCreate.RequeueAfter) 2765 2766 var app v1alpha1.Application 2767 2768 // make sure good app got created 2769 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app) 2770 require.NoError(t, err) 2771 assert.Equal(t, "good-cluster", app.Name) 2772 2773 // Update resource 2774 var retrievedApplicationSet v1alpha1.ApplicationSet 2775 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet) 2776 require.NoError(t, err) 2777 2778 retrievedApplicationSet.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} 2779 retrievedApplicationSet.Spec.Template.Labels = map[string]string{"label-key": "label-value"} 2780 2781 retrievedApplicationSet.Spec.Template.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{ 2782 Values: "global.test: test", 2783 } 2784 2785 err = r.Update(t.Context(), &retrievedApplicationSet) 2786 require.NoError(t, err) 2787 2788 resUpdate, err := r.Reconcile(t.Context(), req) 2789 require.NoError(t, err) 2790 2791 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app) 2792 require.NoError(t, err) 2793 assert.Equal(t, time.Duration(0), resUpdate.RequeueAfter) 2794 assert.Equal(t, "good-cluster", app.Name) 2795 2796 return app 2797 } 2798 2799 func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) { 2800 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2801 2802 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true) 2803 2804 assert.Nil(t, app.Spec.Source.Helm) 2805 assert.Nil(t, app.Annotations) 2806 } 2807 2808 func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) { 2809 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete 2810 2811 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true) 2812 2813 assert.Nil(t, app.Spec.Source.Helm) 2814 assert.Nil(t, app.Annotations) 2815 } 2816 2817 func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) { 2818 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate 2819 2820 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true) 2821 2822 assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values) 2823 assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.Annotations) 2824 assert.Equal(t, map[string]string{"label-key": "label-value"}, app.Labels) 2825 } 2826 2827 func TestUpdatePerformedWithSyncPolicySync(t *testing.T) { 2828 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync 2829 2830 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true) 2831 2832 assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values) 2833 assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.Annotations) 2834 assert.Equal(t, map[string]string{"label-key": "label-value"}, app.Labels) 2835 } 2836 2837 func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) { 2838 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2839 2840 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, false) 2841 2842 assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values) 2843 assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.Annotations) 2844 assert.Equal(t, map[string]string{"label-key": "label-value"}, app.Labels) 2845 } 2846 2847 func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList { 2848 t.Helper() 2849 scheme := runtime.NewScheme() 2850 err := v1alpha1.AddToScheme(scheme) 2851 require.NoError(t, err) 2852 2853 defaultProject := v1alpha1.AppProject{ 2854 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, 2855 Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}}, 2856 } 2857 appSet := v1alpha1.ApplicationSet{ 2858 ObjectMeta: metav1.ObjectMeta{ 2859 Name: "name", 2860 Namespace: "argocd", 2861 }, 2862 Spec: v1alpha1.ApplicationSetSpec{ 2863 Generators: []v1alpha1.ApplicationSetGenerator{ 2864 { 2865 List: &v1alpha1.ListGenerator{ 2866 Elements: []apiextensionsv1.JSON{{ 2867 Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`), 2868 }}, 2869 }, 2870 }, 2871 }, 2872 SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{ 2873 ApplicationsSync: &applicationsSyncPolicy, 2874 }, 2875 Template: v1alpha1.ApplicationSetTemplate{ 2876 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2877 Name: "{{cluster}}", 2878 Namespace: "argocd", 2879 }, 2880 Spec: v1alpha1.ApplicationSpec{ 2881 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 2882 Project: "default", 2883 Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"}, 2884 }, 2885 }, 2886 }, 2887 } 2888 2889 secret := &corev1.Secret{ 2890 ObjectMeta: metav1.ObjectMeta{ 2891 Name: "my-cluster", 2892 Namespace: "argocd", 2893 Labels: map[string]string{ 2894 argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, 2895 }, 2896 }, 2897 Data: map[string][]byte{ 2898 // Since this test requires the cluster to be an invalid destination, we 2899 // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) 2900 "name": []byte("good-cluster"), 2901 "server": []byte("https://good-cluster"), 2902 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 2903 }, 2904 } 2905 2906 kubeclientset := getDefaultTestClientSet(secret) 2907 2908 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2909 metrics := appsetmetrics.NewFakeAppsetMetrics() 2910 2911 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 2912 2913 r := ApplicationSetReconciler{ 2914 Client: client, 2915 Scheme: scheme, 2916 Renderer: &utils.Render{}, 2917 Recorder: record.NewFakeRecorder(recordBuffer), 2918 Generators: map[string]generators.Generator{ 2919 "List": generators.NewListGenerator(), 2920 }, 2921 ArgoDB: argodb, 2922 ArgoCDNamespace: "argocd", 2923 KubeClientset: kubeclientset, 2924 Policy: v1alpha1.ApplicationsSyncPolicySync, 2925 EnablePolicyOverride: allowPolicyOverride, 2926 Metrics: metrics, 2927 } 2928 2929 req := ctrl.Request{ 2930 NamespacedName: types.NamespacedName{ 2931 Namespace: "argocd", 2932 Name: "name", 2933 }, 2934 } 2935 2936 // Verify that on validation error, no error is returned, but the object is requeued 2937 resCreate, err := r.Reconcile(t.Context(), req) 2938 require.NoError(t, err) 2939 assert.Equal(t, time.Duration(0), resCreate.RequeueAfter) 2940 2941 var app v1alpha1.Application 2942 2943 // make sure good app got created 2944 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app) 2945 require.NoError(t, err) 2946 assert.Equal(t, "good-cluster", app.Name) 2947 2948 // Update resource 2949 var retrievedApplicationSet v1alpha1.ApplicationSet 2950 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet) 2951 require.NoError(t, err) 2952 retrievedApplicationSet.Spec.Generators = []v1alpha1.ApplicationSetGenerator{ 2953 { 2954 List: &v1alpha1.ListGenerator{ 2955 Elements: []apiextensionsv1.JSON{}, 2956 }, 2957 }, 2958 } 2959 2960 err = r.Update(t.Context(), &retrievedApplicationSet) 2961 require.NoError(t, err) 2962 2963 resUpdate, err := r.Reconcile(t.Context(), req) 2964 require.NoError(t, err) 2965 2966 var apps v1alpha1.ApplicationList 2967 2968 err = r.List(t.Context(), &apps) 2969 require.NoError(t, err) 2970 assert.Equal(t, time.Duration(0), resUpdate.RequeueAfter) 2971 2972 return apps 2973 } 2974 2975 func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) { 2976 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2977 2978 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 1, true) 2979 2980 assert.Equal(t, "good-cluster", apps.Items[0].Name) 2981 } 2982 2983 func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) { 2984 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate 2985 2986 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 2, true) 2987 2988 assert.Equal(t, "good-cluster", apps.Items[0].Name) 2989 } 2990 2991 func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) { 2992 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete 2993 2994 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true) 2995 2996 assert.NotNil(t, apps.Items[0].DeletionTimestamp) 2997 } 2998 2999 func TestDeletePerformedWithSyncPolicySync(t *testing.T) { 3000 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync 3001 3002 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true) 3003 3004 assert.NotNil(t, apps.Items[0].DeletionTimestamp) 3005 } 3006 3007 func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) { 3008 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 3009 3010 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false) 3011 3012 assert.NotNil(t, apps.Items[0].DeletionTimestamp) 3013 } 3014 3015 func TestPolicies(t *testing.T) { 3016 scheme := runtime.NewScheme() 3017 err := v1alpha1.AddToScheme(scheme) 3018 require.NoError(t, err) 3019 3020 defaultProject := v1alpha1.AppProject{ 3021 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, 3022 Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://kubernetes.default.svc"}}}, 3023 } 3024 3025 kubeclientset := getDefaultTestClientSet() 3026 3027 for _, c := range []struct { 3028 name string 3029 policyName string 3030 allowedUpdate bool 3031 allowedDelete bool 3032 }{ 3033 { 3034 name: "Apps are allowed to update and delete", 3035 policyName: "sync", 3036 allowedUpdate: true, 3037 allowedDelete: true, 3038 }, 3039 { 3040 name: "Apps are not allowed to update and delete", 3041 policyName: "create-only", 3042 allowedUpdate: false, 3043 allowedDelete: false, 3044 }, 3045 { 3046 name: "Apps are allowed to update, not allowed to delete", 3047 policyName: "create-update", 3048 allowedUpdate: true, 3049 allowedDelete: false, 3050 }, 3051 { 3052 name: "Apps are allowed to delete, not allowed to update", 3053 policyName: "create-delete", 3054 allowedUpdate: false, 3055 allowedDelete: true, 3056 }, 3057 } { 3058 t.Run(c.name, func(t *testing.T) { 3059 policy := utils.Policies[c.policyName] 3060 assert.NotNil(t, policy) 3061 3062 appSet := v1alpha1.ApplicationSet{ 3063 ObjectMeta: metav1.ObjectMeta{ 3064 Name: "name", 3065 Namespace: "argocd", 3066 }, 3067 Spec: v1alpha1.ApplicationSetSpec{ 3068 GoTemplate: true, 3069 Generators: []v1alpha1.ApplicationSetGenerator{ 3070 { 3071 List: &v1alpha1.ListGenerator{ 3072 Elements: []apiextensionsv1.JSON{ 3073 { 3074 Raw: []byte(`{"name": "my-app"}`), 3075 }, 3076 }, 3077 }, 3078 }, 3079 }, 3080 Template: v1alpha1.ApplicationSetTemplate{ 3081 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 3082 Name: "{{.name}}", 3083 Namespace: "argocd", 3084 Annotations: map[string]string{ 3085 "key": "value", 3086 }, 3087 }, 3088 Spec: v1alpha1.ApplicationSpec{ 3089 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 3090 Project: "default", 3091 Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"}, 3092 }, 3093 }, 3094 }, 3095 } 3096 3097 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 3098 metrics := appsetmetrics.NewFakeAppsetMetrics() 3099 3100 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 3101 3102 r := ApplicationSetReconciler{ 3103 Client: client, 3104 Scheme: scheme, 3105 Renderer: &utils.Render{}, 3106 Recorder: record.NewFakeRecorder(10), 3107 Generators: map[string]generators.Generator{ 3108 "List": generators.NewListGenerator(), 3109 }, 3110 ArgoDB: argodb, 3111 ArgoCDNamespace: "argocd", 3112 KubeClientset: kubeclientset, 3113 Policy: policy, 3114 Metrics: metrics, 3115 } 3116 3117 req := ctrl.Request{ 3118 NamespacedName: types.NamespacedName{ 3119 Namespace: "argocd", 3120 Name: "name", 3121 }, 3122 } 3123 3124 // Check if Application is created 3125 res, err := r.Reconcile(t.Context(), req) 3126 require.NoError(t, err) 3127 assert.Equal(t, time.Duration(0), res.RequeueAfter) 3128 3129 var app v1alpha1.Application 3130 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app) 3131 require.NoError(t, err) 3132 assert.Equal(t, "value", app.Annotations["key"]) 3133 3134 // Check if Application is updated 3135 app.Annotations["key"] = "edited" 3136 err = r.Update(t.Context(), &app) 3137 require.NoError(t, err) 3138 3139 res, err = r.Reconcile(t.Context(), req) 3140 require.NoError(t, err) 3141 assert.Equal(t, time.Duration(0), res.RequeueAfter) 3142 3143 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app) 3144 require.NoError(t, err) 3145 3146 if c.allowedUpdate { 3147 assert.Equal(t, "value", app.Annotations["key"]) 3148 } else { 3149 assert.Equal(t, "edited", app.Annotations["key"]) 3150 } 3151 3152 // Check if Application is deleted 3153 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &appSet) 3154 require.NoError(t, err) 3155 appSet.Spec.Generators[0] = v1alpha1.ApplicationSetGenerator{ 3156 List: &v1alpha1.ListGenerator{ 3157 Elements: []apiextensionsv1.JSON{}, 3158 }, 3159 } 3160 err = r.Update(t.Context(), &appSet) 3161 require.NoError(t, err) 3162 3163 res, err = r.Reconcile(t.Context(), req) 3164 require.NoError(t, err) 3165 assert.Equal(t, time.Duration(0), res.RequeueAfter) 3166 3167 err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app) 3168 require.NoError(t, err) 3169 if c.allowedDelete { 3170 assert.NotNil(t, app.DeletionTimestamp) 3171 } else { 3172 assert.Nil(t, app.DeletionTimestamp) 3173 } 3174 }) 3175 } 3176 } 3177 3178 func TestSetApplicationSetApplicationStatus(t *testing.T) { 3179 scheme := runtime.NewScheme() 3180 err := v1alpha1.AddToScheme(scheme) 3181 require.NoError(t, err) 3182 3183 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 3184 3185 for _, cc := range []struct { 3186 name string 3187 appSet v1alpha1.ApplicationSet 3188 appStatuses []v1alpha1.ApplicationSetApplicationStatus 3189 expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus 3190 }{ 3191 { 3192 name: "sets a single appstatus", 3193 appSet: v1alpha1.ApplicationSet{ 3194 ObjectMeta: metav1.ObjectMeta{ 3195 Name: "name", 3196 Namespace: "argocd", 3197 }, 3198 Spec: v1alpha1.ApplicationSetSpec{ 3199 Generators: []v1alpha1.ApplicationSetGenerator{ 3200 {List: &v1alpha1.ListGenerator{ 3201 Elements: []apiextensionsv1.JSON{{ 3202 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 3203 }}, 3204 }}, 3205 }, 3206 Template: v1alpha1.ApplicationSetTemplate{}, 3207 }, 3208 }, 3209 appStatuses: []v1alpha1.ApplicationSetApplicationStatus{ 3210 { 3211 Application: "app1", 3212 Message: "testing SetApplicationSetApplicationStatus to Healthy", 3213 Status: "Healthy", 3214 }, 3215 }, 3216 expectedAppStatuses: []v1alpha1.ApplicationSetApplicationStatus{ 3217 { 3218 Application: "app1", 3219 Message: "testing SetApplicationSetApplicationStatus to Healthy", 3220 Status: "Healthy", 3221 }, 3222 }, 3223 }, 3224 { 3225 name: "removes an appstatus", 3226 appSet: v1alpha1.ApplicationSet{ 3227 ObjectMeta: metav1.ObjectMeta{ 3228 Name: "name", 3229 Namespace: "argocd", 3230 }, 3231 Spec: v1alpha1.ApplicationSetSpec{ 3232 Generators: []v1alpha1.ApplicationSetGenerator{ 3233 {List: &v1alpha1.ListGenerator{ 3234 Elements: []apiextensionsv1.JSON{{ 3235 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 3236 }}, 3237 }}, 3238 }, 3239 Template: v1alpha1.ApplicationSetTemplate{}, 3240 }, 3241 Status: v1alpha1.ApplicationSetStatus{ 3242 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 3243 { 3244 Application: "app1", 3245 Message: "testing SetApplicationSetApplicationStatus to Healthy", 3246 Status: "Healthy", 3247 }, 3248 }, 3249 }, 3250 }, 3251 appStatuses: []v1alpha1.ApplicationSetApplicationStatus{}, 3252 expectedAppStatuses: nil, 3253 }, 3254 } { 3255 t.Run(cc.name, func(t *testing.T) { 3256 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build() 3257 metrics := appsetmetrics.NewFakeAppsetMetrics() 3258 3259 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 3260 3261 r := ApplicationSetReconciler{ 3262 Client: client, 3263 Scheme: scheme, 3264 Renderer: &utils.Render{}, 3265 Recorder: record.NewFakeRecorder(1), 3266 Generators: map[string]generators.Generator{ 3267 "List": generators.NewListGenerator(), 3268 }, 3269 ArgoDB: argodb, 3270 KubeClientset: kubeclientset, 3271 Metrics: metrics, 3272 } 3273 3274 err = r.setAppSetApplicationStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appStatuses) 3275 require.NoError(t, err) 3276 3277 assert.Equal(t, cc.expectedAppStatuses, cc.appSet.Status.ApplicationStatus) 3278 }) 3279 } 3280 } 3281 3282 func TestBuildAppDependencyList(t *testing.T) { 3283 scheme := runtime.NewScheme() 3284 err := v1alpha1.AddToScheme(scheme) 3285 require.NoError(t, err) 3286 3287 client := fake.NewClientBuilder().WithScheme(scheme).Build() 3288 metrics := appsetmetrics.NewFakeAppsetMetrics() 3289 3290 for _, cc := range []struct { 3291 name string 3292 appSet v1alpha1.ApplicationSet 3293 apps []v1alpha1.Application 3294 expectedList [][]string 3295 expectedStepMap map[string]int 3296 }{ 3297 { 3298 name: "handles an empty set of applications and no strategy", 3299 appSet: v1alpha1.ApplicationSet{ 3300 ObjectMeta: metav1.ObjectMeta{ 3301 Name: "name", 3302 Namespace: "argocd", 3303 }, 3304 Spec: v1alpha1.ApplicationSetSpec{}, 3305 }, 3306 apps: []v1alpha1.Application{}, 3307 expectedList: [][]string{}, 3308 expectedStepMap: map[string]int{}, 3309 }, 3310 { 3311 name: "handles an empty set of applications and ignores AllAtOnce strategy", 3312 appSet: v1alpha1.ApplicationSet{ 3313 ObjectMeta: metav1.ObjectMeta{ 3314 Name: "name", 3315 Namespace: "argocd", 3316 }, 3317 Spec: v1alpha1.ApplicationSetSpec{ 3318 Strategy: &v1alpha1.ApplicationSetStrategy{ 3319 Type: "AllAtOnce", 3320 }, 3321 }, 3322 }, 3323 apps: []v1alpha1.Application{}, 3324 expectedList: [][]string{}, 3325 expectedStepMap: map[string]int{}, 3326 }, 3327 { 3328 name: "handles an empty set of applications with good 'In' selectors", 3329 appSet: v1alpha1.ApplicationSet{ 3330 ObjectMeta: metav1.ObjectMeta{ 3331 Name: "name", 3332 Namespace: "argocd", 3333 }, 3334 Spec: v1alpha1.ApplicationSetSpec{ 3335 Strategy: &v1alpha1.ApplicationSetStrategy{ 3336 Type: "RollingSync", 3337 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3338 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3339 { 3340 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3341 { 3342 Key: "env", 3343 Operator: "In", 3344 Values: []string{ 3345 "dev", 3346 }, 3347 }, 3348 }, 3349 }, 3350 }, 3351 }, 3352 }, 3353 }, 3354 }, 3355 apps: []v1alpha1.Application{}, 3356 expectedList: [][]string{ 3357 {}, 3358 }, 3359 expectedStepMap: map[string]int{}, 3360 }, 3361 { 3362 name: "handles selecting 1 application with 1 'In' selector", 3363 appSet: v1alpha1.ApplicationSet{ 3364 ObjectMeta: metav1.ObjectMeta{ 3365 Name: "name", 3366 Namespace: "argocd", 3367 }, 3368 Spec: v1alpha1.ApplicationSetSpec{ 3369 Strategy: &v1alpha1.ApplicationSetStrategy{ 3370 Type: "RollingSync", 3371 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3372 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3373 { 3374 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3375 { 3376 Key: "env", 3377 Operator: "In", 3378 Values: []string{ 3379 "dev", 3380 }, 3381 }, 3382 }, 3383 }, 3384 }, 3385 }, 3386 }, 3387 }, 3388 }, 3389 apps: []v1alpha1.Application{ 3390 { 3391 ObjectMeta: metav1.ObjectMeta{ 3392 Name: "app-dev", 3393 Labels: map[string]string{ 3394 "env": "dev", 3395 }, 3396 }, 3397 }, 3398 }, 3399 expectedList: [][]string{ 3400 {"app-dev"}, 3401 }, 3402 expectedStepMap: map[string]int{ 3403 "app-dev": 0, 3404 }, 3405 }, 3406 { 3407 name: "handles 'In' selectors that select no applications", 3408 appSet: v1alpha1.ApplicationSet{ 3409 ObjectMeta: metav1.ObjectMeta{ 3410 Name: "name", 3411 Namespace: "argocd", 3412 }, 3413 Spec: v1alpha1.ApplicationSetSpec{ 3414 Strategy: &v1alpha1.ApplicationSetStrategy{ 3415 Type: "RollingSync", 3416 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3417 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3418 { 3419 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3420 { 3421 Key: "env", 3422 Operator: "In", 3423 Values: []string{ 3424 "dev", 3425 }, 3426 }, 3427 }, 3428 }, 3429 { 3430 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3431 { 3432 Key: "env", 3433 Operator: "In", 3434 Values: []string{ 3435 "qa", 3436 }, 3437 }, 3438 }, 3439 }, 3440 { 3441 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3442 { 3443 Key: "env", 3444 Operator: "In", 3445 Values: []string{ 3446 "prod", 3447 }, 3448 }, 3449 }, 3450 }, 3451 }, 3452 }, 3453 }, 3454 }, 3455 }, 3456 apps: []v1alpha1.Application{ 3457 { 3458 ObjectMeta: metav1.ObjectMeta{ 3459 Name: "app-qa", 3460 Labels: map[string]string{ 3461 "env": "qa", 3462 }, 3463 }, 3464 }, 3465 { 3466 ObjectMeta: metav1.ObjectMeta{ 3467 Name: "app-prod", 3468 Labels: map[string]string{ 3469 "env": "prod", 3470 }, 3471 }, 3472 }, 3473 }, 3474 expectedList: [][]string{ 3475 {}, 3476 {"app-qa"}, 3477 {"app-prod"}, 3478 }, 3479 expectedStepMap: map[string]int{ 3480 "app-qa": 1, 3481 "app-prod": 2, 3482 }, 3483 }, 3484 { 3485 name: "multiple 'In' selectors in the same matchExpression only select Applications that match all selectors", 3486 appSet: v1alpha1.ApplicationSet{ 3487 ObjectMeta: metav1.ObjectMeta{ 3488 Name: "name", 3489 Namespace: "argocd", 3490 }, 3491 Spec: v1alpha1.ApplicationSetSpec{ 3492 Strategy: &v1alpha1.ApplicationSetStrategy{ 3493 Type: "RollingSync", 3494 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3495 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3496 { 3497 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3498 { 3499 Key: "region", 3500 Operator: "In", 3501 Values: []string{ 3502 "us-east-2", 3503 }, 3504 }, 3505 { 3506 Key: "env", 3507 Operator: "In", 3508 Values: []string{ 3509 "qa", 3510 }, 3511 }, 3512 }, 3513 }, 3514 }, 3515 }, 3516 }, 3517 }, 3518 }, 3519 apps: []v1alpha1.Application{ 3520 { 3521 ObjectMeta: metav1.ObjectMeta{ 3522 Name: "app-qa1", 3523 Labels: map[string]string{ 3524 "env": "qa", 3525 }, 3526 }, 3527 }, 3528 { 3529 ObjectMeta: metav1.ObjectMeta{ 3530 Name: "app-qa2", 3531 Labels: map[string]string{ 3532 "env": "qa", 3533 "region": "us-east-2", 3534 }, 3535 }, 3536 }, 3537 }, 3538 expectedList: [][]string{ 3539 {"app-qa2"}, 3540 }, 3541 expectedStepMap: map[string]int{ 3542 "app-qa2": 0, 3543 }, 3544 }, 3545 { 3546 name: "multiple values in the same 'In' matchExpression can match on any value", 3547 appSet: v1alpha1.ApplicationSet{ 3548 ObjectMeta: metav1.ObjectMeta{ 3549 Name: "name", 3550 Namespace: "argocd", 3551 }, 3552 Spec: v1alpha1.ApplicationSetSpec{ 3553 Strategy: &v1alpha1.ApplicationSetStrategy{ 3554 Type: "RollingSync", 3555 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3556 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3557 { 3558 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3559 { 3560 Key: "env", 3561 Operator: "In", 3562 Values: []string{ 3563 "qa", 3564 "prod", 3565 }, 3566 }, 3567 }, 3568 }, 3569 }, 3570 }, 3571 }, 3572 }, 3573 }, 3574 apps: []v1alpha1.Application{ 3575 { 3576 ObjectMeta: metav1.ObjectMeta{ 3577 Name: "app-dev", 3578 Labels: map[string]string{ 3579 "env": "dev", 3580 }, 3581 }, 3582 }, 3583 { 3584 ObjectMeta: metav1.ObjectMeta{ 3585 Name: "app-qa", 3586 Labels: map[string]string{ 3587 "env": "qa", 3588 }, 3589 }, 3590 }, 3591 { 3592 ObjectMeta: metav1.ObjectMeta{ 3593 Name: "app-prod", 3594 Labels: map[string]string{ 3595 "env": "prod", 3596 "region": "us-east-2", 3597 }, 3598 }, 3599 }, 3600 }, 3601 expectedList: [][]string{ 3602 {"app-qa", "app-prod"}, 3603 }, 3604 expectedStepMap: map[string]int{ 3605 "app-qa": 0, 3606 "app-prod": 0, 3607 }, 3608 }, 3609 { 3610 name: "handles an empty set of applications with good 'NotIn' selectors", 3611 appSet: v1alpha1.ApplicationSet{ 3612 ObjectMeta: metav1.ObjectMeta{ 3613 Name: "name", 3614 Namespace: "argocd", 3615 }, 3616 Spec: v1alpha1.ApplicationSetSpec{ 3617 Strategy: &v1alpha1.ApplicationSetStrategy{ 3618 Type: "RollingSync", 3619 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3620 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3621 { 3622 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3623 { 3624 Key: "env", 3625 Operator: "In", 3626 Values: []string{ 3627 "dev", 3628 }, 3629 }, 3630 }, 3631 }, 3632 }, 3633 }, 3634 }, 3635 }, 3636 }, 3637 apps: []v1alpha1.Application{}, 3638 expectedList: [][]string{ 3639 {}, 3640 }, 3641 expectedStepMap: map[string]int{}, 3642 }, 3643 { 3644 name: "selects 1 application with 1 'NotIn' selector", 3645 appSet: v1alpha1.ApplicationSet{ 3646 ObjectMeta: metav1.ObjectMeta{ 3647 Name: "name", 3648 Namespace: "argocd", 3649 }, 3650 Spec: v1alpha1.ApplicationSetSpec{ 3651 Strategy: &v1alpha1.ApplicationSetStrategy{ 3652 Type: "RollingSync", 3653 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3654 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3655 { 3656 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3657 { 3658 Key: "env", 3659 Operator: "NotIn", 3660 Values: []string{ 3661 "qa", 3662 }, 3663 }, 3664 }, 3665 }, 3666 }, 3667 }, 3668 }, 3669 }, 3670 }, 3671 apps: []v1alpha1.Application{ 3672 { 3673 ObjectMeta: metav1.ObjectMeta{ 3674 Name: "app-dev", 3675 Labels: map[string]string{ 3676 "env": "dev", 3677 }, 3678 }, 3679 }, 3680 }, 3681 expectedList: [][]string{ 3682 {"app-dev"}, 3683 }, 3684 expectedStepMap: map[string]int{ 3685 "app-dev": 0, 3686 }, 3687 }, 3688 { 3689 name: "'NotIn' selectors that select no applications", 3690 appSet: v1alpha1.ApplicationSet{ 3691 ObjectMeta: metav1.ObjectMeta{ 3692 Name: "name", 3693 Namespace: "argocd", 3694 }, 3695 Spec: v1alpha1.ApplicationSetSpec{ 3696 Strategy: &v1alpha1.ApplicationSetStrategy{ 3697 Type: "RollingSync", 3698 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3699 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3700 { 3701 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3702 { 3703 Key: "env", 3704 Operator: "NotIn", 3705 Values: []string{ 3706 "dev", 3707 }, 3708 }, 3709 }, 3710 }, 3711 }, 3712 }, 3713 }, 3714 }, 3715 }, 3716 apps: []v1alpha1.Application{ 3717 { 3718 ObjectMeta: metav1.ObjectMeta{ 3719 Name: "app-qa", 3720 Labels: map[string]string{ 3721 "env": "qa", 3722 }, 3723 }, 3724 }, 3725 { 3726 ObjectMeta: metav1.ObjectMeta{ 3727 Name: "app-prod", 3728 Labels: map[string]string{ 3729 "env": "prod", 3730 }, 3731 }, 3732 }, 3733 }, 3734 expectedList: [][]string{ 3735 {"app-qa", "app-prod"}, 3736 }, 3737 expectedStepMap: map[string]int{ 3738 "app-qa": 0, 3739 "app-prod": 0, 3740 }, 3741 }, 3742 { 3743 name: "multiple 'NotIn' selectors remove Applications with mising labels on any match", 3744 appSet: v1alpha1.ApplicationSet{ 3745 ObjectMeta: metav1.ObjectMeta{ 3746 Name: "name", 3747 Namespace: "argocd", 3748 }, 3749 Spec: v1alpha1.ApplicationSetSpec{ 3750 Strategy: &v1alpha1.ApplicationSetStrategy{ 3751 Type: "RollingSync", 3752 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3753 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3754 { 3755 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3756 { 3757 Key: "region", 3758 Operator: "NotIn", 3759 Values: []string{ 3760 "us-east-2", 3761 }, 3762 }, 3763 { 3764 Key: "env", 3765 Operator: "NotIn", 3766 Values: []string{ 3767 "qa", 3768 }, 3769 }, 3770 }, 3771 }, 3772 }, 3773 }, 3774 }, 3775 }, 3776 }, 3777 apps: []v1alpha1.Application{ 3778 { 3779 ObjectMeta: metav1.ObjectMeta{ 3780 Name: "app-qa1", 3781 Labels: map[string]string{ 3782 "env": "qa", 3783 }, 3784 }, 3785 }, 3786 { 3787 ObjectMeta: metav1.ObjectMeta{ 3788 Name: "app-qa2", 3789 Labels: map[string]string{ 3790 "env": "qa", 3791 "region": "us-east-2", 3792 }, 3793 }, 3794 }, 3795 }, 3796 expectedList: [][]string{ 3797 {}, 3798 }, 3799 expectedStepMap: map[string]int{}, 3800 }, 3801 { 3802 name: "multiple 'NotIn' selectors filter all matching Applications", 3803 appSet: v1alpha1.ApplicationSet{ 3804 ObjectMeta: metav1.ObjectMeta{ 3805 Name: "name", 3806 Namespace: "argocd", 3807 }, 3808 Spec: v1alpha1.ApplicationSetSpec{ 3809 Strategy: &v1alpha1.ApplicationSetStrategy{ 3810 Type: "RollingSync", 3811 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3812 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3813 { 3814 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3815 { 3816 Key: "region", 3817 Operator: "NotIn", 3818 Values: []string{ 3819 "us-east-2", 3820 }, 3821 }, 3822 { 3823 Key: "env", 3824 Operator: "NotIn", 3825 Values: []string{ 3826 "qa", 3827 }, 3828 }, 3829 }, 3830 }, 3831 }, 3832 }, 3833 }, 3834 }, 3835 }, 3836 apps: []v1alpha1.Application{ 3837 { 3838 ObjectMeta: metav1.ObjectMeta{ 3839 Name: "app-qa1", 3840 Labels: map[string]string{ 3841 "env": "qa", 3842 "region": "us-east-1", 3843 }, 3844 }, 3845 }, 3846 { 3847 ObjectMeta: metav1.ObjectMeta{ 3848 Name: "app-qa2", 3849 Labels: map[string]string{ 3850 "env": "qa", 3851 "region": "us-east-2", 3852 }, 3853 }, 3854 }, 3855 { 3856 ObjectMeta: metav1.ObjectMeta{ 3857 Name: "app-prod1", 3858 Labels: map[string]string{ 3859 "env": "prod", 3860 "region": "us-east-1", 3861 }, 3862 }, 3863 }, 3864 { 3865 ObjectMeta: metav1.ObjectMeta{ 3866 Name: "app-prod2", 3867 Labels: map[string]string{ 3868 "env": "prod", 3869 "region": "us-east-2", 3870 }, 3871 }, 3872 }, 3873 }, 3874 expectedList: [][]string{ 3875 {"app-prod1"}, 3876 }, 3877 expectedStepMap: map[string]int{ 3878 "app-prod1": 0, 3879 }, 3880 }, 3881 { 3882 name: "multiple values in the same 'NotIn' matchExpression exclude a match from any value", 3883 appSet: v1alpha1.ApplicationSet{ 3884 ObjectMeta: metav1.ObjectMeta{ 3885 Name: "name", 3886 Namespace: "argocd", 3887 }, 3888 Spec: v1alpha1.ApplicationSetSpec{ 3889 Strategy: &v1alpha1.ApplicationSetStrategy{ 3890 Type: "RollingSync", 3891 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3892 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3893 { 3894 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3895 { 3896 Key: "env", 3897 Operator: "NotIn", 3898 Values: []string{ 3899 "qa", 3900 "prod", 3901 }, 3902 }, 3903 }, 3904 }, 3905 }, 3906 }, 3907 }, 3908 }, 3909 }, 3910 apps: []v1alpha1.Application{ 3911 { 3912 ObjectMeta: metav1.ObjectMeta{ 3913 Name: "app-dev", 3914 Labels: map[string]string{ 3915 "env": "dev", 3916 }, 3917 }, 3918 }, 3919 { 3920 ObjectMeta: metav1.ObjectMeta{ 3921 Name: "app-qa", 3922 Labels: map[string]string{ 3923 "env": "qa", 3924 }, 3925 }, 3926 }, 3927 { 3928 ObjectMeta: metav1.ObjectMeta{ 3929 Name: "app-prod", 3930 Labels: map[string]string{ 3931 "env": "prod", 3932 "region": "us-east-2", 3933 }, 3934 }, 3935 }, 3936 }, 3937 expectedList: [][]string{ 3938 {"app-dev"}, 3939 }, 3940 expectedStepMap: map[string]int{ 3941 "app-dev": 0, 3942 }, 3943 }, 3944 { 3945 name: "in a mix of 'In' and 'NotIn' selectors, 'NotIn' takes precedence", 3946 appSet: v1alpha1.ApplicationSet{ 3947 ObjectMeta: metav1.ObjectMeta{ 3948 Name: "name", 3949 Namespace: "argocd", 3950 }, 3951 Spec: v1alpha1.ApplicationSetSpec{ 3952 Strategy: &v1alpha1.ApplicationSetStrategy{ 3953 Type: "RollingSync", 3954 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3955 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3956 { 3957 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3958 { 3959 Key: "env", 3960 Operator: "In", 3961 Values: []string{ 3962 "qa", 3963 "prod", 3964 }, 3965 }, 3966 { 3967 Key: "region", 3968 Operator: "NotIn", 3969 Values: []string{ 3970 "us-west-2", 3971 }, 3972 }, 3973 }, 3974 }, 3975 }, 3976 }, 3977 }, 3978 }, 3979 }, 3980 apps: []v1alpha1.Application{ 3981 { 3982 ObjectMeta: metav1.ObjectMeta{ 3983 Name: "app-dev", 3984 Labels: map[string]string{ 3985 "env": "dev", 3986 }, 3987 }, 3988 }, 3989 { 3990 ObjectMeta: metav1.ObjectMeta{ 3991 Name: "app-qa1", 3992 Labels: map[string]string{ 3993 "env": "qa", 3994 "region": "us-west-2", 3995 }, 3996 }, 3997 }, 3998 { 3999 ObjectMeta: metav1.ObjectMeta{ 4000 Name: "app-qa2", 4001 Labels: map[string]string{ 4002 "env": "qa", 4003 "region": "us-east-2", 4004 }, 4005 }, 4006 }, 4007 }, 4008 expectedList: [][]string{ 4009 {"app-qa2"}, 4010 }, 4011 expectedStepMap: map[string]int{ 4012 "app-qa2": 0, 4013 }, 4014 }, 4015 } { 4016 t.Run(cc.name, func(t *testing.T) { 4017 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 4018 4019 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 4020 4021 r := ApplicationSetReconciler{ 4022 Client: client, 4023 Scheme: scheme, 4024 Recorder: record.NewFakeRecorder(1), 4025 Generators: map[string]generators.Generator{}, 4026 ArgoDB: argodb, 4027 KubeClientset: kubeclientset, 4028 Metrics: metrics, 4029 } 4030 4031 appDependencyList, appStepMap := r.buildAppDependencyList(log.NewEntry(log.StandardLogger()), cc.appSet, cc.apps) 4032 assert.Equal(t, cc.expectedList, appDependencyList, "expected appDependencyList did not match actual") 4033 assert.Equal(t, cc.expectedStepMap, appStepMap, "expected appStepMap did not match actual") 4034 }) 4035 } 4036 } 4037 4038 func TestBuildAppSyncMap(t *testing.T) { 4039 scheme := runtime.NewScheme() 4040 err := v1alpha1.AddToScheme(scheme) 4041 require.NoError(t, err) 4042 4043 client := fake.NewClientBuilder().WithScheme(scheme).Build() 4044 metrics := appsetmetrics.NewFakeAppsetMetrics() 4045 4046 for _, cc := range []struct { 4047 name string 4048 appSet v1alpha1.ApplicationSet 4049 appMap map[string]v1alpha1.Application 4050 appDependencyList [][]string 4051 expectedMap map[string]bool 4052 }{ 4053 { 4054 name: "handles an empty app dependency list", 4055 appSet: v1alpha1.ApplicationSet{ 4056 ObjectMeta: metav1.ObjectMeta{ 4057 Name: "name", 4058 Namespace: "argocd", 4059 }, 4060 Spec: v1alpha1.ApplicationSetSpec{ 4061 Strategy: &v1alpha1.ApplicationSetStrategy{ 4062 Type: "RollingSync", 4063 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4064 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4065 { 4066 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4067 }, 4068 { 4069 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4070 }, 4071 }, 4072 }, 4073 }, 4074 }, 4075 }, 4076 appDependencyList: [][]string{}, 4077 expectedMap: map[string]bool{}, 4078 }, 4079 { 4080 name: "handles two applications with no statuses", 4081 appSet: v1alpha1.ApplicationSet{ 4082 ObjectMeta: metav1.ObjectMeta{ 4083 Name: "name", 4084 Namespace: "argocd", 4085 }, 4086 Spec: v1alpha1.ApplicationSetSpec{ 4087 Strategy: &v1alpha1.ApplicationSetStrategy{ 4088 Type: "RollingSync", 4089 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4090 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4091 { 4092 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4093 }, 4094 { 4095 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4096 }, 4097 }, 4098 }, 4099 }, 4100 }, 4101 }, 4102 appDependencyList: [][]string{ 4103 {"app1"}, 4104 {"app2"}, 4105 }, 4106 expectedMap: map[string]bool{ 4107 "app1": true, 4108 "app2": false, 4109 }, 4110 }, 4111 { 4112 name: "handles applications after an empty selection", 4113 appSet: v1alpha1.ApplicationSet{ 4114 ObjectMeta: metav1.ObjectMeta{ 4115 Name: "name", 4116 Namespace: "argocd", 4117 }, 4118 Spec: v1alpha1.ApplicationSetSpec{ 4119 Strategy: &v1alpha1.ApplicationSetStrategy{ 4120 Type: "RollingSync", 4121 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4122 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4123 { 4124 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4125 }, 4126 { 4127 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4128 }, 4129 }, 4130 }, 4131 }, 4132 }, 4133 }, 4134 appDependencyList: [][]string{ 4135 {}, 4136 {"app1", "app2"}, 4137 }, 4138 expectedMap: map[string]bool{ 4139 "app1": true, 4140 "app2": true, 4141 }, 4142 }, 4143 { 4144 name: "handles RollingSync applications that are healthy and have no changes", 4145 appSet: v1alpha1.ApplicationSet{ 4146 ObjectMeta: metav1.ObjectMeta{ 4147 Name: "name", 4148 Namespace: "argocd", 4149 }, 4150 Spec: v1alpha1.ApplicationSetSpec{ 4151 Strategy: &v1alpha1.ApplicationSetStrategy{ 4152 Type: "RollingSync", 4153 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4154 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4155 { 4156 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4157 }, 4158 { 4159 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4160 }, 4161 }, 4162 }, 4163 }, 4164 }, 4165 Status: v1alpha1.ApplicationSetStatus{ 4166 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4167 { 4168 Application: "app1", 4169 Status: "Healthy", 4170 }, 4171 { 4172 Application: "app2", 4173 Status: "Healthy", 4174 }, 4175 }, 4176 }, 4177 }, 4178 appMap: map[string]v1alpha1.Application{ 4179 "app1": { 4180 ObjectMeta: metav1.ObjectMeta{ 4181 Name: "app1", 4182 }, 4183 Status: v1alpha1.ApplicationStatus{ 4184 Health: v1alpha1.AppHealthStatus{ 4185 Status: health.HealthStatusHealthy, 4186 }, 4187 OperationState: &v1alpha1.OperationState{ 4188 Phase: common.OperationSucceeded, 4189 }, 4190 Sync: v1alpha1.SyncStatus{ 4191 Status: v1alpha1.SyncStatusCodeSynced, 4192 }, 4193 }, 4194 }, 4195 "app2": { 4196 ObjectMeta: metav1.ObjectMeta{ 4197 Name: "app2", 4198 }, 4199 Status: v1alpha1.ApplicationStatus{ 4200 Health: v1alpha1.AppHealthStatus{ 4201 Status: health.HealthStatusHealthy, 4202 }, 4203 OperationState: &v1alpha1.OperationState{ 4204 Phase: common.OperationSucceeded, 4205 }, 4206 Sync: v1alpha1.SyncStatus{ 4207 Status: v1alpha1.SyncStatusCodeSynced, 4208 }, 4209 }, 4210 }, 4211 }, 4212 appDependencyList: [][]string{ 4213 {"app1"}, 4214 {"app2"}, 4215 }, 4216 expectedMap: map[string]bool{ 4217 "app1": true, 4218 "app2": true, 4219 }, 4220 }, 4221 { 4222 name: "blocks RollingSync applications that are healthy and have no changes, but are still pending", 4223 appSet: v1alpha1.ApplicationSet{ 4224 ObjectMeta: metav1.ObjectMeta{ 4225 Name: "name", 4226 Namespace: "argocd", 4227 }, 4228 Spec: v1alpha1.ApplicationSetSpec{ 4229 Strategy: &v1alpha1.ApplicationSetStrategy{ 4230 Type: "RollingSync", 4231 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4232 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4233 { 4234 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4235 }, 4236 { 4237 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4238 }, 4239 }, 4240 }, 4241 }, 4242 }, 4243 Status: v1alpha1.ApplicationSetStatus{ 4244 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4245 { 4246 Application: "app1", 4247 Status: "Pending", 4248 }, 4249 { 4250 Application: "app2", 4251 Status: "Healthy", 4252 }, 4253 }, 4254 }, 4255 }, 4256 appMap: map[string]v1alpha1.Application{ 4257 "app1": { 4258 ObjectMeta: metav1.ObjectMeta{ 4259 Name: "app1", 4260 }, 4261 Status: v1alpha1.ApplicationStatus{ 4262 Health: v1alpha1.AppHealthStatus{ 4263 Status: health.HealthStatusHealthy, 4264 }, 4265 OperationState: &v1alpha1.OperationState{ 4266 Phase: common.OperationSucceeded, 4267 }, 4268 Sync: v1alpha1.SyncStatus{ 4269 Status: v1alpha1.SyncStatusCodeSynced, 4270 }, 4271 }, 4272 }, 4273 "app2": { 4274 ObjectMeta: metav1.ObjectMeta{ 4275 Name: "app2", 4276 }, 4277 Status: v1alpha1.ApplicationStatus{ 4278 Health: v1alpha1.AppHealthStatus{ 4279 Status: health.HealthStatusHealthy, 4280 }, 4281 OperationState: &v1alpha1.OperationState{ 4282 Phase: common.OperationSucceeded, 4283 }, 4284 Sync: v1alpha1.SyncStatus{ 4285 Status: v1alpha1.SyncStatusCodeSynced, 4286 }, 4287 }, 4288 }, 4289 }, 4290 appDependencyList: [][]string{ 4291 {"app1"}, 4292 {"app2"}, 4293 }, 4294 expectedMap: map[string]bool{ 4295 "app1": true, 4296 "app2": false, 4297 }, 4298 }, 4299 { 4300 name: "handles RollingSync applications that are up to date and healthy, but still syncing", 4301 appSet: v1alpha1.ApplicationSet{ 4302 ObjectMeta: metav1.ObjectMeta{ 4303 Name: "name", 4304 Namespace: "argocd", 4305 }, 4306 Spec: v1alpha1.ApplicationSetSpec{ 4307 Strategy: &v1alpha1.ApplicationSetStrategy{ 4308 Type: "RollingSync", 4309 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4310 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4311 { 4312 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4313 }, 4314 { 4315 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4316 }, 4317 }, 4318 }, 4319 }, 4320 }, 4321 Status: v1alpha1.ApplicationSetStatus{ 4322 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4323 { 4324 Application: "app1", 4325 Status: "Progressing", 4326 }, 4327 { 4328 Application: "app2", 4329 Status: "Progressing", 4330 }, 4331 }, 4332 }, 4333 }, 4334 appMap: map[string]v1alpha1.Application{ 4335 "app1": { 4336 ObjectMeta: metav1.ObjectMeta{ 4337 Name: "app1", 4338 }, 4339 Status: v1alpha1.ApplicationStatus{ 4340 Health: v1alpha1.AppHealthStatus{ 4341 Status: health.HealthStatusHealthy, 4342 }, 4343 OperationState: &v1alpha1.OperationState{ 4344 Phase: common.OperationRunning, 4345 }, 4346 Sync: v1alpha1.SyncStatus{ 4347 Status: v1alpha1.SyncStatusCodeSynced, 4348 }, 4349 }, 4350 }, 4351 "app2": { 4352 ObjectMeta: metav1.ObjectMeta{ 4353 Name: "app2", 4354 }, 4355 Status: v1alpha1.ApplicationStatus{ 4356 Health: v1alpha1.AppHealthStatus{ 4357 Status: health.HealthStatusHealthy, 4358 }, 4359 OperationState: &v1alpha1.OperationState{ 4360 Phase: common.OperationRunning, 4361 }, 4362 Sync: v1alpha1.SyncStatus{ 4363 Status: v1alpha1.SyncStatusCodeSynced, 4364 }, 4365 }, 4366 }, 4367 }, 4368 appDependencyList: [][]string{ 4369 {"app1"}, 4370 {"app2"}, 4371 }, 4372 expectedMap: map[string]bool{ 4373 "app1": true, 4374 "app2": false, 4375 }, 4376 }, 4377 { 4378 name: "handles RollingSync applications that are up to date and synced, but degraded", 4379 appSet: v1alpha1.ApplicationSet{ 4380 ObjectMeta: metav1.ObjectMeta{ 4381 Name: "name", 4382 Namespace: "argocd", 4383 }, 4384 Spec: v1alpha1.ApplicationSetSpec{ 4385 Strategy: &v1alpha1.ApplicationSetStrategy{ 4386 Type: "RollingSync", 4387 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4388 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4389 { 4390 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4391 }, 4392 { 4393 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4394 }, 4395 }, 4396 }, 4397 }, 4398 }, 4399 Status: v1alpha1.ApplicationSetStatus{ 4400 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4401 { 4402 Application: "app1", 4403 Status: "Progressing", 4404 }, 4405 { 4406 Application: "app2", 4407 Status: "Progressing", 4408 }, 4409 }, 4410 }, 4411 }, 4412 appMap: map[string]v1alpha1.Application{ 4413 "app1": { 4414 ObjectMeta: metav1.ObjectMeta{ 4415 Name: "app1", 4416 }, 4417 Status: v1alpha1.ApplicationStatus{ 4418 Health: v1alpha1.AppHealthStatus{ 4419 Status: health.HealthStatusDegraded, 4420 }, 4421 OperationState: &v1alpha1.OperationState{ 4422 Phase: common.OperationRunning, 4423 }, 4424 Sync: v1alpha1.SyncStatus{ 4425 Status: v1alpha1.SyncStatusCodeSynced, 4426 }, 4427 }, 4428 }, 4429 "app2": { 4430 ObjectMeta: metav1.ObjectMeta{ 4431 Name: "app2", 4432 }, 4433 Status: v1alpha1.ApplicationStatus{ 4434 Health: v1alpha1.AppHealthStatus{ 4435 Status: health.HealthStatusDegraded, 4436 }, 4437 OperationState: &v1alpha1.OperationState{ 4438 Phase: common.OperationRunning, 4439 }, 4440 Sync: v1alpha1.SyncStatus{ 4441 Status: v1alpha1.SyncStatusCodeSynced, 4442 }, 4443 }, 4444 }, 4445 }, 4446 appDependencyList: [][]string{ 4447 {"app1"}, 4448 {"app2"}, 4449 }, 4450 expectedMap: map[string]bool{ 4451 "app1": true, 4452 "app2": false, 4453 }, 4454 }, 4455 { 4456 name: "handles RollingSync applications that are OutOfSync and healthy", 4457 appSet: v1alpha1.ApplicationSet{ 4458 ObjectMeta: metav1.ObjectMeta{ 4459 Name: "name", 4460 Namespace: "argocd", 4461 }, 4462 Spec: v1alpha1.ApplicationSetSpec{ 4463 Strategy: &v1alpha1.ApplicationSetStrategy{ 4464 Type: "RollingSync", 4465 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4466 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4467 { 4468 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4469 }, 4470 { 4471 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4472 }, 4473 }, 4474 }, 4475 }, 4476 }, 4477 Status: v1alpha1.ApplicationSetStatus{ 4478 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4479 { 4480 Application: "app1", 4481 Status: "Healthy", 4482 }, 4483 { 4484 Application: "app2", 4485 Status: "Healthy", 4486 }, 4487 }, 4488 }, 4489 }, 4490 appDependencyList: [][]string{ 4491 {"app1"}, 4492 {"app2"}, 4493 }, 4494 appMap: map[string]v1alpha1.Application{ 4495 "app1": { 4496 ObjectMeta: metav1.ObjectMeta{ 4497 Name: "app1", 4498 }, 4499 Status: v1alpha1.ApplicationStatus{ 4500 Health: v1alpha1.AppHealthStatus{ 4501 Status: health.HealthStatusHealthy, 4502 }, 4503 OperationState: &v1alpha1.OperationState{ 4504 Phase: common.OperationSucceeded, 4505 }, 4506 Sync: v1alpha1.SyncStatus{ 4507 Status: v1alpha1.SyncStatusCodeOutOfSync, 4508 }, 4509 }, 4510 }, 4511 "app2": { 4512 ObjectMeta: metav1.ObjectMeta{ 4513 Name: "app2", 4514 }, 4515 Status: v1alpha1.ApplicationStatus{ 4516 Health: v1alpha1.AppHealthStatus{ 4517 Status: health.HealthStatusHealthy, 4518 }, 4519 OperationState: &v1alpha1.OperationState{ 4520 Phase: common.OperationSucceeded, 4521 }, 4522 Sync: v1alpha1.SyncStatus{ 4523 Status: v1alpha1.SyncStatusCodeOutOfSync, 4524 }, 4525 }, 4526 }, 4527 }, 4528 expectedMap: map[string]bool{ 4529 "app1": true, 4530 "app2": false, 4531 }, 4532 }, 4533 { 4534 name: "handles a lot of applications", 4535 appSet: v1alpha1.ApplicationSet{ 4536 ObjectMeta: metav1.ObjectMeta{ 4537 Name: "name", 4538 Namespace: "argocd", 4539 }, 4540 Spec: v1alpha1.ApplicationSetSpec{ 4541 Strategy: &v1alpha1.ApplicationSetStrategy{ 4542 Type: "RollingSync", 4543 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4544 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4545 { 4546 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4547 }, 4548 { 4549 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4550 }, 4551 }, 4552 }, 4553 }, 4554 }, 4555 Status: v1alpha1.ApplicationSetStatus{ 4556 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4557 { 4558 Application: "app1", 4559 Status: "Healthy", 4560 }, 4561 { 4562 Application: "app2", 4563 Status: "Healthy", 4564 }, 4565 { 4566 Application: "app3", 4567 Status: "Healthy", 4568 }, 4569 { 4570 Application: "app4", 4571 Status: "Healthy", 4572 }, 4573 { 4574 Application: "app5", 4575 Status: "Healthy", 4576 }, 4577 { 4578 Application: "app7", 4579 Status: "Healthy", 4580 }, 4581 }, 4582 }, 4583 }, 4584 appMap: map[string]v1alpha1.Application{ 4585 "app1": { 4586 ObjectMeta: metav1.ObjectMeta{ 4587 Name: "app1", 4588 }, 4589 Status: v1alpha1.ApplicationStatus{ 4590 Health: v1alpha1.AppHealthStatus{ 4591 Status: health.HealthStatusHealthy, 4592 }, 4593 OperationState: &v1alpha1.OperationState{ 4594 Phase: common.OperationSucceeded, 4595 }, 4596 Sync: v1alpha1.SyncStatus{ 4597 Status: v1alpha1.SyncStatusCodeSynced, 4598 }, 4599 }, 4600 }, 4601 "app2": { 4602 ObjectMeta: metav1.ObjectMeta{ 4603 Name: "app2", 4604 }, 4605 Status: v1alpha1.ApplicationStatus{ 4606 Health: v1alpha1.AppHealthStatus{ 4607 Status: health.HealthStatusHealthy, 4608 }, 4609 OperationState: &v1alpha1.OperationState{ 4610 Phase: common.OperationSucceeded, 4611 }, 4612 Sync: v1alpha1.SyncStatus{ 4613 Status: v1alpha1.SyncStatusCodeSynced, 4614 }, 4615 }, 4616 }, 4617 "app3": { 4618 ObjectMeta: metav1.ObjectMeta{ 4619 Name: "app3", 4620 }, 4621 Status: v1alpha1.ApplicationStatus{ 4622 Health: v1alpha1.AppHealthStatus{ 4623 Status: health.HealthStatusHealthy, 4624 }, 4625 OperationState: &v1alpha1.OperationState{ 4626 Phase: common.OperationSucceeded, 4627 }, 4628 Sync: v1alpha1.SyncStatus{ 4629 Status: v1alpha1.SyncStatusCodeSynced, 4630 }, 4631 }, 4632 }, 4633 "app5": { 4634 ObjectMeta: metav1.ObjectMeta{ 4635 Name: "app5", 4636 }, 4637 Status: v1alpha1.ApplicationStatus{ 4638 Health: v1alpha1.AppHealthStatus{ 4639 Status: health.HealthStatusHealthy, 4640 }, 4641 OperationState: &v1alpha1.OperationState{ 4642 Phase: common.OperationSucceeded, 4643 }, 4644 Sync: v1alpha1.SyncStatus{ 4645 Status: v1alpha1.SyncStatusCodeSynced, 4646 }, 4647 }, 4648 }, 4649 "app6": { 4650 ObjectMeta: metav1.ObjectMeta{ 4651 Name: "app6", 4652 }, 4653 Status: v1alpha1.ApplicationStatus{ 4654 Health: v1alpha1.AppHealthStatus{ 4655 Status: health.HealthStatusDegraded, 4656 }, 4657 OperationState: &v1alpha1.OperationState{ 4658 Phase: common.OperationSucceeded, 4659 }, 4660 Sync: v1alpha1.SyncStatus{ 4661 Status: v1alpha1.SyncStatusCodeSynced, 4662 }, 4663 }, 4664 }, 4665 }, 4666 appDependencyList: [][]string{ 4667 {"app1", "app2", "app3"}, 4668 {"app4", "app5", "app6"}, 4669 {"app7", "app8", "app9"}, 4670 }, 4671 expectedMap: map[string]bool{ 4672 "app1": true, 4673 "app2": true, 4674 "app3": true, 4675 "app4": true, 4676 "app5": true, 4677 "app6": true, 4678 "app7": false, 4679 "app8": false, 4680 "app9": false, 4681 }, 4682 }, 4683 } { 4684 t.Run(cc.name, func(t *testing.T) { 4685 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 4686 4687 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 4688 4689 r := ApplicationSetReconciler{ 4690 Client: client, 4691 Scheme: scheme, 4692 Recorder: record.NewFakeRecorder(1), 4693 Generators: map[string]generators.Generator{}, 4694 ArgoDB: argodb, 4695 KubeClientset: kubeclientset, 4696 Metrics: metrics, 4697 } 4698 4699 appSyncMap := r.buildAppSyncMap(cc.appSet, cc.appDependencyList, cc.appMap) 4700 assert.Equal(t, cc.expectedMap, appSyncMap, "expected appSyncMap did not match actual") 4701 }) 4702 } 4703 } 4704 4705 func TestUpdateApplicationSetApplicationStatus(t *testing.T) { 4706 scheme := runtime.NewScheme() 4707 err := v1alpha1.AddToScheme(scheme) 4708 require.NoError(t, err) 4709 4710 for _, cc := range []struct { 4711 name string 4712 appSet v1alpha1.ApplicationSet 4713 apps []v1alpha1.Application 4714 appStepMap map[string]int 4715 expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus 4716 }{ 4717 { 4718 name: "handles a nil list of statuses and no applications", 4719 appSet: v1alpha1.ApplicationSet{ 4720 ObjectMeta: metav1.ObjectMeta{ 4721 Name: "name", 4722 Namespace: "argocd", 4723 }, 4724 Spec: v1alpha1.ApplicationSetSpec{ 4725 Strategy: &v1alpha1.ApplicationSetStrategy{ 4726 Type: "RollingSync", 4727 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4728 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4729 { 4730 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4731 }, 4732 { 4733 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4734 }, 4735 }, 4736 }, 4737 }, 4738 }, 4739 }, 4740 apps: []v1alpha1.Application{}, 4741 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 4742 }, 4743 { 4744 name: "handles a nil list of statuses with a healthy application", 4745 appSet: v1alpha1.ApplicationSet{ 4746 ObjectMeta: metav1.ObjectMeta{ 4747 Name: "name", 4748 Namespace: "argocd", 4749 }, 4750 Spec: v1alpha1.ApplicationSetSpec{ 4751 Strategy: &v1alpha1.ApplicationSetStrategy{ 4752 Type: "RollingSync", 4753 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4754 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4755 { 4756 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4757 }, 4758 { 4759 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4760 }, 4761 }, 4762 }, 4763 }, 4764 }, 4765 }, 4766 apps: []v1alpha1.Application{ 4767 { 4768 ObjectMeta: metav1.ObjectMeta{ 4769 Name: "app1", 4770 }, 4771 Status: v1alpha1.ApplicationStatus{ 4772 Health: v1alpha1.AppHealthStatus{ 4773 Status: health.HealthStatusHealthy, 4774 }, 4775 OperationState: &v1alpha1.OperationState{ 4776 Phase: common.OperationSucceeded, 4777 }, 4778 Sync: v1alpha1.SyncStatus{ 4779 Status: v1alpha1.SyncStatusCodeSynced, 4780 }, 4781 }, 4782 }, 4783 }, 4784 appStepMap: map[string]int{ 4785 "app1": 0, 4786 }, 4787 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4788 { 4789 Application: "app1", 4790 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4791 Status: "Healthy", 4792 Step: "1", 4793 TargetRevisions: []string{}, 4794 }, 4795 }, 4796 }, 4797 { 4798 name: "handles an empty list of statuses with a healthy application", 4799 appSet: v1alpha1.ApplicationSet{ 4800 ObjectMeta: metav1.ObjectMeta{ 4801 Name: "name", 4802 Namespace: "argocd", 4803 }, 4804 Spec: v1alpha1.ApplicationSetSpec{ 4805 Strategy: &v1alpha1.ApplicationSetStrategy{ 4806 Type: "RollingSync", 4807 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4808 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4809 { 4810 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4811 }, 4812 { 4813 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4814 }, 4815 }, 4816 }, 4817 }, 4818 }, 4819 Status: v1alpha1.ApplicationSetStatus{}, 4820 }, 4821 apps: []v1alpha1.Application{ 4822 { 4823 ObjectMeta: metav1.ObjectMeta{ 4824 Name: "app1", 4825 }, 4826 Status: v1alpha1.ApplicationStatus{ 4827 Health: v1alpha1.AppHealthStatus{ 4828 Status: health.HealthStatusHealthy, 4829 }, 4830 OperationState: &v1alpha1.OperationState{ 4831 Phase: common.OperationSucceeded, 4832 }, 4833 Sync: v1alpha1.SyncStatus{ 4834 Status: v1alpha1.SyncStatusCodeSynced, 4835 }, 4836 }, 4837 }, 4838 }, 4839 appStepMap: map[string]int{ 4840 "app1": 0, 4841 }, 4842 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4843 { 4844 Application: "app1", 4845 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4846 Status: "Healthy", 4847 Step: "1", 4848 TargetRevisions: []string{}, 4849 }, 4850 }, 4851 }, 4852 { 4853 name: "handles an outdated list of statuses with a healthy application, setting required variables", 4854 appSet: v1alpha1.ApplicationSet{ 4855 ObjectMeta: metav1.ObjectMeta{ 4856 Name: "name", 4857 Namespace: "argocd", 4858 }, 4859 Spec: v1alpha1.ApplicationSetSpec{ 4860 Strategy: &v1alpha1.ApplicationSetStrategy{ 4861 Type: "RollingSync", 4862 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4863 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4864 { 4865 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4866 }, 4867 { 4868 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4869 }, 4870 }, 4871 }, 4872 }, 4873 }, 4874 Status: v1alpha1.ApplicationSetStatus{ 4875 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4876 { 4877 Application: "app1", 4878 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4879 Status: "Healthy", 4880 Step: "1", 4881 }, 4882 }, 4883 }, 4884 }, 4885 apps: []v1alpha1.Application{ 4886 { 4887 ObjectMeta: metav1.ObjectMeta{ 4888 Name: "app1", 4889 }, 4890 Status: v1alpha1.ApplicationStatus{ 4891 Health: v1alpha1.AppHealthStatus{ 4892 Status: health.HealthStatusHealthy, 4893 }, 4894 OperationState: &v1alpha1.OperationState{ 4895 Phase: common.OperationSucceeded, 4896 }, 4897 Sync: v1alpha1.SyncStatus{ 4898 Status: v1alpha1.SyncStatusCodeSynced, 4899 }, 4900 }, 4901 }, 4902 }, 4903 appStepMap: map[string]int{ 4904 "app1": 0, 4905 }, 4906 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4907 { 4908 Application: "app1", 4909 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4910 Status: "Healthy", 4911 Step: "1", 4912 TargetRevisions: []string{}, 4913 }, 4914 }, 4915 }, 4916 { 4917 name: "progresses an OutOfSync RollingSync application to waiting", 4918 appSet: v1alpha1.ApplicationSet{ 4919 ObjectMeta: metav1.ObjectMeta{ 4920 Name: "name", 4921 Namespace: "argocd", 4922 }, 4923 Spec: v1alpha1.ApplicationSetSpec{ 4924 Strategy: &v1alpha1.ApplicationSetStrategy{ 4925 Type: "RollingSync", 4926 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 4927 Steps: []v1alpha1.ApplicationSetRolloutStep{ 4928 { 4929 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4930 }, 4931 { 4932 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 4933 }, 4934 }, 4935 }, 4936 }, 4937 }, 4938 Status: v1alpha1.ApplicationSetStatus{ 4939 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4940 { 4941 Application: "app1", 4942 Message: "", 4943 Status: "Healthy", 4944 Step: "1", 4945 TargetRevisions: []string{"Previous"}, 4946 }, 4947 { 4948 Application: "app2-multisource", 4949 Message: "", 4950 Status: "Healthy", 4951 Step: "1", 4952 TargetRevisions: []string{"Previous", "OtherPrevious"}, 4953 }, 4954 }, 4955 }, 4956 }, 4957 apps: []v1alpha1.Application{ 4958 { 4959 ObjectMeta: metav1.ObjectMeta{ 4960 Name: "app1", 4961 }, 4962 Status: v1alpha1.ApplicationStatus{ 4963 Sync: v1alpha1.SyncStatus{ 4964 Status: v1alpha1.SyncStatusCodeOutOfSync, 4965 Revision: "Next", 4966 }, 4967 }, 4968 }, 4969 { 4970 ObjectMeta: metav1.ObjectMeta{ 4971 Name: "app2-multisource", 4972 }, 4973 Status: v1alpha1.ApplicationStatus{ 4974 Sync: v1alpha1.SyncStatus{ 4975 Status: v1alpha1.SyncStatusCodeOutOfSync, 4976 Revisions: []string{"Next", "OtherNext"}, 4977 }, 4978 }, 4979 }, 4980 }, 4981 appStepMap: map[string]int{ 4982 "app1": 0, 4983 "app2-multisource": 0, 4984 }, 4985 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4986 { 4987 Application: "app1", 4988 Message: "Application has pending changes, setting status to Waiting.", 4989 Status: "Waiting", 4990 Step: "1", 4991 TargetRevisions: []string{"Next"}, 4992 }, 4993 { 4994 Application: "app2-multisource", 4995 Message: "Application has pending changes, setting status to Waiting.", 4996 Status: "Waiting", 4997 Step: "1", 4998 TargetRevisions: []string{"Next", "OtherNext"}, 4999 }, 5000 }, 5001 }, 5002 { 5003 name: "progresses a pending progressing application to progressing", 5004 appSet: v1alpha1.ApplicationSet{ 5005 ObjectMeta: metav1.ObjectMeta{ 5006 Name: "name", 5007 Namespace: "argocd", 5008 }, 5009 Spec: v1alpha1.ApplicationSetSpec{ 5010 Strategy: &v1alpha1.ApplicationSetStrategy{ 5011 Type: "RollingSync", 5012 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5013 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5014 { 5015 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5016 }, 5017 { 5018 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5019 }, 5020 }, 5021 }, 5022 }, 5023 }, 5024 Status: v1alpha1.ApplicationSetStatus{ 5025 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5026 { 5027 Application: "app1", 5028 Message: "", 5029 Status: "Pending", 5030 Step: "1", 5031 TargetRevisions: []string{"Next"}, 5032 }, 5033 }, 5034 }, 5035 }, 5036 apps: []v1alpha1.Application{ 5037 { 5038 ObjectMeta: metav1.ObjectMeta{ 5039 Name: "app1", 5040 }, 5041 Status: v1alpha1.ApplicationStatus{ 5042 Health: v1alpha1.AppHealthStatus{ 5043 Status: health.HealthStatusProgressing, 5044 }, 5045 Sync: v1alpha1.SyncStatus{ 5046 Revision: "Next", 5047 }, 5048 }, 5049 }, 5050 }, 5051 appStepMap: map[string]int{ 5052 "app1": 0, 5053 }, 5054 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5055 { 5056 Application: "app1", 5057 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 5058 Status: "Progressing", 5059 Step: "1", 5060 TargetRevisions: []string{"Next"}, 5061 }, 5062 }, 5063 }, 5064 { 5065 name: "progresses a pending synced application to progressing", 5066 appSet: v1alpha1.ApplicationSet{ 5067 ObjectMeta: metav1.ObjectMeta{ 5068 Name: "name", 5069 Namespace: "argocd", 5070 }, 5071 Spec: v1alpha1.ApplicationSetSpec{ 5072 Strategy: &v1alpha1.ApplicationSetStrategy{ 5073 Type: "RollingSync", 5074 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5075 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5076 { 5077 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5078 }, 5079 { 5080 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5081 }, 5082 }, 5083 }, 5084 }, 5085 }, 5086 Status: v1alpha1.ApplicationSetStatus{ 5087 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5088 { 5089 Application: "app1", 5090 Message: "", 5091 Status: "Pending", 5092 Step: "1", 5093 TargetRevisions: []string{"Current"}, 5094 }, 5095 }, 5096 }, 5097 }, 5098 apps: []v1alpha1.Application{ 5099 { 5100 ObjectMeta: metav1.ObjectMeta{ 5101 Name: "app1", 5102 }, 5103 Status: v1alpha1.ApplicationStatus{ 5104 Health: v1alpha1.AppHealthStatus{ 5105 Status: health.HealthStatusHealthy, 5106 }, 5107 OperationState: &v1alpha1.OperationState{ 5108 Phase: common.OperationRunning, 5109 }, 5110 Sync: v1alpha1.SyncStatus{ 5111 Status: v1alpha1.SyncStatusCodeSynced, 5112 Revision: "Current", 5113 }, 5114 }, 5115 }, 5116 }, 5117 appStepMap: map[string]int{ 5118 "app1": 0, 5119 }, 5120 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5121 { 5122 Application: "app1", 5123 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 5124 Status: "Progressing", 5125 Step: "1", 5126 TargetRevisions: []string{"Current"}, 5127 }, 5128 }, 5129 }, 5130 { 5131 name: "progresses a progressing application to healthy", 5132 appSet: v1alpha1.ApplicationSet{ 5133 ObjectMeta: metav1.ObjectMeta{ 5134 Name: "name", 5135 Namespace: "argocd", 5136 }, 5137 Spec: v1alpha1.ApplicationSetSpec{ 5138 Strategy: &v1alpha1.ApplicationSetStrategy{ 5139 Type: "RollingSync", 5140 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5141 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5142 { 5143 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5144 }, 5145 { 5146 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5147 }, 5148 }, 5149 }, 5150 }, 5151 }, 5152 Status: v1alpha1.ApplicationSetStatus{ 5153 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5154 { 5155 Application: "app1", 5156 Message: "", 5157 Status: "Progressing", 5158 Step: "1", 5159 TargetRevisions: []string{"Next"}, 5160 }, 5161 }, 5162 }, 5163 }, 5164 apps: []v1alpha1.Application{ 5165 { 5166 ObjectMeta: metav1.ObjectMeta{ 5167 Name: "app1", 5168 }, 5169 Status: v1alpha1.ApplicationStatus{ 5170 Health: v1alpha1.AppHealthStatus{ 5171 Status: health.HealthStatusHealthy, 5172 }, 5173 OperationState: &v1alpha1.OperationState{ 5174 Phase: common.OperationSucceeded, 5175 }, 5176 Sync: v1alpha1.SyncStatus{ 5177 Status: v1alpha1.SyncStatusCodeSynced, 5178 Revision: "Next", 5179 }, 5180 }, 5181 }, 5182 }, 5183 appStepMap: map[string]int{ 5184 "app1": 0, 5185 }, 5186 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5187 { 5188 Application: "app1", 5189 Message: "Application resource became Healthy, updating status from Progressing to Healthy.", 5190 Status: "Healthy", 5191 Step: "1", 5192 TargetRevisions: []string{"Next"}, 5193 }, 5194 }, 5195 }, 5196 { 5197 name: "progresses a waiting healthy application to healthy", 5198 appSet: v1alpha1.ApplicationSet{ 5199 ObjectMeta: metav1.ObjectMeta{ 5200 Name: "name", 5201 Namespace: "argocd", 5202 }, 5203 Spec: v1alpha1.ApplicationSetSpec{ 5204 Strategy: &v1alpha1.ApplicationSetStrategy{ 5205 Type: "RollingSync", 5206 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5207 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5208 { 5209 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5210 }, 5211 { 5212 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5213 }, 5214 }, 5215 }, 5216 }, 5217 }, 5218 Status: v1alpha1.ApplicationSetStatus{ 5219 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5220 { 5221 Application: "app1", 5222 Message: "", 5223 Status: "Waiting", 5224 Step: "1", 5225 TargetRevisions: []string{"Current"}, 5226 }, 5227 }, 5228 }, 5229 }, 5230 apps: []v1alpha1.Application{ 5231 { 5232 ObjectMeta: metav1.ObjectMeta{ 5233 Name: "app1", 5234 }, 5235 Status: v1alpha1.ApplicationStatus{ 5236 Health: v1alpha1.AppHealthStatus{ 5237 Status: health.HealthStatusHealthy, 5238 }, 5239 OperationState: &v1alpha1.OperationState{ 5240 Phase: common.OperationSucceeded, 5241 }, 5242 Sync: v1alpha1.SyncStatus{ 5243 Revision: "Current", 5244 Status: v1alpha1.SyncStatusCodeSynced, 5245 }, 5246 }, 5247 }, 5248 }, 5249 appStepMap: map[string]int{ 5250 "app1": 0, 5251 }, 5252 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5253 { 5254 Application: "app1", 5255 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 5256 Status: "Healthy", 5257 Step: "1", 5258 TargetRevisions: []string{"Current"}, 5259 }, 5260 }, 5261 }, 5262 { 5263 name: "progresses a new outofsync application in a later step to waiting", 5264 appSet: v1alpha1.ApplicationSet{ 5265 ObjectMeta: metav1.ObjectMeta{ 5266 Name: "name", 5267 Namespace: "argocd", 5268 }, 5269 Spec: v1alpha1.ApplicationSetSpec{ 5270 Strategy: &v1alpha1.ApplicationSetStrategy{ 5271 Type: "RollingSync", 5272 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5273 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5274 { 5275 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5276 }, 5277 { 5278 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5279 }, 5280 }, 5281 }, 5282 }, 5283 }, 5284 }, 5285 apps: []v1alpha1.Application{ 5286 { 5287 ObjectMeta: metav1.ObjectMeta{ 5288 Name: "app1", 5289 }, 5290 Status: v1alpha1.ApplicationStatus{ 5291 Health: v1alpha1.AppHealthStatus{ 5292 Status: health.HealthStatusHealthy, 5293 }, 5294 OperationState: &v1alpha1.OperationState{ 5295 Phase: common.OperationSucceeded, 5296 SyncResult: &v1alpha1.SyncOperationResult{ 5297 Revision: "Previous", 5298 }, 5299 }, 5300 Sync: v1alpha1.SyncStatus{ 5301 Status: v1alpha1.SyncStatusCodeOutOfSync, 5302 Revision: "Next", 5303 }, 5304 }, 5305 }, 5306 }, 5307 appStepMap: map[string]int{ 5308 "app1": 1, 5309 "app2": 0, 5310 }, 5311 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5312 { 5313 Application: "app1", 5314 Message: "No Application status found, defaulting status to Waiting.", 5315 Status: "Waiting", 5316 Step: "2", 5317 TargetRevisions: []string{"Next"}, 5318 }, 5319 }, 5320 }, 5321 { 5322 name: "progresses a pending application with a successful sync triggered by controller to progressing", 5323 appSet: v1alpha1.ApplicationSet{ 5324 ObjectMeta: metav1.ObjectMeta{ 5325 Name: "name", 5326 Namespace: "argocd", 5327 }, 5328 Spec: v1alpha1.ApplicationSetSpec{ 5329 Strategy: &v1alpha1.ApplicationSetStrategy{ 5330 Type: "RollingSync", 5331 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5332 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5333 { 5334 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5335 }, 5336 { 5337 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5338 }, 5339 }, 5340 }, 5341 }, 5342 }, 5343 Status: v1alpha1.ApplicationSetStatus{ 5344 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5345 { 5346 Application: "app1", 5347 LastTransitionTime: &metav1.Time{ 5348 Time: time.Now().Add(time.Duration(-1) * time.Minute), 5349 }, 5350 Message: "", 5351 Status: "Pending", 5352 Step: "1", 5353 TargetRevisions: []string{"Next"}, 5354 }, 5355 }, 5356 }, 5357 }, 5358 apps: []v1alpha1.Application{ 5359 { 5360 ObjectMeta: metav1.ObjectMeta{ 5361 Name: "app1", 5362 }, 5363 Status: v1alpha1.ApplicationStatus{ 5364 Health: v1alpha1.AppHealthStatus{ 5365 Status: health.HealthStatusDegraded, 5366 }, 5367 OperationState: &v1alpha1.OperationState{ 5368 Phase: common.OperationSucceeded, 5369 StartedAt: metav1.Time{ 5370 Time: time.Now(), 5371 }, 5372 Operation: v1alpha1.Operation{ 5373 InitiatedBy: v1alpha1.OperationInitiator{ 5374 Username: "applicationset-controller", 5375 Automated: true, 5376 }, 5377 }, 5378 SyncResult: &v1alpha1.SyncOperationResult{ 5379 Revision: "Next", 5380 }, 5381 }, 5382 Sync: v1alpha1.SyncStatus{ 5383 Status: v1alpha1.SyncStatusCodeSynced, 5384 Revision: "Next", 5385 }, 5386 }, 5387 }, 5388 }, 5389 appStepMap: map[string]int{ 5390 "app1": 0, 5391 }, 5392 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5393 { 5394 Application: "app1", 5395 Message: "Application resource completed a sync successfully, updating status from Pending to Progressing.", 5396 Status: "Progressing", 5397 Step: "1", 5398 TargetRevisions: []string{"Next"}, 5399 }, 5400 }, 5401 }, 5402 { 5403 name: "progresses a pending application with a successful sync trigger by applicationset-controller <1s ago to progressing", 5404 appSet: v1alpha1.ApplicationSet{ 5405 ObjectMeta: metav1.ObjectMeta{ 5406 Name: "name", 5407 Namespace: "argocd", 5408 }, 5409 Spec: v1alpha1.ApplicationSetSpec{ 5410 Strategy: &v1alpha1.ApplicationSetStrategy{ 5411 Type: "RollingSync", 5412 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5413 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5414 { 5415 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5416 }, 5417 { 5418 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5419 }, 5420 }, 5421 }, 5422 }, 5423 }, 5424 Status: v1alpha1.ApplicationSetStatus{ 5425 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5426 { 5427 Application: "app1", 5428 LastTransitionTime: &metav1.Time{ 5429 Time: time.Now(), 5430 }, 5431 Message: "", 5432 Status: "Pending", 5433 Step: "1", 5434 TargetRevisions: []string{"Next"}, 5435 }, 5436 }, 5437 }, 5438 }, 5439 apps: []v1alpha1.Application{ 5440 { 5441 ObjectMeta: metav1.ObjectMeta{ 5442 Name: "app1", 5443 }, 5444 Status: v1alpha1.ApplicationStatus{ 5445 Health: v1alpha1.AppHealthStatus{ 5446 Status: health.HealthStatusDegraded, 5447 }, 5448 OperationState: &v1alpha1.OperationState{ 5449 Phase: common.OperationSucceeded, 5450 StartedAt: metav1.Time{ 5451 Time: time.Now().Add(time.Duration(-1) * time.Second), 5452 }, 5453 Operation: v1alpha1.Operation{ 5454 InitiatedBy: v1alpha1.OperationInitiator{ 5455 Username: "applicationset-controller", 5456 Automated: true, 5457 }, 5458 }, 5459 SyncResult: &v1alpha1.SyncOperationResult{ 5460 Revision: "Next", 5461 }, 5462 }, 5463 Sync: v1alpha1.SyncStatus{ 5464 Status: v1alpha1.SyncStatusCodeSynced, 5465 Revision: "Next", 5466 }, 5467 }, 5468 }, 5469 }, 5470 appStepMap: map[string]int{ 5471 "app1": 0, 5472 }, 5473 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5474 { 5475 Application: "app1", 5476 Message: "Application resource completed a sync successfully, updating status from Pending to Progressing.", 5477 Status: "Progressing", 5478 Step: "1", 5479 TargetRevisions: []string{"Next"}, 5480 }, 5481 }, 5482 }, 5483 { 5484 name: "removes the appStatus for applications that no longer exist", 5485 appSet: v1alpha1.ApplicationSet{ 5486 ObjectMeta: metav1.ObjectMeta{ 5487 Name: "name", 5488 Namespace: "argocd", 5489 }, 5490 Spec: v1alpha1.ApplicationSetSpec{ 5491 Strategy: &v1alpha1.ApplicationSetStrategy{ 5492 Type: "RollingSync", 5493 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5494 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5495 { 5496 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5497 }, 5498 { 5499 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5500 }, 5501 }, 5502 }, 5503 }, 5504 }, 5505 Status: v1alpha1.ApplicationSetStatus{ 5506 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5507 { 5508 Application: "app1", 5509 Message: "Application has pending changes, setting status to Waiting.", 5510 Status: "Waiting", 5511 Step: "1", 5512 TargetRevisions: []string{"Current"}, 5513 }, 5514 { 5515 Application: "app2", 5516 Message: "Application has pending changes, setting status to Waiting.", 5517 Status: "Waiting", 5518 Step: "1", 5519 TargetRevisions: []string{"Current"}, 5520 }, 5521 }, 5522 }, 5523 }, 5524 apps: []v1alpha1.Application{ 5525 { 5526 ObjectMeta: metav1.ObjectMeta{ 5527 Name: "app1", 5528 }, 5529 Status: v1alpha1.ApplicationStatus{ 5530 Health: v1alpha1.AppHealthStatus{ 5531 Status: health.HealthStatusHealthy, 5532 }, 5533 OperationState: &v1alpha1.OperationState{ 5534 Phase: common.OperationSucceeded, 5535 }, 5536 Sync: v1alpha1.SyncStatus{ 5537 Status: v1alpha1.SyncStatusCodeSynced, 5538 Revision: "Current", 5539 }, 5540 }, 5541 }, 5542 }, 5543 appStepMap: map[string]int{ 5544 "app1": 0, 5545 }, 5546 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5547 { 5548 Application: "app1", 5549 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 5550 Status: "Healthy", 5551 Step: "1", 5552 TargetRevisions: []string{"Current"}, 5553 }, 5554 }, 5555 }, 5556 { 5557 name: "progresses a pending synced application with an old revision to progressing with the Current one", 5558 appSet: v1alpha1.ApplicationSet{ 5559 ObjectMeta: metav1.ObjectMeta{ 5560 Name: "name", 5561 Namespace: "argocd", 5562 }, 5563 Spec: v1alpha1.ApplicationSetSpec{ 5564 Strategy: &v1alpha1.ApplicationSetStrategy{ 5565 Type: "RollingSync", 5566 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5567 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5568 { 5569 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5570 }, 5571 { 5572 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5573 }, 5574 }, 5575 }, 5576 }, 5577 }, 5578 Status: v1alpha1.ApplicationSetStatus{ 5579 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5580 { 5581 Application: "app1", 5582 Message: "", 5583 Status: "Pending", 5584 Step: "1", 5585 TargetRevisions: []string{"Old"}, 5586 }, 5587 }, 5588 }, 5589 }, 5590 apps: []v1alpha1.Application{ 5591 { 5592 ObjectMeta: metav1.ObjectMeta{ 5593 Name: "app1", 5594 }, 5595 Status: v1alpha1.ApplicationStatus{ 5596 Health: v1alpha1.AppHealthStatus{ 5597 Status: health.HealthStatusHealthy, 5598 }, 5599 OperationState: &v1alpha1.OperationState{ 5600 Phase: common.OperationSucceeded, 5601 SyncResult: &v1alpha1.SyncOperationResult{ 5602 Revision: "Current", 5603 }, 5604 }, 5605 Sync: v1alpha1.SyncStatus{ 5606 Status: v1alpha1.SyncStatusCodeSynced, 5607 Revisions: []string{"Current"}, 5608 }, 5609 }, 5610 }, 5611 }, 5612 appStepMap: map[string]int{ 5613 "app1": 0, 5614 }, 5615 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5616 { 5617 Application: "app1", 5618 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 5619 Status: "Healthy", 5620 Step: "1", 5621 TargetRevisions: []string{"Current"}, 5622 }, 5623 }, 5624 }, 5625 } { 5626 t.Run(cc.name, func(t *testing.T) { 5627 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 5628 5629 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build() 5630 metrics := appsetmetrics.NewFakeAppsetMetrics() 5631 5632 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 5633 5634 r := ApplicationSetReconciler{ 5635 Client: client, 5636 Scheme: scheme, 5637 Recorder: record.NewFakeRecorder(1), 5638 Generators: map[string]generators.Generator{}, 5639 ArgoDB: argodb, 5640 KubeClientset: kubeclientset, 5641 Metrics: metrics, 5642 } 5643 5644 appStatuses, err := r.updateApplicationSetApplicationStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps, cc.appStepMap) 5645 5646 // opt out of testing the LastTransitionTime is accurate 5647 for i := range appStatuses { 5648 appStatuses[i].LastTransitionTime = nil 5649 } 5650 5651 require.NoError(t, err, "expected no errors, but errors occurred") 5652 assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual") 5653 }) 5654 } 5655 } 5656 5657 func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) { 5658 scheme := runtime.NewScheme() 5659 err := v1alpha1.AddToScheme(scheme) 5660 require.NoError(t, err) 5661 5662 for _, cc := range []struct { 5663 name string 5664 appSet v1alpha1.ApplicationSet 5665 appSyncMap map[string]bool 5666 appStepMap map[string]int 5667 appMap map[string]v1alpha1.Application 5668 expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus 5669 }{ 5670 { 5671 name: "handles an empty appSync and appStepMap", 5672 appSet: v1alpha1.ApplicationSet{ 5673 ObjectMeta: metav1.ObjectMeta{ 5674 Name: "name", 5675 Namespace: "argocd", 5676 }, 5677 Spec: v1alpha1.ApplicationSetSpec{ 5678 Strategy: &v1alpha1.ApplicationSetStrategy{ 5679 Type: "RollingSync", 5680 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5681 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5682 { 5683 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5684 }, 5685 { 5686 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5687 }, 5688 }, 5689 }, 5690 }, 5691 }, 5692 Status: v1alpha1.ApplicationSetStatus{ 5693 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5694 }, 5695 }, 5696 appSyncMap: map[string]bool{}, 5697 appStepMap: map[string]int{}, 5698 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5699 }, 5700 { 5701 name: "handles an empty strategy", 5702 appSet: v1alpha1.ApplicationSet{ 5703 ObjectMeta: metav1.ObjectMeta{ 5704 Name: "name", 5705 Namespace: "argocd", 5706 }, 5707 Spec: v1alpha1.ApplicationSetSpec{}, 5708 Status: v1alpha1.ApplicationSetStatus{ 5709 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5710 }, 5711 }, 5712 appSyncMap: map[string]bool{}, 5713 appStepMap: map[string]int{}, 5714 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5715 }, 5716 { 5717 name: "handles an empty applicationset strategy", 5718 appSet: v1alpha1.ApplicationSet{ 5719 ObjectMeta: metav1.ObjectMeta{ 5720 Name: "name", 5721 Namespace: "argocd", 5722 }, 5723 Spec: v1alpha1.ApplicationSetSpec{ 5724 Strategy: &v1alpha1.ApplicationSetStrategy{}, 5725 }, 5726 Status: v1alpha1.ApplicationSetStatus{ 5727 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5728 }, 5729 }, 5730 appSyncMap: map[string]bool{}, 5731 appStepMap: map[string]int{}, 5732 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5733 }, 5734 { 5735 name: "handles an appSyncMap with no existing statuses", 5736 appSet: v1alpha1.ApplicationSet{ 5737 ObjectMeta: metav1.ObjectMeta{ 5738 Name: "name", 5739 Namespace: "argocd", 5740 }, 5741 Status: v1alpha1.ApplicationSetStatus{ 5742 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5743 }, 5744 }, 5745 appSyncMap: map[string]bool{ 5746 "app1": true, 5747 "app2": false, 5748 }, 5749 appStepMap: map[string]int{ 5750 "app1": 0, 5751 "app2": 1, 5752 }, 5753 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5754 }, 5755 { 5756 name: "handles updating a RollingSync status from Waiting to Pending", 5757 appSet: v1alpha1.ApplicationSet{ 5758 ObjectMeta: metav1.ObjectMeta{ 5759 Name: "name", 5760 Namespace: "argocd", 5761 }, 5762 Spec: v1alpha1.ApplicationSetSpec{ 5763 Strategy: &v1alpha1.ApplicationSetStrategy{ 5764 Type: "RollingSync", 5765 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5766 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5767 { 5768 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5769 }, 5770 { 5771 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5772 }, 5773 }, 5774 }, 5775 }, 5776 }, 5777 Status: v1alpha1.ApplicationSetStatus{ 5778 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5779 { 5780 Application: "app1", 5781 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5782 Status: "Waiting", 5783 TargetRevisions: []string{"Next"}, 5784 }, 5785 }, 5786 }, 5787 }, 5788 appSyncMap: map[string]bool{ 5789 "app1": true, 5790 }, 5791 appStepMap: map[string]int{ 5792 "app1": 0, 5793 }, 5794 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5795 { 5796 Application: "app1", 5797 LastTransitionTime: nil, 5798 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5799 Status: "Pending", 5800 Step: "1", 5801 TargetRevisions: []string{"Next"}, 5802 }, 5803 }, 5804 }, 5805 { 5806 name: "does not update a RollingSync status if appSyncMap is false", 5807 appSet: v1alpha1.ApplicationSet{ 5808 ObjectMeta: metav1.ObjectMeta{ 5809 Name: "name", 5810 Namespace: "argocd", 5811 }, 5812 Spec: v1alpha1.ApplicationSetSpec{ 5813 Strategy: &v1alpha1.ApplicationSetStrategy{ 5814 Type: "RollingSync", 5815 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5816 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5817 { 5818 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5819 }, 5820 { 5821 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5822 }, 5823 }, 5824 }, 5825 }, 5826 }, 5827 Status: v1alpha1.ApplicationSetStatus{ 5828 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5829 { 5830 Application: "app1", 5831 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5832 Status: "Waiting", 5833 Step: "1", 5834 }, 5835 }, 5836 }, 5837 }, 5838 appSyncMap: map[string]bool{ 5839 "app1": false, 5840 }, 5841 appStepMap: map[string]int{ 5842 "app1": 0, 5843 }, 5844 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5845 { 5846 Application: "app1", 5847 LastTransitionTime: nil, 5848 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5849 Status: "Waiting", 5850 Step: "1", 5851 }, 5852 }, 5853 }, 5854 { 5855 name: "does not update a status if status is not pending", 5856 appSet: v1alpha1.ApplicationSet{ 5857 ObjectMeta: metav1.ObjectMeta{ 5858 Name: "name", 5859 Namespace: "argocd", 5860 }, 5861 Spec: v1alpha1.ApplicationSetSpec{ 5862 Strategy: &v1alpha1.ApplicationSetStrategy{ 5863 Type: "RollingSync", 5864 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5865 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5866 { 5867 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5868 }, 5869 { 5870 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5871 }, 5872 }, 5873 }, 5874 }, 5875 }, 5876 Status: v1alpha1.ApplicationSetStatus{ 5877 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5878 { 5879 Application: "app1", 5880 Message: "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.", 5881 Status: "Healthy", 5882 Step: "1", 5883 }, 5884 }, 5885 }, 5886 }, 5887 appSyncMap: map[string]bool{ 5888 "app1": true, 5889 }, 5890 appStepMap: map[string]int{ 5891 "app1": 0, 5892 }, 5893 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5894 { 5895 Application: "app1", 5896 LastTransitionTime: nil, 5897 Message: "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.", 5898 Status: "Healthy", 5899 Step: "1", 5900 }, 5901 }, 5902 }, 5903 { 5904 name: "does not update a status if maxUpdate has already been reached with RollingSync", 5905 appSet: v1alpha1.ApplicationSet{ 5906 ObjectMeta: metav1.ObjectMeta{ 5907 Name: "name", 5908 Namespace: "argocd", 5909 }, 5910 Spec: v1alpha1.ApplicationSetSpec{ 5911 Strategy: &v1alpha1.ApplicationSetStrategy{ 5912 Type: "RollingSync", 5913 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5914 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5915 { 5916 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5917 MaxUpdate: &intstr.IntOrString{ 5918 Type: intstr.Int, 5919 IntVal: 3, 5920 }, 5921 }, 5922 { 5923 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5924 }, 5925 }, 5926 }, 5927 }, 5928 }, 5929 Status: v1alpha1.ApplicationSetStatus{ 5930 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5931 { 5932 Application: "app1", 5933 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 5934 Status: "Progressing", 5935 Step: "1", 5936 }, 5937 { 5938 Application: "app2", 5939 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5940 Status: "Waiting", 5941 Step: "1", 5942 }, 5943 { 5944 Application: "app3", 5945 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5946 Status: "Waiting", 5947 Step: "1", 5948 }, 5949 { 5950 Application: "app4", 5951 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5952 Status: "Pending", 5953 Step: "1", 5954 }, 5955 }, 5956 }, 5957 }, 5958 appSyncMap: map[string]bool{ 5959 "app1": true, 5960 "app2": true, 5961 "app3": true, 5962 "app4": true, 5963 }, 5964 appStepMap: map[string]int{ 5965 "app1": 0, 5966 "app2": 0, 5967 "app3": 0, 5968 "app4": 0, 5969 }, 5970 appMap: map[string]v1alpha1.Application{ 5971 "app1": { 5972 ObjectMeta: metav1.ObjectMeta{ 5973 Name: "app1", 5974 }, 5975 Status: v1alpha1.ApplicationStatus{ 5976 Sync: v1alpha1.SyncStatus{ 5977 Status: v1alpha1.SyncStatusCodeOutOfSync, 5978 }, 5979 }, 5980 }, 5981 "app2": { 5982 ObjectMeta: metav1.ObjectMeta{ 5983 Name: "app2", 5984 }, 5985 Status: v1alpha1.ApplicationStatus{ 5986 Sync: v1alpha1.SyncStatus{ 5987 Status: v1alpha1.SyncStatusCodeOutOfSync, 5988 }, 5989 }, 5990 }, 5991 "app3": { 5992 ObjectMeta: metav1.ObjectMeta{ 5993 Name: "app3", 5994 }, 5995 Status: v1alpha1.ApplicationStatus{ 5996 Sync: v1alpha1.SyncStatus{ 5997 Status: v1alpha1.SyncStatusCodeOutOfSync, 5998 }, 5999 }, 6000 }, 6001 "app4": { 6002 ObjectMeta: metav1.ObjectMeta{ 6003 Name: "app4", 6004 }, 6005 Status: v1alpha1.ApplicationStatus{ 6006 Sync: v1alpha1.SyncStatus{ 6007 Status: v1alpha1.SyncStatusCodeOutOfSync, 6008 }, 6009 }, 6010 }, 6011 }, 6012 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6013 { 6014 Application: "app1", 6015 LastTransitionTime: nil, 6016 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 6017 Status: "Progressing", 6018 Step: "1", 6019 }, 6020 { 6021 Application: "app2", 6022 LastTransitionTime: nil, 6023 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6024 Status: "Pending", 6025 Step: "1", 6026 }, 6027 { 6028 Application: "app3", 6029 LastTransitionTime: nil, 6030 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6031 Status: "Waiting", 6032 Step: "1", 6033 }, 6034 { 6035 Application: "app4", 6036 LastTransitionTime: nil, 6037 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6038 Status: "Pending", 6039 Step: "1", 6040 }, 6041 }, 6042 }, 6043 { 6044 name: "rounds down for maxUpdate set to percentage string", 6045 appSet: v1alpha1.ApplicationSet{ 6046 ObjectMeta: metav1.ObjectMeta{ 6047 Name: "name", 6048 Namespace: "argocd", 6049 }, 6050 Spec: v1alpha1.ApplicationSetSpec{ 6051 Strategy: &v1alpha1.ApplicationSetStrategy{ 6052 Type: "RollingSync", 6053 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 6054 Steps: []v1alpha1.ApplicationSetRolloutStep{ 6055 { 6056 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6057 MaxUpdate: &intstr.IntOrString{ 6058 Type: intstr.String, 6059 StrVal: "50%", 6060 }, 6061 }, 6062 { 6063 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6064 }, 6065 }, 6066 }, 6067 }, 6068 }, 6069 Status: v1alpha1.ApplicationSetStatus{ 6070 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6071 { 6072 Application: "app1", 6073 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6074 Status: "Waiting", 6075 Step: "1", 6076 }, 6077 { 6078 Application: "app2", 6079 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6080 Status: "Waiting", 6081 Step: "1", 6082 }, 6083 { 6084 Application: "app3", 6085 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6086 Status: "Waiting", 6087 Step: "1", 6088 }, 6089 }, 6090 }, 6091 }, 6092 appSyncMap: map[string]bool{ 6093 "app1": true, 6094 "app2": true, 6095 "app3": true, 6096 }, 6097 appStepMap: map[string]int{ 6098 "app1": 0, 6099 "app2": 0, 6100 "app3": 0, 6101 }, 6102 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6103 { 6104 Application: "app1", 6105 LastTransitionTime: nil, 6106 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6107 Status: "Pending", 6108 Step: "1", 6109 }, 6110 { 6111 Application: "app2", 6112 LastTransitionTime: nil, 6113 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6114 Status: "Waiting", 6115 Step: "1", 6116 }, 6117 { 6118 Application: "app3", 6119 LastTransitionTime: nil, 6120 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6121 Status: "Waiting", 6122 Step: "1", 6123 }, 6124 }, 6125 }, 6126 { 6127 name: "does not update any applications with maxUpdate set to 0", 6128 appSet: v1alpha1.ApplicationSet{ 6129 ObjectMeta: metav1.ObjectMeta{ 6130 Name: "name", 6131 Namespace: "argocd", 6132 }, 6133 Spec: v1alpha1.ApplicationSetSpec{ 6134 Strategy: &v1alpha1.ApplicationSetStrategy{ 6135 Type: "RollingSync", 6136 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 6137 Steps: []v1alpha1.ApplicationSetRolloutStep{ 6138 { 6139 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6140 MaxUpdate: &intstr.IntOrString{ 6141 Type: intstr.Int, 6142 IntVal: 0, 6143 }, 6144 }, 6145 { 6146 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6147 }, 6148 }, 6149 }, 6150 }, 6151 }, 6152 Status: v1alpha1.ApplicationSetStatus{ 6153 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6154 { 6155 Application: "app1", 6156 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6157 Status: "Waiting", 6158 Step: "1", 6159 }, 6160 { 6161 Application: "app2", 6162 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6163 Status: "Waiting", 6164 Step: "1", 6165 }, 6166 { 6167 Application: "app3", 6168 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6169 Status: "Waiting", 6170 Step: "1", 6171 }, 6172 }, 6173 }, 6174 }, 6175 appSyncMap: map[string]bool{ 6176 "app1": true, 6177 "app2": true, 6178 "app3": true, 6179 }, 6180 appStepMap: map[string]int{ 6181 "app1": 0, 6182 "app2": 0, 6183 "app3": 0, 6184 }, 6185 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6186 { 6187 Application: "app1", 6188 LastTransitionTime: nil, 6189 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6190 Status: "Waiting", 6191 Step: "1", 6192 }, 6193 { 6194 Application: "app2", 6195 LastTransitionTime: nil, 6196 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6197 Status: "Waiting", 6198 Step: "1", 6199 }, 6200 { 6201 Application: "app3", 6202 LastTransitionTime: nil, 6203 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6204 Status: "Waiting", 6205 Step: "1", 6206 }, 6207 }, 6208 }, 6209 { 6210 name: "updates all applications with maxUpdate set to 100%", 6211 appSet: v1alpha1.ApplicationSet{ 6212 ObjectMeta: metav1.ObjectMeta{ 6213 Name: "name", 6214 Namespace: "argocd", 6215 }, 6216 Spec: v1alpha1.ApplicationSetSpec{ 6217 Strategy: &v1alpha1.ApplicationSetStrategy{ 6218 Type: "RollingSync", 6219 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 6220 Steps: []v1alpha1.ApplicationSetRolloutStep{ 6221 { 6222 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6223 MaxUpdate: &intstr.IntOrString{ 6224 Type: intstr.String, 6225 StrVal: "100%", 6226 }, 6227 }, 6228 { 6229 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6230 }, 6231 }, 6232 }, 6233 }, 6234 }, 6235 Status: v1alpha1.ApplicationSetStatus{ 6236 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6237 { 6238 Application: "app1", 6239 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6240 Status: "Waiting", 6241 Step: "1", 6242 }, 6243 { 6244 Application: "app2", 6245 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6246 Status: "Waiting", 6247 Step: "1", 6248 }, 6249 { 6250 Application: "app3", 6251 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6252 Status: "Waiting", 6253 Step: "1", 6254 }, 6255 }, 6256 }, 6257 }, 6258 appSyncMap: map[string]bool{ 6259 "app1": true, 6260 "app2": true, 6261 "app3": true, 6262 }, 6263 appStepMap: map[string]int{ 6264 "app1": 0, 6265 "app2": 0, 6266 "app3": 0, 6267 }, 6268 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6269 { 6270 Application: "app1", 6271 LastTransitionTime: nil, 6272 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6273 Status: "Pending", 6274 Step: "1", 6275 }, 6276 { 6277 Application: "app2", 6278 LastTransitionTime: nil, 6279 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6280 Status: "Pending", 6281 Step: "1", 6282 }, 6283 { 6284 Application: "app3", 6285 LastTransitionTime: nil, 6286 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6287 Status: "Pending", 6288 Step: "1", 6289 }, 6290 }, 6291 }, 6292 { 6293 name: "updates at least 1 application with maxUpdate >0%", 6294 appSet: v1alpha1.ApplicationSet{ 6295 ObjectMeta: metav1.ObjectMeta{ 6296 Name: "name", 6297 Namespace: "argocd", 6298 }, 6299 Spec: v1alpha1.ApplicationSetSpec{ 6300 Strategy: &v1alpha1.ApplicationSetStrategy{ 6301 Type: "RollingSync", 6302 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 6303 Steps: []v1alpha1.ApplicationSetRolloutStep{ 6304 { 6305 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6306 MaxUpdate: &intstr.IntOrString{ 6307 Type: intstr.String, 6308 StrVal: "1%", 6309 }, 6310 }, 6311 { 6312 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 6313 }, 6314 }, 6315 }, 6316 }, 6317 }, 6318 Status: v1alpha1.ApplicationSetStatus{ 6319 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6320 { 6321 Application: "app1", 6322 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6323 Status: "Waiting", 6324 Step: "1", 6325 }, 6326 { 6327 Application: "app2", 6328 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6329 Status: "Waiting", 6330 Step: "1", 6331 }, 6332 { 6333 Application: "app3", 6334 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6335 Status: "Waiting", 6336 Step: "1", 6337 }, 6338 }, 6339 }, 6340 }, 6341 appSyncMap: map[string]bool{ 6342 "app1": true, 6343 "app2": true, 6344 "app3": true, 6345 }, 6346 appStepMap: map[string]int{ 6347 "app1": 0, 6348 "app2": 0, 6349 "app3": 0, 6350 }, 6351 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6352 { 6353 Application: "app1", 6354 LastTransitionTime: nil, 6355 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 6356 Status: "Pending", 6357 Step: "1", 6358 }, 6359 { 6360 Application: "app2", 6361 LastTransitionTime: nil, 6362 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6363 Status: "Waiting", 6364 Step: "1", 6365 }, 6366 { 6367 Application: "app3", 6368 LastTransitionTime: nil, 6369 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 6370 Status: "Waiting", 6371 Step: "1", 6372 }, 6373 }, 6374 }, 6375 } { 6376 t.Run(cc.name, func(t *testing.T) { 6377 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 6378 6379 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build() 6380 metrics := appsetmetrics.NewFakeAppsetMetrics() 6381 6382 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 6383 6384 r := ApplicationSetReconciler{ 6385 Client: client, 6386 Scheme: scheme, 6387 Recorder: record.NewFakeRecorder(1), 6388 Generators: map[string]generators.Generator{}, 6389 ArgoDB: argodb, 6390 KubeClientset: kubeclientset, 6391 Metrics: metrics, 6392 } 6393 6394 appStatuses, err := r.updateApplicationSetApplicationStatusProgress(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appSyncMap, cc.appStepMap) 6395 6396 // opt out of testing the LastTransitionTime is accurate 6397 for i := range appStatuses { 6398 appStatuses[i].LastTransitionTime = nil 6399 } 6400 6401 require.NoError(t, err, "expected no errors, but errors occurred") 6402 assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual") 6403 }) 6404 } 6405 } 6406 6407 func TestUpdateResourceStatus(t *testing.T) { 6408 scheme := runtime.NewScheme() 6409 err := v1alpha1.AddToScheme(scheme) 6410 require.NoError(t, err) 6411 6412 for _, cc := range []struct { 6413 name string 6414 appSet v1alpha1.ApplicationSet 6415 apps []v1alpha1.Application 6416 expectedResources []v1alpha1.ResourceStatus 6417 maxResourcesStatusCount int 6418 }{ 6419 { 6420 name: "handles an empty application list", 6421 appSet: v1alpha1.ApplicationSet{ 6422 ObjectMeta: metav1.ObjectMeta{ 6423 Name: "name", 6424 Namespace: "argocd", 6425 }, 6426 Status: v1alpha1.ApplicationSetStatus{ 6427 Resources: []v1alpha1.ResourceStatus{}, 6428 }, 6429 }, 6430 apps: []v1alpha1.Application{}, 6431 expectedResources: nil, 6432 }, 6433 { 6434 name: "adds status if no existing statuses", 6435 appSet: v1alpha1.ApplicationSet{ 6436 ObjectMeta: metav1.ObjectMeta{ 6437 Name: "name", 6438 Namespace: "argocd", 6439 }, 6440 Status: v1alpha1.ApplicationSetStatus{ 6441 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 6442 }, 6443 }, 6444 apps: []v1alpha1.Application{ 6445 { 6446 ObjectMeta: metav1.ObjectMeta{ 6447 Name: "app1", 6448 }, 6449 Status: v1alpha1.ApplicationStatus{ 6450 Sync: v1alpha1.SyncStatus{ 6451 Status: v1alpha1.SyncStatusCodeSynced, 6452 }, 6453 Health: v1alpha1.AppHealthStatus{ 6454 Status: health.HealthStatusHealthy, 6455 }, 6456 }, 6457 }, 6458 }, 6459 expectedResources: []v1alpha1.ResourceStatus{ 6460 { 6461 Name: "app1", 6462 Status: v1alpha1.SyncStatusCodeSynced, 6463 Health: &v1alpha1.HealthStatus{ 6464 Status: health.HealthStatusHealthy, 6465 }, 6466 }, 6467 }, 6468 }, 6469 { 6470 name: "handles an applicationset with existing and up-to-date status", 6471 appSet: v1alpha1.ApplicationSet{ 6472 ObjectMeta: metav1.ObjectMeta{ 6473 Name: "name", 6474 Namespace: "argocd", 6475 }, 6476 Status: v1alpha1.ApplicationSetStatus{ 6477 Resources: []v1alpha1.ResourceStatus{ 6478 { 6479 Name: "app1", 6480 Status: v1alpha1.SyncStatusCodeSynced, 6481 Health: &v1alpha1.HealthStatus{ 6482 Status: health.HealthStatusHealthy, 6483 }, 6484 }, 6485 }, 6486 }, 6487 }, 6488 apps: []v1alpha1.Application{ 6489 { 6490 ObjectMeta: metav1.ObjectMeta{ 6491 Name: "app1", 6492 }, 6493 Status: v1alpha1.ApplicationStatus{ 6494 Sync: v1alpha1.SyncStatus{ 6495 Status: v1alpha1.SyncStatusCodeSynced, 6496 }, 6497 Health: v1alpha1.AppHealthStatus{ 6498 Status: health.HealthStatusHealthy, 6499 }, 6500 }, 6501 }, 6502 }, 6503 expectedResources: []v1alpha1.ResourceStatus{ 6504 { 6505 Name: "app1", 6506 Status: v1alpha1.SyncStatusCodeSynced, 6507 Health: &v1alpha1.HealthStatus{ 6508 Status: health.HealthStatusHealthy, 6509 }, 6510 }, 6511 }, 6512 }, 6513 { 6514 name: "updates an applicationset with existing and out of date status", 6515 appSet: v1alpha1.ApplicationSet{ 6516 ObjectMeta: metav1.ObjectMeta{ 6517 Name: "name", 6518 Namespace: "argocd", 6519 }, 6520 Status: v1alpha1.ApplicationSetStatus{ 6521 Resources: []v1alpha1.ResourceStatus{ 6522 { 6523 Name: "app1", 6524 Status: v1alpha1.SyncStatusCodeOutOfSync, 6525 Health: &v1alpha1.HealthStatus{ 6526 Status: health.HealthStatusProgressing, 6527 Message: "Progressing", 6528 }, 6529 }, 6530 }, 6531 }, 6532 }, 6533 apps: []v1alpha1.Application{ 6534 { 6535 ObjectMeta: metav1.ObjectMeta{ 6536 Name: "app1", 6537 }, 6538 Status: v1alpha1.ApplicationStatus{ 6539 Sync: v1alpha1.SyncStatus{ 6540 Status: v1alpha1.SyncStatusCodeSynced, 6541 }, 6542 Health: v1alpha1.AppHealthStatus{ 6543 Status: health.HealthStatusHealthy, 6544 }, 6545 }, 6546 }, 6547 }, 6548 expectedResources: []v1alpha1.ResourceStatus{ 6549 { 6550 Name: "app1", 6551 Status: v1alpha1.SyncStatusCodeSynced, 6552 Health: &v1alpha1.HealthStatus{ 6553 Status: health.HealthStatusHealthy, 6554 }, 6555 }, 6556 }, 6557 }, 6558 { 6559 name: "deletes an applicationset status if the application no longer exists", 6560 appSet: v1alpha1.ApplicationSet{ 6561 ObjectMeta: metav1.ObjectMeta{ 6562 Name: "name", 6563 Namespace: "argocd", 6564 }, 6565 Status: v1alpha1.ApplicationSetStatus{ 6566 Resources: []v1alpha1.ResourceStatus{ 6567 { 6568 Name: "app1", 6569 Status: v1alpha1.SyncStatusCodeSynced, 6570 Health: &v1alpha1.HealthStatus{ 6571 Status: health.HealthStatusHealthy, 6572 Message: "OK", 6573 }, 6574 }, 6575 }, 6576 }, 6577 }, 6578 apps: []v1alpha1.Application{}, 6579 expectedResources: nil, 6580 }, 6581 { 6582 name: "truncates resources status list to", 6583 appSet: v1alpha1.ApplicationSet{ 6584 ObjectMeta: metav1.ObjectMeta{ 6585 Name: "name", 6586 Namespace: "argocd", 6587 }, 6588 Status: v1alpha1.ApplicationSetStatus{ 6589 Resources: []v1alpha1.ResourceStatus{ 6590 { 6591 Name: "app1", 6592 Status: v1alpha1.SyncStatusCodeOutOfSync, 6593 Health: &v1alpha1.HealthStatus{ 6594 Status: health.HealthStatusProgressing, 6595 Message: "this is progressing", 6596 }, 6597 }, 6598 { 6599 Name: "app2", 6600 Status: v1alpha1.SyncStatusCodeOutOfSync, 6601 Health: &v1alpha1.HealthStatus{ 6602 Status: health.HealthStatusProgressing, 6603 Message: "this is progressing", 6604 }, 6605 }, 6606 }, 6607 }, 6608 }, 6609 apps: []v1alpha1.Application{ 6610 { 6611 ObjectMeta: metav1.ObjectMeta{ 6612 Name: "app1", 6613 }, 6614 Status: v1alpha1.ApplicationStatus{ 6615 Sync: v1alpha1.SyncStatus{ 6616 Status: v1alpha1.SyncStatusCodeSynced, 6617 }, 6618 Health: v1alpha1.AppHealthStatus{ 6619 Status: health.HealthStatusHealthy, 6620 }, 6621 }, 6622 }, 6623 { 6624 ObjectMeta: metav1.ObjectMeta{ 6625 Name: "app2", 6626 }, 6627 Status: v1alpha1.ApplicationStatus{ 6628 Sync: v1alpha1.SyncStatus{ 6629 Status: v1alpha1.SyncStatusCodeSynced, 6630 }, 6631 Health: v1alpha1.AppHealthStatus{ 6632 Status: health.HealthStatusHealthy, 6633 }, 6634 }, 6635 }, 6636 }, 6637 expectedResources: []v1alpha1.ResourceStatus{ 6638 { 6639 Name: "app1", 6640 Status: v1alpha1.SyncStatusCodeSynced, 6641 Health: &v1alpha1.HealthStatus{ 6642 Status: health.HealthStatusHealthy, 6643 }, 6644 }, 6645 }, 6646 maxResourcesStatusCount: 1, 6647 }, 6648 } { 6649 t.Run(cc.name, func(t *testing.T) { 6650 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 6651 6652 client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build() 6653 metrics := appsetmetrics.NewFakeAppsetMetrics() 6654 6655 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 6656 6657 r := ApplicationSetReconciler{ 6658 Client: client, 6659 Scheme: scheme, 6660 Recorder: record.NewFakeRecorder(1), 6661 Generators: map[string]generators.Generator{}, 6662 ArgoDB: argodb, 6663 KubeClientset: kubeclientset, 6664 Metrics: metrics, 6665 MaxResourcesStatusCount: cc.maxResourcesStatusCount, 6666 } 6667 6668 err := r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps) 6669 6670 require.NoError(t, err, "expected no errors, but errors occurred") 6671 assert.Equal(t, cc.expectedResources, cc.appSet.Status.Resources, "expected resources did not match actual") 6672 }) 6673 } 6674 } 6675 6676 func generateNAppResourceStatuses(n int) []v1alpha1.ResourceStatus { 6677 var r []v1alpha1.ResourceStatus 6678 for i := 0; i < n; i++ { 6679 r = append(r, v1alpha1.ResourceStatus{ 6680 Name: "app" + strconv.Itoa(i), 6681 Status: v1alpha1.SyncStatusCodeSynced, 6682 Health: &v1alpha1.HealthStatus{ 6683 Status: health.HealthStatusHealthy, 6684 }, 6685 }, 6686 ) 6687 } 6688 return r 6689 } 6690 6691 func generateNHealthyApps(n int) []v1alpha1.Application { 6692 var r []v1alpha1.Application 6693 for i := 0; i < n; i++ { 6694 r = append(r, v1alpha1.Application{ 6695 ObjectMeta: metav1.ObjectMeta{ 6696 Name: "app" + strconv.Itoa(i), 6697 }, 6698 Status: v1alpha1.ApplicationStatus{ 6699 Sync: v1alpha1.SyncStatus{ 6700 Status: v1alpha1.SyncStatusCodeSynced, 6701 }, 6702 Health: v1alpha1.AppHealthStatus{ 6703 Status: health.HealthStatusHealthy, 6704 }, 6705 }, 6706 }) 6707 } 6708 return r 6709 } 6710 6711 func TestResourceStatusAreOrdered(t *testing.T) { 6712 scheme := runtime.NewScheme() 6713 err := v1alpha1.AddToScheme(scheme) 6714 require.NoError(t, err) 6715 6716 err = v1alpha1.AddToScheme(scheme) 6717 require.NoError(t, err) 6718 for _, cc := range []struct { 6719 name string 6720 appSet v1alpha1.ApplicationSet 6721 apps []v1alpha1.Application 6722 expectedResources []v1alpha1.ResourceStatus 6723 }{ 6724 { 6725 name: "Ensures AppSet is always ordered", 6726 appSet: v1alpha1.ApplicationSet{ 6727 ObjectMeta: metav1.ObjectMeta{ 6728 Name: "name", 6729 Namespace: "argocd", 6730 }, 6731 Status: v1alpha1.ApplicationSetStatus{ 6732 Resources: []v1alpha1.ResourceStatus{}, 6733 }, 6734 }, 6735 apps: generateNHealthyApps(10), 6736 expectedResources: generateNAppResourceStatuses(10), 6737 }, 6738 } { 6739 t.Run(cc.name, func(t *testing.T) { 6740 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 6741 6742 client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build() 6743 metrics := appsetmetrics.NewFakeAppsetMetrics() 6744 6745 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 6746 6747 r := ApplicationSetReconciler{ 6748 Client: client, 6749 Scheme: scheme, 6750 Recorder: record.NewFakeRecorder(1), 6751 Generators: map[string]generators.Generator{}, 6752 ArgoDB: argodb, 6753 KubeClientset: kubeclientset, 6754 Metrics: metrics, 6755 } 6756 6757 err := r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps) 6758 require.NoError(t, err, "expected no errors, but errors occurred") 6759 6760 err = r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps) 6761 require.NoError(t, err, "expected no errors, but errors occurred") 6762 6763 err = r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps) 6764 require.NoError(t, err, "expected no errors, but errors occurred") 6765 6766 assert.Equal(t, cc.expectedResources, cc.appSet.Status.Resources, "expected resources did not match actual") 6767 }) 6768 } 6769 } 6770 6771 func TestApplicationOwnsHandler(t *testing.T) { 6772 // progressive syncs do not affect create, delete, or generic 6773 ownsHandler := getApplicationOwnsHandler(true) 6774 assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{})) 6775 assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{})) 6776 assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{})) 6777 ownsHandler = getApplicationOwnsHandler(false) 6778 assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{})) 6779 assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{})) 6780 assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{})) 6781 6782 now := metav1.Now() 6783 type args struct { 6784 e event.UpdateEvent 6785 enableProgressiveSyncs bool 6786 } 6787 tests := []struct { 6788 name string 6789 args args 6790 want bool 6791 }{ 6792 {name: "SameApplicationReconciledAtDiff", args: args{e: event.UpdateEvent{ 6793 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}}, 6794 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}}, 6795 }}, want: false}, 6796 {name: "SameApplicationResourceVersionDiff", args: args{e: event.UpdateEvent{ 6797 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6798 ResourceVersion: "foo", 6799 }}, 6800 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6801 ResourceVersion: "bar", 6802 }}, 6803 }}, want: false}, 6804 {name: "ApplicationHealthStatusDiff", args: args{ 6805 e: event.UpdateEvent{ 6806 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6807 Health: v1alpha1.AppHealthStatus{ 6808 Status: "Unknown", 6809 }, 6810 }}, 6811 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6812 Health: v1alpha1.AppHealthStatus{ 6813 Status: "Healthy", 6814 }, 6815 }}, 6816 }, 6817 enableProgressiveSyncs: true, 6818 }, want: true}, 6819 {name: "ApplicationSyncStatusDiff", args: args{ 6820 e: event.UpdateEvent{ 6821 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6822 Sync: v1alpha1.SyncStatus{ 6823 Status: "OutOfSync", 6824 }, 6825 }}, 6826 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6827 Sync: v1alpha1.SyncStatus{ 6828 Status: "Synced", 6829 }, 6830 }}, 6831 }, 6832 enableProgressiveSyncs: true, 6833 }, want: true}, 6834 {name: "ApplicationOperationStateDiff", args: args{ 6835 e: event.UpdateEvent{ 6836 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6837 OperationState: &v1alpha1.OperationState{ 6838 Phase: "foo", 6839 }, 6840 }}, 6841 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6842 OperationState: &v1alpha1.OperationState{ 6843 Phase: "bar", 6844 }, 6845 }}, 6846 }, 6847 enableProgressiveSyncs: true, 6848 }, want: true}, 6849 {name: "ApplicationOperationStartedAtDiff", args: args{ 6850 e: event.UpdateEvent{ 6851 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6852 OperationState: &v1alpha1.OperationState{ 6853 StartedAt: now, 6854 }, 6855 }}, 6856 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6857 OperationState: &v1alpha1.OperationState{ 6858 StartedAt: metav1.NewTime(now.Add(time.Minute * 1)), 6859 }, 6860 }}, 6861 }, 6862 enableProgressiveSyncs: true, 6863 }, want: true}, 6864 {name: "SameApplicationGeneration", args: args{e: event.UpdateEvent{ 6865 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6866 Generation: 1, 6867 }}, 6868 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6869 Generation: 2, 6870 }}, 6871 }}, want: false}, 6872 {name: "DifferentApplicationSpec", args: args{e: event.UpdateEvent{ 6873 ObjectOld: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "default"}}, 6874 ObjectNew: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "not-default"}}, 6875 }}, want: true}, 6876 {name: "DifferentApplicationLabels", args: args{e: event.UpdateEvent{ 6877 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}}, 6878 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}}, 6879 }}, want: true}, 6880 {name: "DifferentApplicationLabelsNil", args: args{e: event.UpdateEvent{ 6881 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}}, 6882 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: nil}}, 6883 }}, want: false}, 6884 {name: "DifferentApplicationAnnotations", args: args{e: event.UpdateEvent{ 6885 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "bar"}}}, 6886 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"bar": "foo"}}}, 6887 }}, want: true}, 6888 {name: "DifferentApplicationAnnotationsNil", args: args{e: event.UpdateEvent{ 6889 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}, 6890 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: nil}}, 6891 }}, want: false}, 6892 {name: "DifferentApplicationFinalizers", args: args{e: event.UpdateEvent{ 6893 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"argo"}}}, 6894 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"none"}}}, 6895 }}, want: true}, 6896 {name: "DifferentApplicationFinalizersNil", args: args{e: event.UpdateEvent{ 6897 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}}, 6898 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: nil}}, 6899 }}, want: false}, 6900 {name: "ApplicationDestinationSame", args: args{ 6901 e: event.UpdateEvent{ 6902 ObjectOld: &v1alpha1.Application{ 6903 Spec: v1alpha1.ApplicationSpec{ 6904 Destination: v1alpha1.ApplicationDestination{ 6905 Server: "server", 6906 Namespace: "ns", 6907 Name: "name", 6908 }, 6909 }, 6910 }, 6911 ObjectNew: &v1alpha1.Application{ 6912 Spec: v1alpha1.ApplicationSpec{ 6913 Destination: v1alpha1.ApplicationDestination{ 6914 Server: "server", 6915 Namespace: "ns", 6916 Name: "name", 6917 }, 6918 }, 6919 }, 6920 }, 6921 enableProgressiveSyncs: true, 6922 }, want: false}, 6923 {name: "ApplicationDestinationDiff", args: args{ 6924 e: event.UpdateEvent{ 6925 ObjectOld: &v1alpha1.Application{ 6926 Spec: v1alpha1.ApplicationSpec{ 6927 Destination: v1alpha1.ApplicationDestination{ 6928 Server: "server", 6929 Namespace: "ns", 6930 Name: "name", 6931 }, 6932 }, 6933 }, 6934 ObjectNew: &v1alpha1.Application{ 6935 Spec: v1alpha1.ApplicationSpec{ 6936 Destination: v1alpha1.ApplicationDestination{ 6937 Server: "notSameServer", 6938 Namespace: "ns", 6939 Name: "name", 6940 }, 6941 }, 6942 }, 6943 }, 6944 enableProgressiveSyncs: true, 6945 }, want: true}, 6946 {name: "NotAnAppOld", args: args{e: event.UpdateEvent{ 6947 ObjectOld: &v1alpha1.AppProject{}, 6948 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}}, 6949 }}, want: false}, 6950 {name: "NotAnAppNew", args: args{e: event.UpdateEvent{ 6951 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}}, 6952 ObjectNew: &v1alpha1.AppProject{}, 6953 }}, want: false}, 6954 } 6955 for _, tt := range tests { 6956 t.Run(tt.name, func(t *testing.T) { 6957 ownsHandler = getApplicationOwnsHandler(tt.args.enableProgressiveSyncs) 6958 assert.Equalf(t, tt.want, ownsHandler.UpdateFunc(tt.args.e), "UpdateFunc(%v)", tt.args.e) 6959 }) 6960 } 6961 } 6962 6963 func TestMigrateStatus(t *testing.T) { 6964 scheme := runtime.NewScheme() 6965 err := v1alpha1.AddToScheme(scheme) 6966 require.NoError(t, err) 6967 6968 err = v1alpha1.AddToScheme(scheme) 6969 require.NoError(t, err) 6970 6971 for _, tc := range []struct { 6972 name string 6973 appset v1alpha1.ApplicationSet 6974 expectedStatus v1alpha1.ApplicationSetStatus 6975 }{ 6976 { 6977 name: "status without applicationstatus target revisions set will default to empty list", 6978 appset: v1alpha1.ApplicationSet{ 6979 ObjectMeta: metav1.ObjectMeta{ 6980 Name: "test", 6981 Namespace: "test", 6982 }, 6983 Status: v1alpha1.ApplicationSetStatus{ 6984 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6985 {}, 6986 }, 6987 }, 6988 }, 6989 expectedStatus: v1alpha1.ApplicationSetStatus{ 6990 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 6991 { 6992 TargetRevisions: []string{}, 6993 }, 6994 }, 6995 }, 6996 }, 6997 { 6998 name: "status with applicationstatus target revisions set will do nothing", 6999 appset: v1alpha1.ApplicationSet{ 7000 ObjectMeta: metav1.ObjectMeta{ 7001 Name: "test", 7002 Namespace: "test", 7003 }, 7004 Status: v1alpha1.ApplicationSetStatus{ 7005 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7006 { 7007 TargetRevisions: []string{"Current"}, 7008 }, 7009 }, 7010 }, 7011 }, 7012 expectedStatus: v1alpha1.ApplicationSetStatus{ 7013 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7014 { 7015 TargetRevisions: []string{"Current"}, 7016 }, 7017 }, 7018 }, 7019 }, 7020 } { 7021 t.Run(tc.name, func(t *testing.T) { 7022 client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&tc.appset).WithObjects(&tc.appset).Build() 7023 r := ApplicationSetReconciler{ 7024 Client: client, 7025 } 7026 7027 err := r.migrateStatus(t.Context(), &tc.appset) 7028 require.NoError(t, err) 7029 assert.Equal(t, tc.expectedStatus, tc.appset.Status) 7030 }) 7031 } 7032 } 7033 7034 func TestApplicationSetOwnsHandlerUpdate(t *testing.T) { 7035 buildAppSet := func(annotations map[string]string) *v1alpha1.ApplicationSet { 7036 return &v1alpha1.ApplicationSet{ 7037 ObjectMeta: metav1.ObjectMeta{ 7038 Annotations: annotations, 7039 }, 7040 } 7041 } 7042 7043 tests := []struct { 7044 name string 7045 appSetOld crtclient.Object 7046 appSetNew crtclient.Object 7047 enableProgressiveSyncs bool 7048 want bool 7049 }{ 7050 { 7051 name: "Different Spec", 7052 appSetOld: &v1alpha1.ApplicationSet{ 7053 Spec: v1alpha1.ApplicationSetSpec{ 7054 Generators: []v1alpha1.ApplicationSetGenerator{ 7055 {List: &v1alpha1.ListGenerator{}}, 7056 }, 7057 }, 7058 }, 7059 appSetNew: &v1alpha1.ApplicationSet{ 7060 Spec: v1alpha1.ApplicationSetSpec{ 7061 Generators: []v1alpha1.ApplicationSetGenerator{ 7062 {Git: &v1alpha1.GitGenerator{}}, 7063 }, 7064 }, 7065 }, 7066 enableProgressiveSyncs: false, 7067 want: true, 7068 }, 7069 { 7070 name: "Different Annotations", 7071 appSetOld: buildAppSet(map[string]string{"key1": "value1"}), 7072 appSetNew: buildAppSet(map[string]string{"key1": "value2"}), 7073 enableProgressiveSyncs: false, 7074 want: true, 7075 }, 7076 { 7077 name: "Different Labels", 7078 appSetOld: &v1alpha1.ApplicationSet{ 7079 ObjectMeta: metav1.ObjectMeta{ 7080 Labels: map[string]string{"key1": "value1"}, 7081 }, 7082 }, 7083 appSetNew: &v1alpha1.ApplicationSet{ 7084 ObjectMeta: metav1.ObjectMeta{ 7085 Labels: map[string]string{"key1": "value2"}, 7086 }, 7087 }, 7088 enableProgressiveSyncs: false, 7089 want: true, 7090 }, 7091 { 7092 name: "Different Finalizers", 7093 appSetOld: &v1alpha1.ApplicationSet{ 7094 ObjectMeta: metav1.ObjectMeta{ 7095 Finalizers: []string{"finalizer1"}, 7096 }, 7097 }, 7098 appSetNew: &v1alpha1.ApplicationSet{ 7099 ObjectMeta: metav1.ObjectMeta{ 7100 Finalizers: []string{"finalizer2"}, 7101 }, 7102 }, 7103 enableProgressiveSyncs: false, 7104 want: true, 7105 }, 7106 { 7107 name: "No Changes", 7108 appSetOld: &v1alpha1.ApplicationSet{ 7109 Spec: v1alpha1.ApplicationSetSpec{ 7110 Generators: []v1alpha1.ApplicationSetGenerator{ 7111 {List: &v1alpha1.ListGenerator{}}, 7112 }, 7113 }, 7114 ObjectMeta: metav1.ObjectMeta{ 7115 Annotations: map[string]string{"key1": "value1"}, 7116 Labels: map[string]string{"key1": "value1"}, 7117 Finalizers: []string{"finalizer1"}, 7118 }, 7119 }, 7120 appSetNew: &v1alpha1.ApplicationSet{ 7121 Spec: v1alpha1.ApplicationSetSpec{ 7122 Generators: []v1alpha1.ApplicationSetGenerator{ 7123 {List: &v1alpha1.ListGenerator{}}, 7124 }, 7125 }, 7126 ObjectMeta: metav1.ObjectMeta{ 7127 Annotations: map[string]string{"key1": "value1"}, 7128 Labels: map[string]string{"key1": "value1"}, 7129 Finalizers: []string{"finalizer1"}, 7130 }, 7131 }, 7132 enableProgressiveSyncs: false, 7133 want: false, 7134 }, 7135 { 7136 name: "annotation removed", 7137 appSetOld: buildAppSet(map[string]string{ 7138 argocommon.AnnotationApplicationSetRefresh: "true", 7139 }), 7140 appSetNew: buildAppSet(map[string]string{}), 7141 enableProgressiveSyncs: false, 7142 want: false, 7143 }, 7144 { 7145 name: "annotation not removed", 7146 appSetOld: buildAppSet(map[string]string{ 7147 argocommon.AnnotationApplicationSetRefresh: "true", 7148 }), 7149 appSetNew: buildAppSet(map[string]string{ 7150 argocommon.AnnotationApplicationSetRefresh: "true", 7151 }), 7152 enableProgressiveSyncs: false, 7153 want: false, 7154 }, 7155 { 7156 name: "annotation added", 7157 appSetOld: buildAppSet(map[string]string{}), 7158 appSetNew: buildAppSet(map[string]string{ 7159 argocommon.AnnotationApplicationSetRefresh: "true", 7160 }), 7161 enableProgressiveSyncs: false, 7162 want: true, 7163 }, 7164 { 7165 name: "old object is not an appset", 7166 appSetOld: &v1alpha1.Application{}, 7167 appSetNew: buildAppSet(map[string]string{}), 7168 enableProgressiveSyncs: false, 7169 want: false, 7170 }, 7171 { 7172 name: "new object is not an appset", 7173 appSetOld: buildAppSet(map[string]string{}), 7174 appSetNew: &v1alpha1.Application{}, 7175 enableProgressiveSyncs: false, 7176 want: false, 7177 }, 7178 { 7179 name: "deletionTimestamp present when progressive sync enabled", 7180 appSetOld: buildAppSet(map[string]string{}), 7181 appSetNew: &v1alpha1.ApplicationSet{ 7182 ObjectMeta: metav1.ObjectMeta{ 7183 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 7184 }, 7185 }, 7186 enableProgressiveSyncs: true, 7187 want: true, 7188 }, 7189 { 7190 name: "deletionTimestamp present when progressive sync disabled", 7191 appSetOld: buildAppSet(map[string]string{}), 7192 appSetNew: &v1alpha1.ApplicationSet{ 7193 ObjectMeta: metav1.ObjectMeta{ 7194 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 7195 }, 7196 }, 7197 enableProgressiveSyncs: false, 7198 want: true, 7199 }, 7200 } 7201 7202 for _, tt := range tests { 7203 t.Run(tt.name, func(t *testing.T) { 7204 ownsHandler := getApplicationSetOwnsHandler(tt.enableProgressiveSyncs) 7205 requeue := ownsHandler.UpdateFunc(event.UpdateEvent{ 7206 ObjectOld: tt.appSetOld, 7207 ObjectNew: tt.appSetNew, 7208 }) 7209 assert.Equalf(t, tt.want, requeue, "ownsHandler.UpdateFunc(%v, %v, %t)", tt.appSetOld, tt.appSetNew, tt.enableProgressiveSyncs) 7210 }) 7211 } 7212 } 7213 7214 func TestApplicationSetOwnsHandlerGeneric(t *testing.T) { 7215 ownsHandler := getApplicationSetOwnsHandler(false) 7216 tests := []struct { 7217 name string 7218 obj crtclient.Object 7219 want bool 7220 }{ 7221 { 7222 name: "Object is ApplicationSet", 7223 obj: &v1alpha1.ApplicationSet{}, 7224 want: true, 7225 }, 7226 { 7227 name: "Object is not ApplicationSet", 7228 obj: &v1alpha1.Application{}, 7229 want: false, 7230 }, 7231 } 7232 7233 for _, tt := range tests { 7234 t.Run(tt.name, func(t *testing.T) { 7235 requeue := ownsHandler.GenericFunc(event.GenericEvent{ 7236 Object: tt.obj, 7237 }) 7238 assert.Equalf(t, tt.want, requeue, "ownsHandler.GenericFunc(%v)", tt.obj) 7239 }) 7240 } 7241 } 7242 7243 func TestApplicationSetOwnsHandlerCreate(t *testing.T) { 7244 ownsHandler := getApplicationSetOwnsHandler(false) 7245 tests := []struct { 7246 name string 7247 obj crtclient.Object 7248 want bool 7249 }{ 7250 { 7251 name: "Object is ApplicationSet", 7252 obj: &v1alpha1.ApplicationSet{}, 7253 want: true, 7254 }, 7255 { 7256 name: "Object is not ApplicationSet", 7257 obj: &v1alpha1.Application{}, 7258 want: false, 7259 }, 7260 } 7261 7262 for _, tt := range tests { 7263 t.Run(tt.name, func(t *testing.T) { 7264 requeue := ownsHandler.CreateFunc(event.CreateEvent{ 7265 Object: tt.obj, 7266 }) 7267 assert.Equalf(t, tt.want, requeue, "ownsHandler.CreateFunc(%v)", tt.obj) 7268 }) 7269 } 7270 } 7271 7272 func TestApplicationSetOwnsHandlerDelete(t *testing.T) { 7273 ownsHandler := getApplicationSetOwnsHandler(false) 7274 tests := []struct { 7275 name string 7276 obj crtclient.Object 7277 want bool 7278 }{ 7279 { 7280 name: "Object is ApplicationSet", 7281 obj: &v1alpha1.ApplicationSet{}, 7282 want: true, 7283 }, 7284 { 7285 name: "Object is not ApplicationSet", 7286 obj: &v1alpha1.Application{}, 7287 want: false, 7288 }, 7289 } 7290 7291 for _, tt := range tests { 7292 t.Run(tt.name, func(t *testing.T) { 7293 requeue := ownsHandler.DeleteFunc(event.DeleteEvent{ 7294 Object: tt.obj, 7295 }) 7296 assert.Equalf(t, tt.want, requeue, "ownsHandler.DeleteFunc(%v)", tt.obj) 7297 }) 7298 } 7299 } 7300 7301 func TestShouldRequeueForApplicationSet(t *testing.T) { 7302 type args struct { 7303 appSetOld *v1alpha1.ApplicationSet 7304 appSetNew *v1alpha1.ApplicationSet 7305 enableProgressiveSyncs bool 7306 } 7307 tests := []struct { 7308 name string 7309 args args 7310 want bool 7311 }{ 7312 { 7313 name: "NilAppSet", 7314 args: args{ 7315 appSetNew: &v1alpha1.ApplicationSet{}, 7316 appSetOld: nil, 7317 enableProgressiveSyncs: false, 7318 }, 7319 want: false, 7320 }, 7321 { 7322 name: "ApplicationSetApplicationStatusChanged", 7323 args: args{ 7324 appSetOld: &v1alpha1.ApplicationSet{ 7325 Status: v1alpha1.ApplicationSetStatus{ 7326 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7327 { 7328 Application: "app1", 7329 Status: "Healthy", 7330 }, 7331 }, 7332 }, 7333 }, 7334 appSetNew: &v1alpha1.ApplicationSet{ 7335 Status: v1alpha1.ApplicationSetStatus{ 7336 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7337 { 7338 Application: "app1", 7339 Status: "Waiting", 7340 }, 7341 }, 7342 }, 7343 }, 7344 enableProgressiveSyncs: true, 7345 }, 7346 want: true, 7347 }, 7348 { 7349 name: "ApplicationSetWithDeletionTimestamp", 7350 args: args{ 7351 appSetOld: &v1alpha1.ApplicationSet{ 7352 Status: v1alpha1.ApplicationSetStatus{ 7353 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7354 { 7355 Application: "app1", 7356 Status: "Healthy", 7357 }, 7358 }, 7359 }, 7360 }, 7361 appSetNew: &v1alpha1.ApplicationSet{ 7362 ObjectMeta: metav1.ObjectMeta{ 7363 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 7364 }, 7365 Status: v1alpha1.ApplicationSetStatus{ 7366 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7367 { 7368 Application: "app1", 7369 Status: "Waiting", 7370 }, 7371 }, 7372 }, 7373 }, 7374 enableProgressiveSyncs: false, 7375 }, 7376 want: true, 7377 }, 7378 } 7379 for _, tt := range tests { 7380 t.Run(tt.name, func(t *testing.T) { 7381 assert.Equalf(t, tt.want, shouldRequeueForApplicationSet(tt.args.appSetOld, tt.args.appSetNew, tt.args.enableProgressiveSyncs), "shouldRequeueForApplicationSet(%v, %v)", tt.args.appSetOld, tt.args.appSetNew) 7382 }) 7383 } 7384 } 7385 7386 func TestIgnoreNotAllowedNamespaces(t *testing.T) { 7387 tests := []struct { 7388 name string 7389 namespaces []string 7390 objectNS string 7391 expected bool 7392 }{ 7393 { 7394 name: "Namespace allowed", 7395 namespaces: []string{"allowed-namespace"}, 7396 objectNS: "allowed-namespace", 7397 expected: true, 7398 }, 7399 { 7400 name: "Namespace not allowed", 7401 namespaces: []string{"allowed-namespace"}, 7402 objectNS: "not-allowed-namespace", 7403 expected: false, 7404 }, 7405 { 7406 name: "Empty allowed namespaces", 7407 namespaces: []string{}, 7408 objectNS: "any-namespace", 7409 expected: false, 7410 }, 7411 { 7412 name: "Multiple allowed namespaces", 7413 namespaces: []string{"allowed-namespace-1", "allowed-namespace-2"}, 7414 objectNS: "allowed-namespace-2", 7415 expected: true, 7416 }, 7417 { 7418 name: "Namespace not in multiple allowed namespaces", 7419 namespaces: []string{"allowed-namespace-1", "allowed-namespace-2"}, 7420 objectNS: "not-allowed-namespace", 7421 expected: false, 7422 }, 7423 { 7424 name: "Namespace matched by glob pattern", 7425 namespaces: []string{"allowed-namespace-*"}, 7426 objectNS: "allowed-namespace-1", 7427 expected: true, 7428 }, 7429 { 7430 name: "Namespace matched by regex pattern", 7431 namespaces: []string{"/^allowed-namespace-[^-]+$/"}, 7432 objectNS: "allowed-namespace-1", 7433 expected: true, 7434 }, 7435 } 7436 7437 for _, tt := range tests { 7438 t.Run(tt.name, func(t *testing.T) { 7439 predicate := ignoreNotAllowedNamespaces(tt.namespaces) 7440 object := &v1alpha1.ApplicationSet{ 7441 ObjectMeta: metav1.ObjectMeta{ 7442 Namespace: tt.objectNS, 7443 }, 7444 } 7445 7446 t.Run(tt.name+":Create", func(t *testing.T) { 7447 result := predicate.Create(event.CreateEvent{Object: object}) 7448 assert.Equal(t, tt.expected, result) 7449 }) 7450 7451 t.Run(tt.name+":Update", func(t *testing.T) { 7452 result := predicate.Update(event.UpdateEvent{ObjectNew: object}) 7453 assert.Equal(t, tt.expected, result) 7454 }) 7455 7456 t.Run(tt.name+":Delete", func(t *testing.T) { 7457 result := predicate.Delete(event.DeleteEvent{Object: object}) 7458 assert.Equal(t, tt.expected, result) 7459 }) 7460 7461 t.Run(tt.name+":Generic", func(t *testing.T) { 7462 result := predicate.Generic(event.GenericEvent{Object: object}) 7463 assert.Equal(t, tt.expected, result) 7464 }) 7465 }) 7466 } 7467 } 7468 7469 func TestIsRollingSyncStrategy(t *testing.T) { 7470 tests := []struct { 7471 name string 7472 appset *v1alpha1.ApplicationSet 7473 expected bool 7474 }{ 7475 { 7476 name: "RollingSync strategy is explicitly set", 7477 appset: &v1alpha1.ApplicationSet{ 7478 Spec: v1alpha1.ApplicationSetSpec{ 7479 Strategy: &v1alpha1.ApplicationSetStrategy{ 7480 Type: "RollingSync", 7481 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 7482 Steps: []v1alpha1.ApplicationSetRolloutStep{}, 7483 }, 7484 }, 7485 }, 7486 }, 7487 expected: true, 7488 }, 7489 { 7490 name: "AllAtOnce strategy is explicitly set", 7491 appset: &v1alpha1.ApplicationSet{ 7492 Spec: v1alpha1.ApplicationSetSpec{ 7493 Strategy: &v1alpha1.ApplicationSetStrategy{ 7494 Type: "AllAtOnce", 7495 }, 7496 }, 7497 }, 7498 expected: false, 7499 }, 7500 { 7501 name: "Strategy is empty", 7502 appset: &v1alpha1.ApplicationSet{ 7503 Spec: v1alpha1.ApplicationSetSpec{ 7504 Strategy: &v1alpha1.ApplicationSetStrategy{}, 7505 }, 7506 }, 7507 expected: false, 7508 }, 7509 { 7510 name: "Strategy is nil", 7511 appset: &v1alpha1.ApplicationSet{ 7512 Spec: v1alpha1.ApplicationSetSpec{ 7513 Strategy: nil, 7514 }, 7515 }, 7516 expected: false, 7517 }, 7518 } 7519 7520 for _, tt := range tests { 7521 t.Run(tt.name, func(t *testing.T) { 7522 result := isRollingSyncStrategy(tt.appset) 7523 assert.Equal(t, tt.expected, result) 7524 }) 7525 } 7526 } 7527 7528 func TestSyncApplication(t *testing.T) { 7529 tests := []struct { 7530 name string 7531 input v1alpha1.Application 7532 prune bool 7533 expected v1alpha1.Application 7534 }{ 7535 { 7536 name: "Default retry limit with no SyncPolicy", 7537 input: v1alpha1.Application{ 7538 Spec: v1alpha1.ApplicationSpec{}, 7539 }, 7540 prune: false, 7541 expected: v1alpha1.Application{ 7542 Spec: v1alpha1.ApplicationSpec{}, 7543 Operation: &v1alpha1.Operation{ 7544 InitiatedBy: v1alpha1.OperationInitiator{ 7545 Username: "applicationset-controller", 7546 Automated: true, 7547 }, 7548 Info: []*v1alpha1.Info{ 7549 { 7550 Name: "Reason", 7551 Value: "ApplicationSet RollingSync triggered a sync of this Application resource", 7552 }, 7553 }, 7554 Sync: &v1alpha1.SyncOperation{ 7555 Prune: false, 7556 }, 7557 Retry: v1alpha1.RetryStrategy{ 7558 Limit: 5, 7559 }, 7560 }, 7561 }, 7562 }, 7563 { 7564 name: "Retry and SyncOptions from SyncPolicy are applied", 7565 input: v1alpha1.Application{ 7566 Spec: v1alpha1.ApplicationSpec{ 7567 SyncPolicy: &v1alpha1.SyncPolicy{ 7568 Retry: &v1alpha1.RetryStrategy{ 7569 Limit: 10, 7570 }, 7571 SyncOptions: []string{"CreateNamespace=true"}, 7572 }, 7573 }, 7574 }, 7575 prune: true, 7576 expected: v1alpha1.Application{ 7577 Spec: v1alpha1.ApplicationSpec{ 7578 SyncPolicy: &v1alpha1.SyncPolicy{ 7579 Retry: &v1alpha1.RetryStrategy{ 7580 Limit: 10, 7581 }, 7582 SyncOptions: []string{"CreateNamespace=true"}, 7583 }, 7584 }, 7585 Operation: &v1alpha1.Operation{ 7586 InitiatedBy: v1alpha1.OperationInitiator{ 7587 Username: "applicationset-controller", 7588 Automated: true, 7589 }, 7590 Info: []*v1alpha1.Info{ 7591 { 7592 Name: "Reason", 7593 Value: "ApplicationSet RollingSync triggered a sync of this Application resource", 7594 }, 7595 }, 7596 Sync: &v1alpha1.SyncOperation{ 7597 SyncOptions: []string{"CreateNamespace=true"}, 7598 Prune: true, 7599 }, 7600 Retry: v1alpha1.RetryStrategy{ 7601 Limit: 10, 7602 }, 7603 }, 7604 }, 7605 }, 7606 } 7607 7608 for _, tt := range tests { 7609 t.Run(tt.name, func(t *testing.T) { 7610 result := syncApplication(tt.input, tt.prune) 7611 assert.Equal(t, tt.expected, result) 7612 }) 7613 } 7614 } 7615 7616 func TestReconcileProgressiveSyncDisabled(t *testing.T) { 7617 scheme := runtime.NewScheme() 7618 err := v1alpha1.AddToScheme(scheme) 7619 require.NoError(t, err) 7620 7621 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 7622 7623 for _, cc := range []struct { 7624 name string 7625 appSet v1alpha1.ApplicationSet 7626 enableProgressiveSyncs bool 7627 expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus 7628 }{ 7629 { 7630 name: "clears applicationStatus when Progressive Sync is disabled", 7631 appSet: v1alpha1.ApplicationSet{ 7632 ObjectMeta: metav1.ObjectMeta{ 7633 Name: "test-appset", 7634 Namespace: "argocd", 7635 }, 7636 Spec: v1alpha1.ApplicationSetSpec{ 7637 Generators: []v1alpha1.ApplicationSetGenerator{}, 7638 Template: v1alpha1.ApplicationSetTemplate{}, 7639 }, 7640 Status: v1alpha1.ApplicationSetStatus{ 7641 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 7642 { 7643 Application: "test-appset-guestbook", 7644 Message: "Application resource became Healthy, updating status from Progressing to Healthy.", 7645 Status: "Healthy", 7646 Step: "1", 7647 }, 7648 }, 7649 }, 7650 }, 7651 enableProgressiveSyncs: false, 7652 expectedAppStatuses: nil, 7653 }, 7654 } { 7655 t.Run(cc.name, func(t *testing.T) { 7656 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 7657 metrics := appsetmetrics.NewFakeAppsetMetrics() 7658 7659 argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) 7660 7661 r := ApplicationSetReconciler{ 7662 Client: client, 7663 Scheme: scheme, 7664 Renderer: &utils.Render{}, 7665 Recorder: record.NewFakeRecorder(1), 7666 Generators: map[string]generators.Generator{}, 7667 ArgoDB: argodb, 7668 KubeClientset: kubeclientset, 7669 Metrics: metrics, 7670 EnableProgressiveSyncs: cc.enableProgressiveSyncs, 7671 } 7672 7673 req := ctrl.Request{ 7674 NamespacedName: types.NamespacedName{ 7675 Namespace: cc.appSet.Namespace, 7676 Name: cc.appSet.Name, 7677 }, 7678 } 7679 7680 // Run reconciliation 7681 _, err = r.Reconcile(t.Context(), req) 7682 require.NoError(t, err) 7683 7684 // Fetch the updated ApplicationSet 7685 var updatedAppSet v1alpha1.ApplicationSet 7686 err = r.Get(t.Context(), req.NamespacedName, &updatedAppSet) 7687 require.NoError(t, err) 7688 7689 // Verify the applicationStatus field 7690 assert.Equal(t, cc.expectedAppStatuses, updatedAppSet.Status.ApplicationStatus, "applicationStatus should match expected value") 7691 }) 7692 } 7693 }