github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/controllers/applicationset_controller_test.go (about) 1 package controllers 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strings" 9 "testing" 10 "time" 11 12 log "github.com/sirupsen/logrus" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/mock" 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 k8scache "k8s.io/client-go/tools/cache" 23 "k8s.io/client-go/tools/record" 24 ctrl "sigs.k8s.io/controller-runtime" 25 "sigs.k8s.io/controller-runtime/pkg/cache" 26 crtclient "sigs.k8s.io/controller-runtime/pkg/client" 27 "sigs.k8s.io/controller-runtime/pkg/client/fake" 28 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 29 "sigs.k8s.io/controller-runtime/pkg/event" 30 31 "github.com/argoproj/gitops-engine/pkg/health" 32 "github.com/argoproj/gitops-engine/pkg/sync/common" 33 34 "github.com/argoproj/argo-cd/v2/applicationset/generators" 35 "github.com/argoproj/argo-cd/v2/applicationset/utils" 36 37 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 38 appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake" 39 "github.com/argoproj/argo-cd/v2/util/collections" 40 dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks" 41 42 "github.com/argoproj/argo-cd/v2/pkg/apis/application" 43 ) 44 45 type fakeStore struct { 46 k8scache.Store 47 } 48 49 func (f *fakeStore) Update(obj interface{}) error { 50 return nil 51 } 52 53 type fakeInformer struct { 54 k8scache.SharedInformer 55 } 56 57 func (f *fakeInformer) AddIndexers(indexers k8scache.Indexers) error { 58 return nil 59 } 60 61 func (f *fakeInformer) GetStore() k8scache.Store { 62 return &fakeStore{} 63 } 64 65 type fakeCache struct { 66 cache.Cache 67 } 68 69 func (f *fakeCache) GetInformer(ctx context.Context, obj crtclient.Object) (cache.Informer, error) { 70 return &fakeInformer{}, nil 71 } 72 73 type generatorMock struct { 74 mock.Mock 75 } 76 77 func (g *generatorMock) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate { 78 args := g.Called(appSetGenerator) 79 80 return args.Get(0).(*v1alpha1.ApplicationSetTemplate) 81 } 82 83 func (g *generatorMock) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, _ *v1alpha1.ApplicationSet) ([]map[string]interface{}, error) { 84 args := g.Called(appSetGenerator) 85 86 return args.Get(0).([]map[string]interface{}), args.Error(1) 87 } 88 89 func (g *generatorMock) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) { 90 args := g.Called(tmpl, replaceMap, useGoTemplate, goTemplateOptions) 91 92 return args.Get(0).(string), args.Error(1) 93 } 94 95 type rendererMock struct { 96 mock.Mock 97 } 98 99 func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration { 100 args := g.Called(appSetGenerator) 101 102 return args.Get(0).(time.Duration) 103 } 104 105 func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*v1alpha1.Application, error) { 106 args := r.Called(tmpl, params, useGoTemplate, goTemplateOptions) 107 108 if args.Error(1) != nil { 109 return nil, args.Error(1) 110 } 111 112 return args.Get(0).(*v1alpha1.Application), args.Error(1) 113 114 } 115 116 func (r *rendererMock) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) { 117 args := r.Called(tmpl, replaceMap, useGoTemplate, goTemplateOptions) 118 119 return args.Get(0).(string), args.Error(1) 120 } 121 122 func TestExtractApplications(t *testing.T) { 123 scheme := runtime.NewScheme() 124 err := v1alpha1.AddToScheme(scheme) 125 assert.Nil(t, err) 126 127 err = v1alpha1.AddToScheme(scheme) 128 assert.Nil(t, err) 129 130 for _, c := range []struct { 131 name string 132 params []map[string]interface{} 133 template v1alpha1.ApplicationSetTemplate 134 generateParamsError error 135 rendererError error 136 expectErr bool 137 expectedReason v1alpha1.ApplicationSetReasonType 138 }{ 139 { 140 name: "Generate two applications", 141 params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}}, 142 template: v1alpha1.ApplicationSetTemplate{ 143 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 144 Name: "name", 145 Namespace: "namespace", 146 Labels: map[string]string{"label_name": "label_value"}, 147 }, 148 Spec: v1alpha1.ApplicationSpec{}, 149 }, 150 expectedReason: "", 151 }, 152 { 153 name: "Handles error from the generator", 154 generateParamsError: fmt.Errorf("error"), 155 expectErr: true, 156 expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError, 157 }, 158 { 159 name: "Handles error from the render", 160 params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}}, 161 template: v1alpha1.ApplicationSetTemplate{ 162 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 163 Name: "name", 164 Namespace: "namespace", 165 Labels: map[string]string{"label_name": "label_value"}, 166 }, 167 Spec: v1alpha1.ApplicationSpec{}, 168 }, 169 rendererError: fmt.Errorf("error"), 170 expectErr: true, 171 expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError, 172 }, 173 } { 174 cc := c 175 app := v1alpha1.Application{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: "test", 178 }, 179 } 180 181 t.Run(cc.name, func(t *testing.T) { 182 183 appSet := &v1alpha1.ApplicationSet{ 184 ObjectMeta: metav1.ObjectMeta{ 185 Name: "name", 186 Namespace: "namespace", 187 }, 188 } 189 190 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(appSet).Build() 191 192 generatorMock := generatorMock{} 193 generator := v1alpha1.ApplicationSetGenerator{ 194 List: &v1alpha1.ListGenerator{}, 195 } 196 197 generatorMock.On("GenerateParams", &generator). 198 Return(cc.params, cc.generateParamsError) 199 200 generatorMock.On("GetTemplate", &generator). 201 Return(&v1alpha1.ApplicationSetTemplate{}) 202 203 rendererMock := rendererMock{} 204 205 var expectedApps []v1alpha1.Application 206 207 if cc.generateParamsError == nil { 208 for _, p := range cc.params { 209 210 if cc.rendererError != nil { 211 rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)). 212 Return(nil, cc.rendererError) 213 } else { 214 rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)). 215 Return(&app, nil) 216 expectedApps = append(expectedApps, app) 217 } 218 } 219 } 220 221 r := ApplicationSetReconciler{ 222 Client: client, 223 Scheme: scheme, 224 Recorder: record.NewFakeRecorder(1), 225 Generators: map[string]generators.Generator{ 226 "List": &generatorMock, 227 }, 228 Renderer: &rendererMock, 229 KubeClientset: kubefake.NewSimpleClientset(), 230 Cache: &fakeCache{}, 231 } 232 233 got, reason, err := r.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{ 234 ObjectMeta: metav1.ObjectMeta{ 235 Name: "name", 236 Namespace: "namespace", 237 }, 238 Spec: v1alpha1.ApplicationSetSpec{ 239 Generators: []v1alpha1.ApplicationSetGenerator{generator}, 240 Template: cc.template, 241 }, 242 }) 243 244 if cc.expectErr { 245 assert.Error(t, err) 246 } else { 247 assert.NoError(t, err) 248 } 249 assert.Equal(t, expectedApps, got) 250 assert.Equal(t, cc.expectedReason, reason) 251 generatorMock.AssertNumberOfCalls(t, "GenerateParams", 1) 252 253 if cc.generateParamsError == nil { 254 rendererMock.AssertNumberOfCalls(t, "RenderTemplateParams", len(cc.params)) 255 } 256 257 }) 258 } 259 260 } 261 262 func TestMergeTemplateApplications(t *testing.T) { 263 scheme := runtime.NewScheme() 264 _ = v1alpha1.AddToScheme(scheme) 265 _ = v1alpha1.AddToScheme(scheme) 266 267 client := fake.NewClientBuilder().WithScheme(scheme).Build() 268 269 for _, c := range []struct { 270 name string 271 params []map[string]interface{} 272 template v1alpha1.ApplicationSetTemplate 273 overrideTemplate v1alpha1.ApplicationSetTemplate 274 expectedMerged v1alpha1.ApplicationSetTemplate 275 expectedApps []v1alpha1.Application 276 }{ 277 { 278 name: "Generate app", 279 params: []map[string]interface{}{{"name": "app1"}}, 280 template: v1alpha1.ApplicationSetTemplate{ 281 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 282 Name: "name", 283 Namespace: "namespace", 284 Labels: map[string]string{"label_name": "label_value"}, 285 }, 286 Spec: v1alpha1.ApplicationSpec{}, 287 }, 288 overrideTemplate: v1alpha1.ApplicationSetTemplate{ 289 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 290 Name: "test", 291 Labels: map[string]string{"foo": "bar"}, 292 }, 293 Spec: v1alpha1.ApplicationSpec{}, 294 }, 295 expectedMerged: v1alpha1.ApplicationSetTemplate{ 296 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 297 Name: "test", 298 Namespace: "namespace", 299 Labels: map[string]string{"label_name": "label_value", "foo": "bar"}, 300 }, 301 Spec: v1alpha1.ApplicationSpec{}, 302 }, 303 expectedApps: []v1alpha1.Application{ 304 { 305 ObjectMeta: metav1.ObjectMeta{ 306 Name: "test", 307 Namespace: "test", 308 Labels: map[string]string{"foo": "bar"}, 309 }, 310 Spec: v1alpha1.ApplicationSpec{}, 311 }, 312 }, 313 }, 314 } { 315 cc := c 316 317 t.Run(cc.name, func(t *testing.T) { 318 319 generatorMock := generatorMock{} 320 generator := v1alpha1.ApplicationSetGenerator{ 321 List: &v1alpha1.ListGenerator{}, 322 } 323 324 generatorMock.On("GenerateParams", &generator). 325 Return(cc.params, nil) 326 327 generatorMock.On("GetTemplate", &generator). 328 Return(&cc.overrideTemplate) 329 330 rendererMock := rendererMock{} 331 332 rendererMock.On("RenderTemplateParams", getTempApplication(cc.expectedMerged), cc.params[0], false, []string(nil)). 333 Return(&cc.expectedApps[0], nil) 334 335 r := ApplicationSetReconciler{ 336 Client: client, 337 Scheme: scheme, 338 Recorder: record.NewFakeRecorder(1), 339 Generators: map[string]generators.Generator{ 340 "List": &generatorMock, 341 }, 342 Renderer: &rendererMock, 343 KubeClientset: kubefake.NewSimpleClientset(), 344 } 345 346 got, _, _ := r.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{ 347 ObjectMeta: metav1.ObjectMeta{ 348 Name: "name", 349 Namespace: "namespace", 350 }, 351 Spec: v1alpha1.ApplicationSetSpec{ 352 Generators: []v1alpha1.ApplicationSetGenerator{generator}, 353 Template: cc.template, 354 }, 355 }, 356 ) 357 358 assert.Equal(t, cc.expectedApps, got) 359 }) 360 } 361 362 } 363 364 func TestCreateOrUpdateInCluster(t *testing.T) { 365 366 scheme := runtime.NewScheme() 367 err := v1alpha1.AddToScheme(scheme) 368 assert.Nil(t, err) 369 370 err = v1alpha1.AddToScheme(scheme) 371 assert.Nil(t, err) 372 373 for _, c := range []struct { 374 // name is human-readable test name 375 name string 376 // appSet is the ApplicationSet we are generating resources for 377 appSet v1alpha1.ApplicationSet 378 // existingApps are the apps that already exist on the cluster 379 existingApps []v1alpha1.Application 380 // desiredApps are the generated apps to create/update 381 desiredApps []v1alpha1.Application 382 // expected is what we expect the cluster Applications to look like, after createOrUpdateInCluster 383 expected []v1alpha1.Application 384 }{ 385 { 386 name: "Create an app that doesn't exist", 387 appSet: v1alpha1.ApplicationSet{ 388 ObjectMeta: metav1.ObjectMeta{ 389 Name: "name", 390 Namespace: "namespace", 391 }, 392 }, 393 existingApps: nil, 394 desiredApps: []v1alpha1.Application{ 395 { 396 ObjectMeta: metav1.ObjectMeta{ 397 Name: "app1", 398 }, 399 }, 400 }, 401 expected: []v1alpha1.Application{ 402 { 403 TypeMeta: metav1.TypeMeta{ 404 Kind: application.ApplicationKind, 405 APIVersion: "argoproj.io/v1alpha1", 406 }, 407 ObjectMeta: metav1.ObjectMeta{ 408 Name: "app1", 409 Namespace: "namespace", 410 ResourceVersion: "1", 411 }, 412 Spec: v1alpha1.ApplicationSpec{Project: "default"}, 413 }, 414 }, 415 }, 416 { 417 name: "Update an existing app with a different project name", 418 appSet: v1alpha1.ApplicationSet{ 419 ObjectMeta: metav1.ObjectMeta{ 420 Name: "name", 421 Namespace: "namespace", 422 }, 423 Spec: v1alpha1.ApplicationSetSpec{ 424 Template: v1alpha1.ApplicationSetTemplate{ 425 Spec: v1alpha1.ApplicationSpec{ 426 Project: "project", 427 }, 428 }, 429 }, 430 }, 431 existingApps: []v1alpha1.Application{ 432 { 433 TypeMeta: metav1.TypeMeta{ 434 Kind: application.ApplicationKind, 435 APIVersion: "argoproj.io/v1alpha1", 436 }, 437 ObjectMeta: metav1.ObjectMeta{ 438 Name: "app1", 439 Namespace: "namespace", 440 ResourceVersion: "2", 441 }, 442 Spec: v1alpha1.ApplicationSpec{ 443 Project: "test", 444 }, 445 }, 446 }, 447 desiredApps: []v1alpha1.Application{ 448 { 449 ObjectMeta: metav1.ObjectMeta{ 450 Name: "app1", 451 }, 452 Spec: v1alpha1.ApplicationSpec{ 453 Project: "project", 454 }, 455 }, 456 }, 457 expected: []v1alpha1.Application{ 458 { 459 TypeMeta: metav1.TypeMeta{ 460 Kind: application.ApplicationKind, 461 APIVersion: "argoproj.io/v1alpha1", 462 }, 463 ObjectMeta: metav1.ObjectMeta{ 464 Name: "app1", 465 Namespace: "namespace", 466 ResourceVersion: "3", 467 }, 468 Spec: v1alpha1.ApplicationSpec{ 469 Project: "project", 470 }, 471 }, 472 }, 473 }, 474 { 475 name: "Create a new app and check it doesn't replace the existing app", 476 appSet: v1alpha1.ApplicationSet{ 477 ObjectMeta: metav1.ObjectMeta{ 478 Name: "name", 479 Namespace: "namespace", 480 }, 481 Spec: v1alpha1.ApplicationSetSpec{ 482 Template: v1alpha1.ApplicationSetTemplate{ 483 Spec: v1alpha1.ApplicationSpec{ 484 Project: "project", 485 }, 486 }, 487 }, 488 }, 489 existingApps: []v1alpha1.Application{ 490 { 491 TypeMeta: metav1.TypeMeta{ 492 Kind: application.ApplicationKind, 493 APIVersion: "argoproj.io/v1alpha1", 494 }, 495 ObjectMeta: metav1.ObjectMeta{ 496 Name: "app1", 497 Namespace: "namespace", 498 ResourceVersion: "2", 499 }, 500 Spec: v1alpha1.ApplicationSpec{ 501 Project: "test", 502 }, 503 }, 504 }, 505 desiredApps: []v1alpha1.Application{ 506 { 507 ObjectMeta: metav1.ObjectMeta{ 508 Name: "app2", 509 }, 510 Spec: v1alpha1.ApplicationSpec{ 511 Project: "project", 512 }, 513 }, 514 }, 515 expected: []v1alpha1.Application{ 516 { 517 TypeMeta: metav1.TypeMeta{ 518 Kind: application.ApplicationKind, 519 APIVersion: "argoproj.io/v1alpha1", 520 }, 521 ObjectMeta: metav1.ObjectMeta{ 522 Name: "app2", 523 Namespace: "namespace", 524 ResourceVersion: "1", 525 }, 526 Spec: v1alpha1.ApplicationSpec{ 527 Project: "project", 528 }, 529 }, 530 }, 531 }, 532 { 533 name: "Ensure that labels and annotations are added (via update) into an exiting application", 534 appSet: v1alpha1.ApplicationSet{ 535 ObjectMeta: metav1.ObjectMeta{ 536 Name: "name", 537 Namespace: "namespace", 538 }, 539 Spec: v1alpha1.ApplicationSetSpec{ 540 Template: v1alpha1.ApplicationSetTemplate{ 541 Spec: v1alpha1.ApplicationSpec{ 542 Project: "project", 543 }, 544 }, 545 }, 546 }, 547 existingApps: []v1alpha1.Application{ 548 { 549 TypeMeta: metav1.TypeMeta{ 550 Kind: application.ApplicationKind, 551 APIVersion: "argoproj.io/v1alpha1", 552 }, 553 ObjectMeta: metav1.ObjectMeta{ 554 Name: "app1", 555 Namespace: "namespace", 556 ResourceVersion: "2", 557 }, 558 Spec: v1alpha1.ApplicationSpec{ 559 Project: "project", 560 }, 561 }, 562 }, 563 desiredApps: []v1alpha1.Application{ 564 { 565 ObjectMeta: metav1.ObjectMeta{ 566 Name: "app1", 567 Labels: map[string]string{"label-key": "label-value"}, 568 Annotations: map[string]string{"annot-key": "annot-value"}, 569 }, 570 Spec: v1alpha1.ApplicationSpec{ 571 Project: "project", 572 }, 573 }, 574 }, 575 expected: []v1alpha1.Application{ 576 { 577 TypeMeta: metav1.TypeMeta{ 578 Kind: application.ApplicationKind, 579 APIVersion: "argoproj.io/v1alpha1", 580 }, 581 ObjectMeta: metav1.ObjectMeta{ 582 Name: "app1", 583 Namespace: "namespace", 584 Labels: map[string]string{"label-key": "label-value"}, 585 Annotations: map[string]string{"annot-key": "annot-value"}, 586 ResourceVersion: "3", 587 }, 588 Spec: v1alpha1.ApplicationSpec{ 589 Project: "project", 590 }, 591 }, 592 }, 593 }, 594 { 595 name: "Ensure that labels and annotations are removed from an existing app", 596 appSet: v1alpha1.ApplicationSet{ 597 ObjectMeta: metav1.ObjectMeta{ 598 Name: "name", 599 Namespace: "namespace", 600 }, 601 Spec: v1alpha1.ApplicationSetSpec{ 602 Template: v1alpha1.ApplicationSetTemplate{ 603 Spec: v1alpha1.ApplicationSpec{ 604 Project: "project", 605 }, 606 }, 607 }, 608 }, 609 existingApps: []v1alpha1.Application{ 610 { 611 TypeMeta: metav1.TypeMeta{ 612 Kind: application.ApplicationKind, 613 APIVersion: "argoproj.io/v1alpha1", 614 }, 615 ObjectMeta: metav1.ObjectMeta{ 616 Name: "app1", 617 Namespace: "namespace", 618 ResourceVersion: "2", 619 Labels: map[string]string{"label-key": "label-value"}, 620 Annotations: map[string]string{"annot-key": "annot-value"}, 621 }, 622 Spec: v1alpha1.ApplicationSpec{ 623 Project: "project", 624 }, 625 }, 626 }, 627 desiredApps: []v1alpha1.Application{ 628 { 629 ObjectMeta: metav1.ObjectMeta{ 630 Name: "app1", 631 }, 632 Spec: v1alpha1.ApplicationSpec{ 633 Project: "project", 634 }, 635 }, 636 }, 637 expected: []v1alpha1.Application{ 638 { 639 TypeMeta: metav1.TypeMeta{ 640 Kind: application.ApplicationKind, 641 APIVersion: "argoproj.io/v1alpha1", 642 }, 643 ObjectMeta: metav1.ObjectMeta{ 644 Name: "app1", 645 Namespace: "namespace", 646 ResourceVersion: "3", 647 }, 648 Spec: v1alpha1.ApplicationSpec{ 649 Project: "project", 650 }, 651 }, 652 }, 653 }, 654 { 655 name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations", 656 appSet: v1alpha1.ApplicationSet{ 657 ObjectMeta: metav1.ObjectMeta{ 658 Name: "name", 659 Namespace: "namespace", 660 }, 661 Spec: v1alpha1.ApplicationSetSpec{ 662 Template: v1alpha1.ApplicationSetTemplate{ 663 Spec: v1alpha1.ApplicationSpec{ 664 Project: "project", 665 }, 666 }, 667 }, 668 }, 669 existingApps: []v1alpha1.Application{ 670 { 671 TypeMeta: metav1.TypeMeta{ 672 Kind: application.ApplicationKind, 673 APIVersion: "argoproj.io/v1alpha1", 674 }, 675 ObjectMeta: metav1.ObjectMeta{ 676 Name: "app1", 677 Namespace: "namespace", 678 ResourceVersion: "2", 679 Labels: map[string]string{"label-key": "label-value"}, 680 Annotations: map[string]string{"annot-key": "annot-value"}, 681 }, 682 Spec: v1alpha1.ApplicationSpec{ 683 Project: "project", 684 }, 685 Status: v1alpha1.ApplicationStatus{ 686 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 687 }, 688 Operation: &v1alpha1.Operation{ 689 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 690 }, 691 }, 692 }, 693 desiredApps: []v1alpha1.Application{ 694 { 695 ObjectMeta: metav1.ObjectMeta{ 696 Name: "app1", 697 }, 698 Spec: v1alpha1.ApplicationSpec{ 699 Project: "project", 700 }, 701 }, 702 }, 703 expected: []v1alpha1.Application{ 704 { 705 TypeMeta: metav1.TypeMeta{ 706 Kind: application.ApplicationKind, 707 APIVersion: "argoproj.io/v1alpha1", 708 }, 709 ObjectMeta: metav1.ObjectMeta{ 710 Name: "app1", 711 Namespace: "namespace", 712 ResourceVersion: "3", 713 }, 714 Spec: v1alpha1.ApplicationSpec{ 715 Project: "project", 716 }, 717 Status: v1alpha1.ApplicationStatus{ 718 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 719 }, 720 Operation: &v1alpha1.Operation{ 721 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 722 }, 723 }, 724 }, 725 }, 726 { 727 name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations and adding other fields", 728 appSet: v1alpha1.ApplicationSet{ 729 ObjectMeta: metav1.ObjectMeta{ 730 Name: "name", 731 Namespace: "namespace", 732 }, 733 Spec: v1alpha1.ApplicationSetSpec{ 734 Template: v1alpha1.ApplicationSetTemplate{ 735 Spec: v1alpha1.ApplicationSpec{ 736 Project: "project", 737 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 738 Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"}, 739 }, 740 }, 741 }, 742 }, 743 existingApps: []v1alpha1.Application{ 744 { 745 TypeMeta: metav1.TypeMeta{ 746 Kind: application.ApplicationKind, 747 APIVersion: "argoproj.io/v1alpha1", 748 }, 749 ObjectMeta: metav1.ObjectMeta{ 750 Name: "app1", 751 Namespace: "namespace", 752 ResourceVersion: "2", 753 }, 754 Spec: v1alpha1.ApplicationSpec{ 755 Project: "project", 756 }, 757 Status: v1alpha1.ApplicationStatus{ 758 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 759 }, 760 Operation: &v1alpha1.Operation{ 761 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 762 }, 763 }, 764 }, 765 desiredApps: []v1alpha1.Application{ 766 { 767 ObjectMeta: metav1.ObjectMeta{ 768 Name: "app1", 769 Labels: map[string]string{"label-key": "label-value"}, 770 Annotations: map[string]string{"annot-key": "annot-value"}, 771 }, 772 Spec: v1alpha1.ApplicationSpec{ 773 Project: "project", 774 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 775 Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"}, 776 }, 777 }, 778 }, 779 expected: []v1alpha1.Application{ 780 { 781 TypeMeta: metav1.TypeMeta{ 782 Kind: application.ApplicationKind, 783 APIVersion: "argoproj.io/v1alpha1", 784 }, 785 ObjectMeta: metav1.ObjectMeta{ 786 Name: "app1", 787 Namespace: "namespace", 788 Labels: map[string]string{"label-key": "label-value"}, 789 Annotations: map[string]string{"annot-key": "annot-value"}, 790 ResourceVersion: "3", 791 }, 792 Spec: v1alpha1.ApplicationSpec{ 793 Project: "project", 794 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 795 Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"}, 796 }, 797 Status: v1alpha1.ApplicationStatus{ 798 Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}}, 799 }, 800 Operation: &v1alpha1.Operation{ 801 Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"}, 802 }, 803 }, 804 }, 805 }, 806 { 807 name: "Ensure that argocd notifications state and refresh annotation is preserved from an existing app", 808 appSet: v1alpha1.ApplicationSet{ 809 ObjectMeta: metav1.ObjectMeta{ 810 Name: "name", 811 Namespace: "namespace", 812 }, 813 Spec: v1alpha1.ApplicationSetSpec{ 814 Template: v1alpha1.ApplicationSetTemplate{ 815 Spec: v1alpha1.ApplicationSpec{ 816 Project: "project", 817 }, 818 }, 819 }, 820 }, 821 existingApps: []v1alpha1.Application{ 822 { 823 TypeMeta: metav1.TypeMeta{ 824 Kind: application.ApplicationKind, 825 APIVersion: "argoproj.io/v1alpha1", 826 }, 827 ObjectMeta: metav1.ObjectMeta{ 828 Name: "app1", 829 Namespace: "namespace", 830 ResourceVersion: "2", 831 Labels: map[string]string{"label-key": "label-value"}, 832 Annotations: map[string]string{ 833 "annot-key": "annot-value", 834 NotifiedAnnotationKey: `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`, 835 v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal), 836 }, 837 }, 838 Spec: v1alpha1.ApplicationSpec{ 839 Project: "project", 840 }, 841 }, 842 }, 843 desiredApps: []v1alpha1.Application{ 844 { 845 ObjectMeta: metav1.ObjectMeta{ 846 Name: "app1", 847 }, 848 Spec: v1alpha1.ApplicationSpec{ 849 Project: "project", 850 }, 851 }, 852 }, 853 expected: []v1alpha1.Application{ 854 { 855 TypeMeta: metav1.TypeMeta{ 856 Kind: application.ApplicationKind, 857 APIVersion: "argoproj.io/v1alpha1", 858 }, 859 ObjectMeta: metav1.ObjectMeta{ 860 Name: "app1", 861 Namespace: "namespace", 862 ResourceVersion: "3", 863 Annotations: map[string]string{ 864 NotifiedAnnotationKey: `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`, 865 v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal), 866 }, 867 }, 868 Spec: v1alpha1.ApplicationSpec{ 869 Project: "project", 870 }, 871 }, 872 }, 873 }, { 874 name: "Ensure that configured preserved annotations are preserved from an existing app", 875 appSet: v1alpha1.ApplicationSet{ 876 ObjectMeta: metav1.ObjectMeta{ 877 Name: "name", 878 Namespace: "namespace", 879 }, 880 Spec: v1alpha1.ApplicationSetSpec{ 881 Template: v1alpha1.ApplicationSetTemplate{ 882 Spec: v1alpha1.ApplicationSpec{ 883 Project: "project", 884 }, 885 }, 886 PreservedFields: &v1alpha1.ApplicationPreservedFields{ 887 Annotations: []string{"preserved-annot-key"}, 888 }, 889 }, 890 }, 891 existingApps: []v1alpha1.Application{ 892 { 893 TypeMeta: metav1.TypeMeta{ 894 Kind: "Application", 895 APIVersion: "argoproj.io/v1alpha1", 896 }, 897 ObjectMeta: metav1.ObjectMeta{ 898 Name: "app1", 899 Namespace: "namespace", 900 ResourceVersion: "2", 901 Annotations: map[string]string{ 902 "annot-key": "annot-value", 903 "preserved-annot-key": "preserved-annot-value", 904 }, 905 }, 906 Spec: v1alpha1.ApplicationSpec{ 907 Project: "project", 908 }, 909 }, 910 }, 911 desiredApps: []v1alpha1.Application{ 912 { 913 ObjectMeta: metav1.ObjectMeta{ 914 Name: "app1", 915 }, 916 Spec: v1alpha1.ApplicationSpec{ 917 Project: "project", 918 }, 919 }, 920 }, 921 expected: []v1alpha1.Application{ 922 { 923 TypeMeta: metav1.TypeMeta{ 924 Kind: "Application", 925 APIVersion: "argoproj.io/v1alpha1", 926 }, 927 ObjectMeta: metav1.ObjectMeta{ 928 Name: "app1", 929 Namespace: "namespace", 930 ResourceVersion: "3", 931 Annotations: map[string]string{ 932 "preserved-annot-key": "preserved-annot-value", 933 }, 934 }, 935 Spec: v1alpha1.ApplicationSpec{ 936 Project: "project", 937 }, 938 }, 939 }, 940 }, { 941 name: "Ensure that the app spec is normalized before applying", 942 appSet: v1alpha1.ApplicationSet{ 943 ObjectMeta: metav1.ObjectMeta{ 944 Name: "name", 945 Namespace: "namespace", 946 }, 947 Spec: v1alpha1.ApplicationSetSpec{ 948 Template: v1alpha1.ApplicationSetTemplate{ 949 Spec: v1alpha1.ApplicationSpec{ 950 Project: "project", 951 Source: &v1alpha1.ApplicationSource{ 952 Directory: &v1alpha1.ApplicationSourceDirectory{ 953 Jsonnet: v1alpha1.ApplicationSourceJsonnet{}, 954 }, 955 }, 956 }, 957 }, 958 }, 959 }, 960 desiredApps: []v1alpha1.Application{ 961 { 962 ObjectMeta: metav1.ObjectMeta{ 963 Name: "app1", 964 }, 965 Spec: v1alpha1.ApplicationSpec{ 966 Project: "project", 967 Source: &v1alpha1.ApplicationSource{ 968 Directory: &v1alpha1.ApplicationSourceDirectory{ 969 Jsonnet: v1alpha1.ApplicationSourceJsonnet{}, 970 }, 971 }, 972 }, 973 }, 974 }, 975 expected: []v1alpha1.Application{ 976 { 977 TypeMeta: metav1.TypeMeta{ 978 Kind: "Application", 979 APIVersion: "argoproj.io/v1alpha1", 980 }, 981 ObjectMeta: metav1.ObjectMeta{ 982 Name: "app1", 983 Namespace: "namespace", 984 ResourceVersion: "1", 985 }, 986 Spec: v1alpha1.ApplicationSpec{ 987 Project: "project", 988 Source: &v1alpha1.ApplicationSource{ 989 // Directory and jsonnet block are removed 990 }, 991 }, 992 }, 993 }, 994 }, { 995 // For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278 996 name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes", 997 appSet: v1alpha1.ApplicationSet{ 998 ObjectMeta: metav1.ObjectMeta{ 999 Name: "name", 1000 Namespace: "namespace", 1001 }, 1002 Spec: v1alpha1.ApplicationSetSpec{ 1003 IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{ 1004 {JQPathExpressions: []string{".spec.source.targetRevision"}}, 1005 }, 1006 Template: v1alpha1.ApplicationSetTemplate{ 1007 Spec: v1alpha1.ApplicationSpec{ 1008 Project: "project", 1009 Source: &v1alpha1.ApplicationSource{ 1010 RepoURL: "https://git.example.com/test-org/test-repo.git", 1011 TargetRevision: "foo", 1012 }, 1013 }, 1014 }, 1015 }, 1016 }, 1017 existingApps: []v1alpha1.Application{ 1018 { 1019 TypeMeta: metav1.TypeMeta{ 1020 Kind: "Application", 1021 APIVersion: "argoproj.io/v1alpha1", 1022 }, 1023 ObjectMeta: metav1.ObjectMeta{ 1024 Name: "app1", 1025 Namespace: "namespace", 1026 ResourceVersion: "2", 1027 }, 1028 Spec: v1alpha1.ApplicationSpec{ 1029 Project: "project", 1030 Source: &v1alpha1.ApplicationSource{ 1031 RepoURL: "https://git.example.com/test-org/test-repo.git", 1032 TargetRevision: "bar", 1033 }, 1034 }, 1035 }, 1036 }, 1037 desiredApps: []v1alpha1.Application{ 1038 { 1039 ObjectMeta: metav1.ObjectMeta{ 1040 Name: "app1", 1041 }, 1042 Spec: v1alpha1.ApplicationSpec{ 1043 Project: "project", 1044 Source: &v1alpha1.ApplicationSource{ 1045 RepoURL: "https://git.example.com/test-org/test-repo.git", 1046 // The targetRevision is ignored, so this should not be updated. 1047 TargetRevision: "foo", 1048 // This should be updated. 1049 Helm: &v1alpha1.ApplicationSourceHelm{ 1050 Parameters: []v1alpha1.HelmParameter{ 1051 {Name: "hi", Value: "there"}, 1052 }, 1053 }, 1054 }, 1055 }, 1056 }, 1057 }, 1058 expected: []v1alpha1.Application{ 1059 { 1060 TypeMeta: metav1.TypeMeta{ 1061 Kind: "Application", 1062 APIVersion: "argoproj.io/v1alpha1", 1063 }, 1064 ObjectMeta: metav1.ObjectMeta{ 1065 Name: "app1", 1066 Namespace: "namespace", 1067 ResourceVersion: "3", 1068 }, 1069 Spec: v1alpha1.ApplicationSpec{ 1070 Project: "project", 1071 Source: &v1alpha1.ApplicationSource{ 1072 RepoURL: "https://git.example.com/test-org/test-repo.git", 1073 // This is the existing value from the cluster, which should not be updated because the field is ignored. 1074 TargetRevision: "bar", 1075 // This was missing on the cluster, so it should be added. 1076 Helm: &v1alpha1.ApplicationSourceHelm{ 1077 Parameters: []v1alpha1.HelmParameter{ 1078 {Name: "hi", Value: "there"}, 1079 }, 1080 }, 1081 }, 1082 }, 1083 }, 1084 }, 1085 }, { 1086 // For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799 1087 name: "ignore parameters added to a multi-source app in the cluster", 1088 appSet: v1alpha1.ApplicationSet{ 1089 ObjectMeta: metav1.ObjectMeta{ 1090 Name: "name", 1091 Namespace: "namespace", 1092 }, 1093 Spec: v1alpha1.ApplicationSetSpec{ 1094 IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{ 1095 {JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}}, 1096 }, 1097 Template: v1alpha1.ApplicationSetTemplate{ 1098 Spec: v1alpha1.ApplicationSpec{ 1099 Project: "project", 1100 Sources: []v1alpha1.ApplicationSource{ 1101 { 1102 RepoURL: "https://git.example.com/test-org/test-repo.git", 1103 Helm: &v1alpha1.ApplicationSourceHelm{ 1104 Values: "foo: bar", 1105 }, 1106 }, 1107 }, 1108 }, 1109 }, 1110 }, 1111 }, 1112 existingApps: []v1alpha1.Application{ 1113 { 1114 TypeMeta: metav1.TypeMeta{ 1115 Kind: "Application", 1116 APIVersion: "argoproj.io/v1alpha1", 1117 }, 1118 ObjectMeta: metav1.ObjectMeta{ 1119 Name: "app1", 1120 Namespace: "namespace", 1121 ResourceVersion: "2", 1122 }, 1123 Spec: v1alpha1.ApplicationSpec{ 1124 Project: "project", 1125 Sources: []v1alpha1.ApplicationSource{ 1126 { 1127 RepoURL: "https://git.example.com/test-org/test-repo.git", 1128 Helm: &v1alpha1.ApplicationSourceHelm{ 1129 Values: "foo: bar", 1130 Parameters: []v1alpha1.HelmParameter{ 1131 {Name: "hi", Value: "there"}, 1132 }, 1133 }, 1134 }, 1135 }, 1136 }, 1137 }, 1138 }, 1139 desiredApps: []v1alpha1.Application{ 1140 { 1141 ObjectMeta: metav1.ObjectMeta{ 1142 Name: "app1", 1143 }, 1144 Spec: v1alpha1.ApplicationSpec{ 1145 Project: "project", 1146 Sources: []v1alpha1.ApplicationSource{ 1147 { 1148 RepoURL: "https://git.example.com/test-org/test-repo.git", 1149 Helm: &v1alpha1.ApplicationSourceHelm{ 1150 Values: "foo: bar", 1151 }, 1152 }, 1153 }, 1154 }, 1155 }, 1156 }, 1157 expected: []v1alpha1.Application{ 1158 { 1159 TypeMeta: metav1.TypeMeta{ 1160 Kind: "Application", 1161 APIVersion: "argoproj.io/v1alpha1", 1162 }, 1163 ObjectMeta: metav1.ObjectMeta{ 1164 Name: "app1", 1165 Namespace: "namespace", 1166 // This should not be updated, because reconciliation shouldn't modify the App. 1167 ResourceVersion: "2", 1168 }, 1169 Spec: v1alpha1.ApplicationSpec{ 1170 Project: "project", 1171 Sources: []v1alpha1.ApplicationSource{ 1172 { 1173 RepoURL: "https://git.example.com/test-org/test-repo.git", 1174 Helm: &v1alpha1.ApplicationSourceHelm{ 1175 Values: "foo: bar", 1176 Parameters: []v1alpha1.HelmParameter{ 1177 // This existed only in the cluster, but it shouldn't be removed, because the field is ignored. 1178 {Name: "hi", Value: "there"}, 1179 }, 1180 }, 1181 }, 1182 }, 1183 }, 1184 }, 1185 }, 1186 }, { 1187 name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975 1188 appSet: v1alpha1.ApplicationSet{ 1189 ObjectMeta: metav1.ObjectMeta{ 1190 Name: "name", 1191 Namespace: "namespace", 1192 }, 1193 Spec: v1alpha1.ApplicationSetSpec{ 1194 IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{ 1195 {JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}}, 1196 }, 1197 Template: v1alpha1.ApplicationSetTemplate{ 1198 Spec: v1alpha1.ApplicationSpec{ 1199 Project: "project", 1200 Sources: []v1alpha1.ApplicationSource{ 1201 { 1202 RepoURL: "https://git.example.com/test-org/test-repo.git", 1203 Helm: &v1alpha1.ApplicationSourceHelm{ 1204 Values: "new: values", 1205 }, 1206 }, 1207 }, 1208 }, 1209 }, 1210 }, 1211 }, 1212 existingApps: []v1alpha1.Application{ 1213 { 1214 TypeMeta: metav1.TypeMeta{ 1215 Kind: "Application", 1216 APIVersion: "argoproj.io/v1alpha1", 1217 }, 1218 ObjectMeta: metav1.ObjectMeta{ 1219 Name: "app1", 1220 Namespace: "namespace", 1221 ResourceVersion: "2", 1222 }, 1223 Spec: v1alpha1.ApplicationSpec{ 1224 Project: "project", 1225 Sources: []v1alpha1.ApplicationSource{ 1226 { 1227 RepoURL: "https://git.example.com/test-org/test-repo.git", 1228 Helm: &v1alpha1.ApplicationSourceHelm{ 1229 Values: "foo: bar", 1230 Parameters: []v1alpha1.HelmParameter{ 1231 {Name: "hi", Value: "there"}, 1232 }, 1233 }, 1234 }, 1235 }, 1236 }, 1237 }, 1238 }, 1239 desiredApps: []v1alpha1.Application{ 1240 { 1241 ObjectMeta: metav1.ObjectMeta{ 1242 Name: "app1", 1243 }, 1244 Spec: v1alpha1.ApplicationSpec{ 1245 Project: "project", 1246 Sources: []v1alpha1.ApplicationSource{ 1247 { 1248 RepoURL: "https://git.example.com/test-org/test-repo.git", 1249 Helm: &v1alpha1.ApplicationSourceHelm{ 1250 Values: "new: values", 1251 }, 1252 }, 1253 }, 1254 }, 1255 }, 1256 }, 1257 expected: []v1alpha1.Application{ 1258 { 1259 TypeMeta: metav1.TypeMeta{ 1260 Kind: "Application", 1261 APIVersion: "argoproj.io/v1alpha1", 1262 }, 1263 ObjectMeta: metav1.ObjectMeta{ 1264 Name: "app1", 1265 Namespace: "namespace", 1266 ResourceVersion: "3", 1267 }, 1268 Spec: v1alpha1.ApplicationSpec{ 1269 Project: "project", 1270 Sources: []v1alpha1.ApplicationSource{ 1271 { 1272 RepoURL: "https://git.example.com/test-org/test-repo.git", 1273 Helm: &v1alpha1.ApplicationSourceHelm{ 1274 Values: "new: values", 1275 // The Parameters field got blown away, because the values field changed. MergePatch 1276 // doesn't merge list items, it replaces the whole list if an item changes. 1277 // If we eventually add a `name` field to Sources, we can use StrategicMergePatch. 1278 }, 1279 }, 1280 }, 1281 }, 1282 }, 1283 }, 1284 }, 1285 } { 1286 1287 t.Run(c.name, func(t *testing.T) { 1288 1289 initObjs := []crtclient.Object{&c.appSet} 1290 1291 for _, a := range c.existingApps { 1292 err = controllerutil.SetControllerReference(&c.appSet, &a, scheme) 1293 assert.Nil(t, err) 1294 initObjs = append(initObjs, &a) 1295 } 1296 1297 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1298 1299 r := ApplicationSetReconciler{ 1300 Client: client, 1301 Scheme: scheme, 1302 Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)), 1303 Cache: &fakeCache{}, 1304 } 1305 1306 err = r.createOrUpdateInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps) 1307 assert.NoError(t, err) 1308 1309 for _, obj := range c.expected { 1310 got := &v1alpha1.Application{} 1311 _ = client.Get(context.Background(), crtclient.ObjectKey{ 1312 Namespace: obj.Namespace, 1313 Name: obj.Name, 1314 }, got) 1315 1316 err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme) 1317 assert.Equal(t, obj, *got) 1318 } 1319 }) 1320 } 1321 } 1322 1323 func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) { 1324 1325 scheme := runtime.NewScheme() 1326 err := v1alpha1.AddToScheme(scheme) 1327 assert.Nil(t, err) 1328 1329 err = v1alpha1.AddToScheme(scheme) 1330 assert.Nil(t, err) 1331 1332 for _, c := range []struct { 1333 // name is human-readable test name 1334 name string 1335 existingFinalizers []string 1336 expectedFinalizers []string 1337 }{ 1338 { 1339 name: "no finalizers", 1340 existingFinalizers: []string{}, 1341 expectedFinalizers: nil, 1342 }, 1343 { 1344 name: "contains only argo finalizer", 1345 existingFinalizers: []string{v1alpha1.ResourcesFinalizerName}, 1346 expectedFinalizers: nil, 1347 }, 1348 { 1349 name: "contains only non-argo finalizer", 1350 existingFinalizers: []string{"non-argo-finalizer"}, 1351 expectedFinalizers: []string{"non-argo-finalizer"}, 1352 }, 1353 { 1354 name: "contains both argo and non-argo finalizer", 1355 existingFinalizers: []string{"non-argo-finalizer", v1alpha1.ResourcesFinalizerName}, 1356 expectedFinalizers: []string{"non-argo-finalizer"}, 1357 }, 1358 } { 1359 t.Run(c.name, func(t *testing.T) { 1360 1361 appSet := v1alpha1.ApplicationSet{ 1362 ObjectMeta: metav1.ObjectMeta{ 1363 Name: "name", 1364 Namespace: "namespace", 1365 }, 1366 Spec: v1alpha1.ApplicationSetSpec{ 1367 Template: v1alpha1.ApplicationSetTemplate{ 1368 Spec: v1alpha1.ApplicationSpec{ 1369 Project: "project", 1370 }, 1371 }, 1372 }, 1373 } 1374 1375 app := v1alpha1.Application{ 1376 ObjectMeta: metav1.ObjectMeta{ 1377 Name: "app1", 1378 Finalizers: c.existingFinalizers, 1379 }, 1380 Spec: v1alpha1.ApplicationSpec{ 1381 Project: "project", 1382 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 1383 // Destination is always invalid, for this test: 1384 Destination: v1alpha1.ApplicationDestination{Name: "my-cluster", Namespace: "namespace"}, 1385 }, 1386 } 1387 1388 initObjs := []crtclient.Object{&app, &appSet} 1389 1390 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1391 secret := &corev1.Secret{ 1392 ObjectMeta: metav1.ObjectMeta{ 1393 Name: "my-secret", 1394 Namespace: "namespace", 1395 Labels: map[string]string{ 1396 generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster, 1397 }, 1398 }, 1399 Data: map[string][]byte{ 1400 // Since this test requires the cluster to be an invalid destination, we 1401 // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) 1402 "name": []byte("mycluster2"), 1403 "server": []byte("https://kubernetes.default.svc"), 1404 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 1405 }, 1406 } 1407 1408 objects := append([]runtime.Object{}, secret) 1409 kubeclientset := kubefake.NewSimpleClientset(objects...) 1410 1411 r := ApplicationSetReconciler{ 1412 Client: client, 1413 Scheme: scheme, 1414 Recorder: record.NewFakeRecorder(10), 1415 KubeClientset: kubeclientset, 1416 Cache: &fakeCache{}, 1417 } 1418 //settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "namespace") 1419 //argoDB := db.NewDB("namespace", settingsMgr, r.KubeClientset) 1420 //clusterList, err := argoDB.ListClusters(context.Background()) 1421 clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace") 1422 assert.NoError(t, err, "Unexpected error") 1423 1424 appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": ""}) 1425 1426 appInputParam := app.DeepCopy() 1427 1428 err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, clusterList, appLog) 1429 assert.NoError(t, err, "Unexpected error") 1430 1431 retrievedApp := v1alpha1.Application{} 1432 err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp) 1433 assert.NoError(t, err, "Unexpected error") 1434 1435 // App on the cluster should have the expected finalizers 1436 assert.ElementsMatch(t, c.expectedFinalizers, retrievedApp.Finalizers) 1437 1438 // App object passed in as a parameter should have the expected finaliers 1439 assert.ElementsMatch(t, c.expectedFinalizers, appInputParam.Finalizers) 1440 1441 bytes, _ := json.MarshalIndent(retrievedApp, "", " ") 1442 t.Log("Contents of app after call:", string(bytes)) 1443 1444 }) 1445 } 1446 } 1447 1448 func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) { 1449 1450 scheme := runtime.NewScheme() 1451 err := v1alpha1.AddToScheme(scheme) 1452 assert.Nil(t, err) 1453 1454 err = v1alpha1.AddToScheme(scheme) 1455 assert.Nil(t, err) 1456 1457 for _, c := range []struct { 1458 // name is human-readable test name 1459 name string 1460 destinationField v1alpha1.ApplicationDestination 1461 expectFinalizerRemoved bool 1462 }{ 1463 { 1464 name: "invalid cluster: empty destination", 1465 destinationField: v1alpha1.ApplicationDestination{ 1466 Namespace: "namespace", 1467 }, 1468 expectFinalizerRemoved: true, 1469 }, 1470 { 1471 name: "invalid cluster: invalid server url", 1472 destinationField: v1alpha1.ApplicationDestination{ 1473 Namespace: "namespace", 1474 Server: "https://1.2.3.4", 1475 }, 1476 expectFinalizerRemoved: true, 1477 }, 1478 { 1479 name: "invalid cluster: invalid cluster name", 1480 destinationField: v1alpha1.ApplicationDestination{ 1481 Namespace: "namespace", 1482 Name: "invalid-cluster", 1483 }, 1484 expectFinalizerRemoved: true, 1485 }, 1486 { 1487 name: "invalid cluster by both valid", 1488 destinationField: v1alpha1.ApplicationDestination{ 1489 Namespace: "namespace", 1490 Name: "mycluster2", 1491 Server: "https://kubernetes.default.svc", 1492 }, 1493 expectFinalizerRemoved: true, 1494 }, 1495 { 1496 name: "invalid cluster by both invalid", 1497 destinationField: v1alpha1.ApplicationDestination{ 1498 Namespace: "namespace", 1499 Name: "mycluster3", 1500 Server: "https://4.5.6.7", 1501 }, 1502 expectFinalizerRemoved: true, 1503 }, 1504 { 1505 name: "valid cluster by name", 1506 destinationField: v1alpha1.ApplicationDestination{ 1507 Namespace: "namespace", 1508 Name: "mycluster2", 1509 }, 1510 expectFinalizerRemoved: false, 1511 }, 1512 { 1513 name: "valid cluster by server", 1514 destinationField: v1alpha1.ApplicationDestination{ 1515 Namespace: "namespace", 1516 Server: "https://kubernetes.default.svc", 1517 }, 1518 expectFinalizerRemoved: false, 1519 }, 1520 } { 1521 1522 t.Run(c.name, func(t *testing.T) { 1523 1524 appSet := v1alpha1.ApplicationSet{ 1525 ObjectMeta: metav1.ObjectMeta{ 1526 Name: "name", 1527 Namespace: "namespace", 1528 }, 1529 Spec: v1alpha1.ApplicationSetSpec{ 1530 Template: v1alpha1.ApplicationSetTemplate{ 1531 Spec: v1alpha1.ApplicationSpec{ 1532 Project: "project", 1533 }, 1534 }, 1535 }, 1536 } 1537 1538 app := v1alpha1.Application{ 1539 ObjectMeta: metav1.ObjectMeta{ 1540 Name: "app1", 1541 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 1542 }, 1543 Spec: v1alpha1.ApplicationSpec{ 1544 Project: "project", 1545 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 1546 Destination: c.destinationField, 1547 }, 1548 } 1549 1550 initObjs := []crtclient.Object{&app, &appSet} 1551 1552 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1553 secret := &corev1.Secret{ 1554 ObjectMeta: metav1.ObjectMeta{ 1555 Name: "my-secret", 1556 Namespace: "namespace", 1557 Labels: map[string]string{ 1558 generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster, 1559 }, 1560 }, 1561 Data: map[string][]byte{ 1562 // Since this test requires the cluster to be an invalid destination, we 1563 // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) 1564 "name": []byte("mycluster2"), 1565 "server": []byte("https://kubernetes.default.svc"), 1566 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 1567 }, 1568 } 1569 1570 objects := append([]runtime.Object{}, secret) 1571 kubeclientset := kubefake.NewSimpleClientset(objects...) 1572 1573 r := ApplicationSetReconciler{ 1574 Client: client, 1575 Scheme: scheme, 1576 Recorder: record.NewFakeRecorder(10), 1577 KubeClientset: kubeclientset, 1578 Cache: &fakeCache{}, 1579 } 1580 // settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "argocd") 1581 // argoDB := db.NewDB("argocd", settingsMgr, r.KubeClientset) 1582 // clusterList, err := argoDB.ListClusters(context.Background()) 1583 clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace") 1584 assert.NoError(t, err, "Unexpected error") 1585 1586 appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": ""}) 1587 1588 appInputParam := app.DeepCopy() 1589 1590 err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, clusterList, appLog) 1591 assert.NoError(t, err, "Unexpected error") 1592 1593 retrievedApp := v1alpha1.Application{} 1594 err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp) 1595 assert.NoError(t, err, "Unexpected error") 1596 1597 finalizerRemoved := len(retrievedApp.Finalizers) == 0 1598 1599 assert.True(t, c.expectFinalizerRemoved == finalizerRemoved) 1600 1601 bytes, _ := json.MarshalIndent(retrievedApp, "", " ") 1602 t.Log("Contents of app after call:", string(bytes)) 1603 1604 }) 1605 } 1606 } 1607 1608 func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) { 1609 scheme := runtime.NewScheme() 1610 err := v1alpha1.AddToScheme(scheme) 1611 assert.Nil(t, err) 1612 1613 err = v1alpha1.AddToScheme(scheme) 1614 assert.Nil(t, err) 1615 1616 for _, c := range []struct { 1617 // name is human-readable test name 1618 name string 1619 }{ 1620 { 1621 name: "ownerReferences cleared", 1622 }, 1623 } { 1624 t.Run(c.name, func(t *testing.T) { 1625 appSet := v1alpha1.ApplicationSet{ 1626 ObjectMeta: metav1.ObjectMeta{ 1627 Name: "name", 1628 Namespace: "namespace", 1629 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 1630 }, 1631 Spec: v1alpha1.ApplicationSetSpec{ 1632 Template: v1alpha1.ApplicationSetTemplate{ 1633 Spec: v1alpha1.ApplicationSpec{ 1634 Project: "project", 1635 }, 1636 }, 1637 }, 1638 } 1639 1640 app := v1alpha1.Application{ 1641 ObjectMeta: metav1.ObjectMeta{ 1642 Name: "app1", 1643 Namespace: "namespace", 1644 }, 1645 Spec: v1alpha1.ApplicationSpec{ 1646 Project: "project", 1647 Source: &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, 1648 Destination: v1alpha1.ApplicationDestination{ 1649 Namespace: "namespace", 1650 Server: "https://kubernetes.default.svc", 1651 }, 1652 }, 1653 } 1654 1655 err := controllerutil.SetControllerReference(&appSet, &app, scheme) 1656 assert.NoError(t, err, "Unexpected error") 1657 1658 initObjs := []crtclient.Object{&app, &appSet} 1659 1660 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1661 1662 r := ApplicationSetReconciler{ 1663 Client: client, 1664 Scheme: scheme, 1665 Recorder: record.NewFakeRecorder(10), 1666 KubeClientset: nil, 1667 Cache: &fakeCache{}, 1668 } 1669 1670 err = r.removeOwnerReferencesOnDeleteAppSet(context.Background(), appSet) 1671 assert.NoError(t, err, "Unexpected error") 1672 1673 retrievedApp := v1alpha1.Application{} 1674 err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp) 1675 assert.NoError(t, err, "Unexpected error") 1676 1677 ownerReferencesRemoved := len(retrievedApp.OwnerReferences) == 0 1678 assert.True(t, ownerReferencesRemoved) 1679 }) 1680 } 1681 } 1682 1683 func TestCreateApplications(t *testing.T) { 1684 1685 scheme := runtime.NewScheme() 1686 err := v1alpha1.AddToScheme(scheme) 1687 assert.Nil(t, err) 1688 1689 err = v1alpha1.AddToScheme(scheme) 1690 assert.Nil(t, err) 1691 1692 testCases := []struct { 1693 name string 1694 appSet v1alpha1.ApplicationSet 1695 existsApps []v1alpha1.Application 1696 apps []v1alpha1.Application 1697 expected []v1alpha1.Application 1698 }{ 1699 { 1700 name: "no existing apps", 1701 appSet: v1alpha1.ApplicationSet{ 1702 ObjectMeta: metav1.ObjectMeta{ 1703 Name: "name", 1704 Namespace: "namespace", 1705 }, 1706 }, 1707 existsApps: nil, 1708 apps: []v1alpha1.Application{ 1709 { 1710 ObjectMeta: metav1.ObjectMeta{ 1711 Name: "app1", 1712 }, 1713 }, 1714 }, 1715 expected: []v1alpha1.Application{ 1716 { 1717 TypeMeta: metav1.TypeMeta{ 1718 Kind: application.ApplicationKind, 1719 APIVersion: "argoproj.io/v1alpha1", 1720 }, 1721 ObjectMeta: metav1.ObjectMeta{ 1722 Name: "app1", 1723 Namespace: "namespace", 1724 ResourceVersion: "1", 1725 }, 1726 Spec: v1alpha1.ApplicationSpec{ 1727 Project: "default", 1728 }, 1729 }, 1730 }, 1731 }, 1732 { 1733 name: "existing apps", 1734 appSet: v1alpha1.ApplicationSet{ 1735 ObjectMeta: metav1.ObjectMeta{ 1736 Name: "name", 1737 Namespace: "namespace", 1738 }, 1739 Spec: v1alpha1.ApplicationSetSpec{ 1740 Template: v1alpha1.ApplicationSetTemplate{ 1741 Spec: v1alpha1.ApplicationSpec{ 1742 Project: "project", 1743 }, 1744 }, 1745 }, 1746 }, 1747 existsApps: []v1alpha1.Application{ 1748 { 1749 TypeMeta: metav1.TypeMeta{ 1750 Kind: application.ApplicationKind, 1751 APIVersion: "argoproj.io/v1alpha1", 1752 }, 1753 ObjectMeta: metav1.ObjectMeta{ 1754 Name: "app1", 1755 Namespace: "namespace", 1756 ResourceVersion: "2", 1757 }, 1758 Spec: v1alpha1.ApplicationSpec{ 1759 Project: "test", 1760 }, 1761 }, 1762 }, 1763 apps: []v1alpha1.Application{ 1764 { 1765 ObjectMeta: metav1.ObjectMeta{ 1766 Name: "app1", 1767 }, 1768 Spec: v1alpha1.ApplicationSpec{ 1769 Project: "project", 1770 }, 1771 }, 1772 }, 1773 expected: []v1alpha1.Application{ 1774 { 1775 TypeMeta: metav1.TypeMeta{ 1776 Kind: application.ApplicationKind, 1777 APIVersion: "argoproj.io/v1alpha1", 1778 }, 1779 ObjectMeta: metav1.ObjectMeta{ 1780 Name: "app1", 1781 Namespace: "namespace", 1782 ResourceVersion: "2", 1783 }, 1784 Spec: v1alpha1.ApplicationSpec{ 1785 Project: "test", 1786 }, 1787 }, 1788 }, 1789 }, 1790 { 1791 name: "existing apps with different project", 1792 appSet: v1alpha1.ApplicationSet{ 1793 ObjectMeta: metav1.ObjectMeta{ 1794 Name: "name", 1795 Namespace: "namespace", 1796 }, 1797 Spec: v1alpha1.ApplicationSetSpec{ 1798 Template: v1alpha1.ApplicationSetTemplate{ 1799 Spec: v1alpha1.ApplicationSpec{ 1800 Project: "project", 1801 }, 1802 }, 1803 }, 1804 }, 1805 existsApps: []v1alpha1.Application{ 1806 { 1807 TypeMeta: metav1.TypeMeta{ 1808 Kind: application.ApplicationKind, 1809 APIVersion: "argoproj.io/v1alpha1", 1810 }, 1811 ObjectMeta: metav1.ObjectMeta{ 1812 Name: "app1", 1813 Namespace: "namespace", 1814 ResourceVersion: "2", 1815 }, 1816 Spec: v1alpha1.ApplicationSpec{ 1817 Project: "test", 1818 }, 1819 }, 1820 }, 1821 apps: []v1alpha1.Application{ 1822 { 1823 ObjectMeta: metav1.ObjectMeta{ 1824 Name: "app2", 1825 }, 1826 Spec: v1alpha1.ApplicationSpec{ 1827 Project: "project", 1828 }, 1829 }, 1830 }, 1831 expected: []v1alpha1.Application{ 1832 { 1833 TypeMeta: metav1.TypeMeta{ 1834 Kind: application.ApplicationKind, 1835 APIVersion: "argoproj.io/v1alpha1", 1836 }, 1837 ObjectMeta: metav1.ObjectMeta{ 1838 Name: "app2", 1839 Namespace: "namespace", 1840 ResourceVersion: "1", 1841 }, 1842 Spec: v1alpha1.ApplicationSpec{ 1843 Project: "project", 1844 }, 1845 }, 1846 }, 1847 }, 1848 } 1849 1850 for _, c := range testCases { 1851 t.Run(c.name, func(t *testing.T) { 1852 initObjs := []crtclient.Object{&c.appSet} 1853 for _, a := range c.existsApps { 1854 err = controllerutil.SetControllerReference(&c.appSet, &a, scheme) 1855 assert.Nil(t, err) 1856 initObjs = append(initObjs, &a) 1857 } 1858 1859 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 1860 1861 r := ApplicationSetReconciler{ 1862 Client: client, 1863 Scheme: scheme, 1864 Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)), 1865 Cache: &fakeCache{}, 1866 } 1867 1868 err = r.createInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.apps) 1869 assert.Nil(t, err) 1870 1871 for _, obj := range c.expected { 1872 got := &v1alpha1.Application{} 1873 _ = client.Get(context.Background(), crtclient.ObjectKey{ 1874 Namespace: obj.Namespace, 1875 Name: obj.Name, 1876 }, got) 1877 1878 err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme) 1879 assert.Nil(t, err) 1880 1881 assert.Equal(t, obj, *got) 1882 } 1883 }) 1884 } 1885 } 1886 1887 func TestDeleteInCluster(t *testing.T) { 1888 1889 scheme := runtime.NewScheme() 1890 err := v1alpha1.AddToScheme(scheme) 1891 assert.Nil(t, err) 1892 err = v1alpha1.AddToScheme(scheme) 1893 assert.Nil(t, err) 1894 1895 for _, c := range []struct { 1896 // appSet is the application set on which the delete function is called 1897 appSet v1alpha1.ApplicationSet 1898 // existingApps is the current state of Applications on the cluster 1899 existingApps []v1alpha1.Application 1900 // desireApps is the apps generated by the generator that we wish to keep alive 1901 desiredApps []v1alpha1.Application 1902 // expected is the list of applications that we expect to exist after calling delete 1903 expected []v1alpha1.Application 1904 // notExpected is the list of applications that we expect not to exist after calling delete 1905 notExpected []v1alpha1.Application 1906 }{ 1907 { 1908 appSet: v1alpha1.ApplicationSet{ 1909 ObjectMeta: metav1.ObjectMeta{ 1910 Name: "name", 1911 Namespace: "namespace", 1912 }, 1913 Spec: v1alpha1.ApplicationSetSpec{ 1914 Template: v1alpha1.ApplicationSetTemplate{ 1915 Spec: v1alpha1.ApplicationSpec{ 1916 Project: "project", 1917 }, 1918 }, 1919 }, 1920 }, 1921 existingApps: []v1alpha1.Application{ 1922 { 1923 TypeMeta: metav1.TypeMeta{ 1924 Kind: application.ApplicationKind, 1925 APIVersion: "argoproj.io/v1alpha1", 1926 }, 1927 ObjectMeta: metav1.ObjectMeta{ 1928 Name: "delete", 1929 Namespace: "namespace", 1930 ResourceVersion: "2", 1931 }, 1932 Spec: v1alpha1.ApplicationSpec{ 1933 Project: "project", 1934 }, 1935 }, 1936 { 1937 TypeMeta: metav1.TypeMeta{ 1938 Kind: application.ApplicationKind, 1939 APIVersion: "argoproj.io/v1alpha1", 1940 }, 1941 ObjectMeta: metav1.ObjectMeta{ 1942 Name: "keep", 1943 Namespace: "namespace", 1944 ResourceVersion: "2", 1945 }, 1946 Spec: v1alpha1.ApplicationSpec{ 1947 Project: "project", 1948 }, 1949 }, 1950 }, 1951 desiredApps: []v1alpha1.Application{ 1952 { 1953 ObjectMeta: metav1.ObjectMeta{ 1954 Name: "keep", 1955 }, 1956 Spec: v1alpha1.ApplicationSpec{ 1957 Project: "project", 1958 }, 1959 }, 1960 }, 1961 expected: []v1alpha1.Application{ 1962 { 1963 TypeMeta: metav1.TypeMeta{ 1964 Kind: application.ApplicationKind, 1965 APIVersion: "argoproj.io/v1alpha1", 1966 }, 1967 ObjectMeta: metav1.ObjectMeta{ 1968 Name: "keep", 1969 Namespace: "namespace", 1970 ResourceVersion: "2", 1971 }, 1972 Spec: v1alpha1.ApplicationSpec{ 1973 Project: "project", 1974 }, 1975 }, 1976 }, 1977 notExpected: []v1alpha1.Application{ 1978 { 1979 TypeMeta: metav1.TypeMeta{ 1980 Kind: application.ApplicationKind, 1981 APIVersion: "argoproj.io/v1alpha1", 1982 }, 1983 ObjectMeta: metav1.ObjectMeta{ 1984 Name: "delete", 1985 Namespace: "namespace", 1986 ResourceVersion: "1", 1987 }, 1988 Spec: v1alpha1.ApplicationSpec{ 1989 Project: "project", 1990 }, 1991 }, 1992 }, 1993 }, 1994 } { 1995 initObjs := []crtclient.Object{&c.appSet} 1996 for _, a := range c.existingApps { 1997 temp := a 1998 err = controllerutil.SetControllerReference(&c.appSet, &temp, scheme) 1999 assert.Nil(t, err) 2000 initObjs = append(initObjs, &temp) 2001 } 2002 2003 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2004 2005 r := ApplicationSetReconciler{ 2006 Client: client, 2007 Scheme: scheme, 2008 Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)), 2009 KubeClientset: kubefake.NewSimpleClientset(), 2010 } 2011 2012 err = r.deleteInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps) 2013 assert.Nil(t, err) 2014 2015 // For each of the expected objects, verify they exist on the cluster 2016 for _, obj := range c.expected { 2017 got := &v1alpha1.Application{} 2018 _ = client.Get(context.Background(), crtclient.ObjectKey{ 2019 Namespace: obj.Namespace, 2020 Name: obj.Name, 2021 }, got) 2022 2023 err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme) 2024 assert.Nil(t, err) 2025 2026 assert.Equal(t, obj, *got) 2027 } 2028 2029 // Verify each of the unexpected objs cannot be found 2030 for _, obj := range c.notExpected { 2031 got := &v1alpha1.Application{} 2032 err := client.Get(context.Background(), crtclient.ObjectKey{ 2033 Namespace: obj.Namespace, 2034 Name: obj.Name, 2035 }, got) 2036 2037 assert.EqualError(t, err, fmt.Sprintf("applications.argoproj.io \"%s\" not found", obj.Name)) 2038 } 2039 } 2040 } 2041 2042 func TestGetMinRequeueAfter(t *testing.T) { 2043 scheme := runtime.NewScheme() 2044 err := v1alpha1.AddToScheme(scheme) 2045 assert.Nil(t, err) 2046 err = v1alpha1.AddToScheme(scheme) 2047 assert.Nil(t, err) 2048 2049 client := fake.NewClientBuilder().WithScheme(scheme).Build() 2050 2051 generator := v1alpha1.ApplicationSetGenerator{ 2052 List: &v1alpha1.ListGenerator{}, 2053 Git: &v1alpha1.GitGenerator{}, 2054 Clusters: &v1alpha1.ClusterGenerator{}, 2055 } 2056 2057 generatorMock0 := generatorMock{} 2058 generatorMock0.On("GetRequeueAfter", &generator). 2059 Return(generators.NoRequeueAfter) 2060 2061 generatorMock1 := generatorMock{} 2062 generatorMock1.On("GetRequeueAfter", &generator). 2063 Return(time.Duration(1) * time.Second) 2064 2065 generatorMock10 := generatorMock{} 2066 generatorMock10.On("GetRequeueAfter", &generator). 2067 Return(time.Duration(10) * time.Second) 2068 2069 r := ApplicationSetReconciler{ 2070 Client: client, 2071 Scheme: scheme, 2072 Recorder: record.NewFakeRecorder(0), 2073 Cache: &fakeCache{}, 2074 Generators: map[string]generators.Generator{ 2075 "List": &generatorMock10, 2076 "Git": &generatorMock1, 2077 "Clusters": &generatorMock1, 2078 }, 2079 } 2080 2081 got := r.getMinRequeueAfter(&v1alpha1.ApplicationSet{ 2082 Spec: v1alpha1.ApplicationSetSpec{ 2083 Generators: []v1alpha1.ApplicationSetGenerator{generator}, 2084 }, 2085 }) 2086 2087 assert.Equal(t, time.Duration(1)*time.Second, got) 2088 } 2089 2090 func TestValidateGeneratedApplications(t *testing.T) { 2091 2092 scheme := runtime.NewScheme() 2093 err := v1alpha1.AddToScheme(scheme) 2094 assert.Nil(t, err) 2095 2096 err = v1alpha1.AddToScheme(scheme) 2097 assert.Nil(t, err) 2098 2099 client := fake.NewClientBuilder().WithScheme(scheme).Build() 2100 2101 // Valid cluster 2102 myCluster := v1alpha1.Cluster{ 2103 Server: "https://kubernetes.default.svc", 2104 Name: "my-cluster", 2105 } 2106 2107 // Valid project 2108 myProject := &v1alpha1.AppProject{ 2109 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "namespace"}, 2110 Spec: v1alpha1.AppProjectSpec{ 2111 SourceRepos: []string{"*"}, 2112 Destinations: []v1alpha1.ApplicationDestination{ 2113 { 2114 Namespace: "*", 2115 Server: "*", 2116 }, 2117 }, 2118 ClusterResourceWhitelist: []metav1.GroupKind{ 2119 { 2120 Group: "*", 2121 Kind: "*", 2122 }, 2123 }, 2124 }, 2125 } 2126 2127 // Test a subset of the validations that 'validateGeneratedApplications' performs 2128 for _, cc := range []struct { 2129 name string 2130 apps []v1alpha1.Application 2131 expectedErrors []string 2132 validationErrors map[int]error 2133 }{ 2134 { 2135 name: "valid app should return true", 2136 apps: []v1alpha1.Application{ 2137 { 2138 TypeMeta: metav1.TypeMeta{}, 2139 ObjectMeta: metav1.ObjectMeta{}, 2140 Spec: v1alpha1.ApplicationSpec{ 2141 Project: "default", 2142 Source: &v1alpha1.ApplicationSource{ 2143 RepoURL: "https://url", 2144 Path: "/", 2145 TargetRevision: "HEAD", 2146 }, 2147 Destination: v1alpha1.ApplicationDestination{ 2148 Namespace: "namespace", 2149 Name: "my-cluster", 2150 }, 2151 }, 2152 }, 2153 }, 2154 expectedErrors: []string{}, 2155 validationErrors: map[int]error{}, 2156 }, 2157 { 2158 name: "can't have both name and server defined", 2159 apps: []v1alpha1.Application{ 2160 { 2161 TypeMeta: metav1.TypeMeta{}, 2162 ObjectMeta: metav1.ObjectMeta{}, 2163 Spec: v1alpha1.ApplicationSpec{ 2164 Project: "default", 2165 Source: &v1alpha1.ApplicationSource{ 2166 RepoURL: "https://url", 2167 Path: "/", 2168 TargetRevision: "HEAD", 2169 }, 2170 Destination: v1alpha1.ApplicationDestination{ 2171 Namespace: "namespace", 2172 Server: "my-server", 2173 Name: "my-cluster", 2174 }, 2175 }, 2176 }, 2177 }, 2178 expectedErrors: []string{"application destination can't have both name and server defined"}, 2179 validationErrors: map[int]error{0: fmt.Errorf("application destination spec is invalid: application destination can't have both name and server defined: my-cluster my-server")}, 2180 }, 2181 { 2182 name: "project mismatch should return error", 2183 apps: []v1alpha1.Application{ 2184 { 2185 TypeMeta: metav1.TypeMeta{}, 2186 ObjectMeta: metav1.ObjectMeta{}, 2187 Spec: v1alpha1.ApplicationSpec{ 2188 Project: "DOES-NOT-EXIST", 2189 Source: &v1alpha1.ApplicationSource{ 2190 RepoURL: "https://url", 2191 Path: "/", 2192 TargetRevision: "HEAD", 2193 }, 2194 Destination: v1alpha1.ApplicationDestination{ 2195 Namespace: "namespace", 2196 Name: "my-cluster", 2197 }, 2198 }, 2199 }, 2200 }, 2201 expectedErrors: []string{"application references project DOES-NOT-EXIST which does not exist"}, 2202 validationErrors: map[int]error{0: fmt.Errorf("application references project DOES-NOT-EXIST which does not exist")}, 2203 }, 2204 { 2205 name: "valid app should return true", 2206 apps: []v1alpha1.Application{ 2207 { 2208 TypeMeta: metav1.TypeMeta{}, 2209 ObjectMeta: metav1.ObjectMeta{}, 2210 Spec: v1alpha1.ApplicationSpec{ 2211 Project: "default", 2212 Source: &v1alpha1.ApplicationSource{ 2213 RepoURL: "https://url", 2214 Path: "/", 2215 TargetRevision: "HEAD", 2216 }, 2217 Destination: v1alpha1.ApplicationDestination{ 2218 Namespace: "namespace", 2219 Name: "my-cluster", 2220 }, 2221 }, 2222 }, 2223 }, 2224 expectedErrors: []string{}, 2225 validationErrors: map[int]error{}, 2226 }, 2227 { 2228 name: "cluster should match", 2229 apps: []v1alpha1.Application{ 2230 { 2231 TypeMeta: metav1.TypeMeta{}, 2232 ObjectMeta: metav1.ObjectMeta{}, 2233 Spec: v1alpha1.ApplicationSpec{ 2234 Project: "default", 2235 Source: &v1alpha1.ApplicationSource{ 2236 RepoURL: "https://url", 2237 Path: "/", 2238 TargetRevision: "HEAD", 2239 }, 2240 Destination: v1alpha1.ApplicationDestination{ 2241 Namespace: "namespace", 2242 Name: "nonexistent-cluster", 2243 }, 2244 }, 2245 }, 2246 }, 2247 expectedErrors: []string{"there are no clusters with this name: nonexistent-cluster"}, 2248 validationErrors: map[int]error{0: fmt.Errorf("application destination spec is invalid: unable to find destination server: there are no clusters with this name: nonexistent-cluster")}, 2249 }, 2250 } { 2251 2252 t.Run(cc.name, func(t *testing.T) { 2253 2254 secret := &corev1.Secret{ 2255 ObjectMeta: metav1.ObjectMeta{ 2256 Name: "my-secret", 2257 Namespace: "namespace", 2258 Labels: map[string]string{ 2259 generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster, 2260 }, 2261 }, 2262 Data: map[string][]byte{ 2263 "name": []byte("my-cluster"), 2264 "server": []byte("https://kubernetes.default.svc"), 2265 "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), 2266 }, 2267 } 2268 2269 objects := append([]runtime.Object{}, secret) 2270 kubeclientset := kubefake.NewSimpleClientset(objects...) 2271 2272 argoDBMock := dbmocks.ArgoDB{} 2273 argoDBMock.On("GetCluster", mock.Anything, "https://kubernetes.default.svc").Return(&myCluster, nil) 2274 argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ 2275 myCluster, 2276 }}, nil) 2277 2278 argoObjs := []runtime.Object{myProject} 2279 for _, app := range cc.apps { 2280 argoObjs = append(argoObjs, &app) 2281 } 2282 2283 r := ApplicationSetReconciler{ 2284 Client: client, 2285 Scheme: scheme, 2286 Recorder: record.NewFakeRecorder(1), 2287 Cache: &fakeCache{}, 2288 Generators: map[string]generators.Generator{}, 2289 ArgoDB: &argoDBMock, 2290 ArgoCDNamespace: "namespace", 2291 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 2292 KubeClientset: kubeclientset, 2293 } 2294 2295 appSetInfo := v1alpha1.ApplicationSet{} 2296 2297 validationErrors, _ := r.validateGeneratedApplications(context.TODO(), cc.apps, appSetInfo) 2298 var errorMessages []string 2299 for _, v := range validationErrors { 2300 errorMessages = append(errorMessages, v.Error()) 2301 } 2302 2303 if len(errorMessages) == 0 { 2304 assert.Equal(t, len(cc.expectedErrors), 0, "Expected errors but none were seen") 2305 } else { 2306 // An error was returned: it should be expected 2307 matched := false 2308 for _, expectedErr := range cc.expectedErrors { 2309 foundMatch := strings.Contains(strings.Join(errorMessages, ";"), expectedErr) 2310 assert.True(t, foundMatch, "Unble to locate expected error: %s", cc.expectedErrors) 2311 matched = matched || foundMatch 2312 } 2313 assert.True(t, matched, "An unexpected error occurrred: %v", err) 2314 // validation message was returned: it should be expected 2315 matched = false 2316 foundMatch := reflect.DeepEqual(validationErrors, cc.validationErrors) 2317 var message string 2318 for _, v := range validationErrors { 2319 message = v.Error() 2320 break 2321 } 2322 assert.True(t, foundMatch, "Unble to locate validation message: %s", message) 2323 matched = matched || foundMatch 2324 assert.True(t, matched, "An unexpected error occurrred: %v", err) 2325 } 2326 }) 2327 } 2328 } 2329 2330 func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) { 2331 2332 scheme := runtime.NewScheme() 2333 err := v1alpha1.AddToScheme(scheme) 2334 assert.Nil(t, err) 2335 err = v1alpha1.AddToScheme(scheme) 2336 assert.Nil(t, err) 2337 2338 project := v1alpha1.AppProject{ 2339 ObjectMeta: metav1.ObjectMeta{Name: "good-project", Namespace: "argocd"}, 2340 } 2341 appSet := v1alpha1.ApplicationSet{ 2342 ObjectMeta: metav1.ObjectMeta{ 2343 Name: "name", 2344 Namespace: "argocd", 2345 }, 2346 Spec: v1alpha1.ApplicationSetSpec{ 2347 GoTemplate: true, 2348 Generators: []v1alpha1.ApplicationSetGenerator{ 2349 { 2350 List: &v1alpha1.ListGenerator{ 2351 Elements: []apiextensionsv1.JSON{{ 2352 Raw: []byte(`{"project": "good-project"}`), 2353 }, { 2354 Raw: []byte(`{"project": "bad-project"}`), 2355 }}, 2356 }, 2357 }, 2358 }, 2359 Template: v1alpha1.ApplicationSetTemplate{ 2360 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2361 Name: "{{.project}}", 2362 Namespace: "argocd", 2363 }, 2364 Spec: v1alpha1.ApplicationSpec{ 2365 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 2366 Project: "{{.project}}", 2367 Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"}, 2368 }, 2369 }, 2370 }, 2371 } 2372 2373 kubeclientset := kubefake.NewSimpleClientset() 2374 argoDBMock := dbmocks.ArgoDB{} 2375 argoObjs := []runtime.Object{&project} 2376 2377 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2378 goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"} 2379 badCluster := v1alpha1.Cluster{Server: "https://bad-cluster", Name: "bad-cluster"} 2380 argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil) 2381 argoDBMock.On("GetCluster", mock.Anything, "https://bad-cluster").Return(&badCluster, nil) 2382 argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ 2383 goodCluster, 2384 }}, nil) 2385 2386 r := ApplicationSetReconciler{ 2387 Client: client, 2388 Scheme: scheme, 2389 Renderer: &utils.Render{}, 2390 Recorder: record.NewFakeRecorder(1), 2391 Cache: &fakeCache{}, 2392 Generators: map[string]generators.Generator{ 2393 "List": generators.NewListGenerator(), 2394 }, 2395 ArgoDB: &argoDBMock, 2396 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 2397 KubeClientset: kubeclientset, 2398 Policy: v1alpha1.ApplicationsSyncPolicySync, 2399 ArgoCDNamespace: "argocd", 2400 } 2401 2402 req := ctrl.Request{ 2403 NamespacedName: types.NamespacedName{ 2404 Namespace: "argocd", 2405 Name: "name", 2406 }, 2407 } 2408 2409 // Verify that on validation error, no error is returned, but the object is requeued 2410 res, err := r.Reconcile(context.Background(), req) 2411 assert.Nil(t, err) 2412 assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError) 2413 2414 var app v1alpha1.Application 2415 2416 // make sure good app got created 2417 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-project"}, &app) 2418 assert.NoError(t, err) 2419 assert.Equal(t, app.Name, "good-project") 2420 2421 // make sure bad app was not created 2422 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "bad-project"}, &app) 2423 assert.Error(t, err) 2424 } 2425 2426 func TestSetApplicationSetStatusCondition(t *testing.T) { 2427 scheme := runtime.NewScheme() 2428 err := v1alpha1.AddToScheme(scheme) 2429 assert.Nil(t, err) 2430 err = v1alpha1.AddToScheme(scheme) 2431 assert.Nil(t, err) 2432 2433 appSet := v1alpha1.ApplicationSet{ 2434 ObjectMeta: metav1.ObjectMeta{ 2435 Name: "name", 2436 Namespace: "argocd", 2437 }, 2438 Spec: v1alpha1.ApplicationSetSpec{ 2439 Generators: []v1alpha1.ApplicationSetGenerator{ 2440 {List: &v1alpha1.ListGenerator{ 2441 Elements: []apiextensionsv1.JSON{{ 2442 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 2443 }}, 2444 }}, 2445 }, 2446 Template: v1alpha1.ApplicationSetTemplate{}, 2447 }, 2448 } 2449 2450 appCondition := v1alpha1.ApplicationSetCondition{ 2451 Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, 2452 Message: "All applications have been generated successfully", 2453 Reason: v1alpha1.ApplicationSetReasonApplicationSetUpToDate, 2454 Status: v1alpha1.ApplicationSetConditionStatusTrue, 2455 } 2456 2457 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 2458 argoDBMock := dbmocks.ArgoDB{} 2459 argoObjs := []runtime.Object{} 2460 2461 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2462 2463 r := ApplicationSetReconciler{ 2464 Client: client, 2465 Scheme: scheme, 2466 Renderer: &utils.Render{}, 2467 Recorder: record.NewFakeRecorder(1), 2468 Cache: &fakeCache{}, 2469 Generators: map[string]generators.Generator{ 2470 "List": generators.NewListGenerator(), 2471 }, 2472 ArgoDB: &argoDBMock, 2473 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 2474 KubeClientset: kubeclientset, 2475 } 2476 2477 err = r.setApplicationSetStatusCondition(context.TODO(), &appSet, appCondition, true) 2478 assert.Nil(t, err) 2479 2480 assert.Len(t, appSet.Status.Conditions, 3) 2481 } 2482 2483 func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.Application { 2484 2485 scheme := runtime.NewScheme() 2486 err := v1alpha1.AddToScheme(scheme) 2487 assert.Nil(t, err) 2488 err = v1alpha1.AddToScheme(scheme) 2489 assert.Nil(t, err) 2490 2491 defaultProject := v1alpha1.AppProject{ 2492 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, 2493 Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}}, 2494 } 2495 appSet := v1alpha1.ApplicationSet{ 2496 ObjectMeta: metav1.ObjectMeta{ 2497 Name: "name", 2498 Namespace: "argocd", 2499 }, 2500 Spec: v1alpha1.ApplicationSetSpec{ 2501 Generators: []v1alpha1.ApplicationSetGenerator{ 2502 { 2503 List: &v1alpha1.ListGenerator{ 2504 Elements: []apiextensionsv1.JSON{{ 2505 Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`), 2506 }}, 2507 }, 2508 }, 2509 }, 2510 SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{ 2511 ApplicationsSync: &applicationsSyncPolicy, 2512 }, 2513 Template: v1alpha1.ApplicationSetTemplate{ 2514 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2515 Name: "{{cluster}}", 2516 Namespace: "argocd", 2517 }, 2518 Spec: v1alpha1.ApplicationSpec{ 2519 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 2520 Project: "default", 2521 Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"}, 2522 }, 2523 }, 2524 }, 2525 } 2526 2527 kubeclientset := kubefake.NewSimpleClientset() 2528 argoDBMock := dbmocks.ArgoDB{} 2529 argoObjs := []runtime.Object{&defaultProject} 2530 2531 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2532 goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"} 2533 argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil) 2534 argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ 2535 goodCluster, 2536 }}, nil) 2537 2538 r := ApplicationSetReconciler{ 2539 Client: client, 2540 Scheme: scheme, 2541 Renderer: &utils.Render{}, 2542 Recorder: record.NewFakeRecorder(recordBuffer), 2543 Cache: &fakeCache{}, 2544 Generators: map[string]generators.Generator{ 2545 "List": generators.NewListGenerator(), 2546 }, 2547 ArgoDB: &argoDBMock, 2548 ArgoCDNamespace: "argocd", 2549 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 2550 KubeClientset: kubeclientset, 2551 Policy: v1alpha1.ApplicationsSyncPolicySync, 2552 EnablePolicyOverride: allowPolicyOverride, 2553 } 2554 2555 req := ctrl.Request{ 2556 NamespacedName: types.NamespacedName{ 2557 Namespace: "argocd", 2558 Name: "name", 2559 }, 2560 } 2561 2562 // Verify that on validation error, no error is returned, but the object is requeued 2563 resCreate, err := r.Reconcile(context.Background(), req) 2564 assert.Nil(t, err) 2565 assert.True(t, resCreate.RequeueAfter == 0) 2566 2567 var app v1alpha1.Application 2568 2569 // make sure good app got created 2570 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app) 2571 assert.Nil(t, err) 2572 assert.Equal(t, app.Name, "good-cluster") 2573 2574 // Update resource 2575 var retrievedApplicationSet v1alpha1.ApplicationSet 2576 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet) 2577 assert.Nil(t, err) 2578 2579 retrievedApplicationSet.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} 2580 retrievedApplicationSet.Spec.Template.Labels = map[string]string{"label-key": "label-value"} 2581 2582 retrievedApplicationSet.Spec.Template.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{ 2583 Values: "global.test: test", 2584 } 2585 2586 err = r.Client.Update(context.TODO(), &retrievedApplicationSet) 2587 assert.Nil(t, err) 2588 2589 resUpdate, err := r.Reconcile(context.Background(), req) 2590 assert.Nil(t, err) 2591 2592 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app) 2593 assert.Nil(t, err) 2594 assert.True(t, resUpdate.RequeueAfter == 0) 2595 assert.Equal(t, app.Name, "good-cluster") 2596 2597 return app 2598 } 2599 2600 func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) { 2601 2602 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2603 2604 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true) 2605 2606 assert.Nil(t, app.Spec.Source.Helm) 2607 assert.Nil(t, app.ObjectMeta.Annotations) 2608 } 2609 2610 func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) { 2611 2612 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete 2613 2614 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true) 2615 2616 assert.Nil(t, app.Spec.Source.Helm) 2617 assert.Nil(t, app.ObjectMeta.Annotations) 2618 } 2619 2620 func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) { 2621 2622 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate 2623 2624 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true) 2625 2626 assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values) 2627 assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations) 2628 assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels) 2629 } 2630 2631 func TestUpdatePerformedWithSyncPolicySync(t *testing.T) { 2632 2633 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync 2634 2635 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true) 2636 2637 assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values) 2638 assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations) 2639 assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels) 2640 } 2641 2642 func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) { 2643 2644 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2645 2646 app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, false) 2647 2648 assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values) 2649 assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations) 2650 assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels) 2651 } 2652 2653 func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList { 2654 2655 scheme := runtime.NewScheme() 2656 err := v1alpha1.AddToScheme(scheme) 2657 assert.Nil(t, err) 2658 err = v1alpha1.AddToScheme(scheme) 2659 assert.Nil(t, err) 2660 2661 defaultProject := v1alpha1.AppProject{ 2662 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, 2663 Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}}, 2664 } 2665 appSet := v1alpha1.ApplicationSet{ 2666 ObjectMeta: metav1.ObjectMeta{ 2667 Name: "name", 2668 Namespace: "argocd", 2669 }, 2670 Spec: v1alpha1.ApplicationSetSpec{ 2671 Generators: []v1alpha1.ApplicationSetGenerator{ 2672 { 2673 List: &v1alpha1.ListGenerator{ 2674 Elements: []apiextensionsv1.JSON{{ 2675 Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`), 2676 }}, 2677 }, 2678 }, 2679 }, 2680 SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{ 2681 ApplicationsSync: &applicationsSyncPolicy, 2682 }, 2683 Template: v1alpha1.ApplicationSetTemplate{ 2684 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2685 Name: "{{cluster}}", 2686 Namespace: "argocd", 2687 }, 2688 Spec: v1alpha1.ApplicationSpec{ 2689 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 2690 Project: "default", 2691 Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"}, 2692 }, 2693 }, 2694 }, 2695 } 2696 2697 kubeclientset := kubefake.NewSimpleClientset() 2698 argoDBMock := dbmocks.ArgoDB{} 2699 argoObjs := []runtime.Object{&defaultProject} 2700 2701 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 2702 goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"} 2703 argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil) 2704 argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ 2705 goodCluster, 2706 }}, nil) 2707 2708 r := ApplicationSetReconciler{ 2709 Client: client, 2710 Scheme: scheme, 2711 Renderer: &utils.Render{}, 2712 Recorder: record.NewFakeRecorder(recordBuffer), 2713 Cache: &fakeCache{}, 2714 Generators: map[string]generators.Generator{ 2715 "List": generators.NewListGenerator(), 2716 }, 2717 ArgoDB: &argoDBMock, 2718 ArgoCDNamespace: "argocd", 2719 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 2720 KubeClientset: kubeclientset, 2721 Policy: v1alpha1.ApplicationsSyncPolicySync, 2722 EnablePolicyOverride: allowPolicyOverride, 2723 } 2724 2725 req := ctrl.Request{ 2726 NamespacedName: types.NamespacedName{ 2727 Namespace: "argocd", 2728 Name: "name", 2729 }, 2730 } 2731 2732 // Verify that on validation error, no error is returned, but the object is requeued 2733 resCreate, err := r.Reconcile(context.Background(), req) 2734 assert.Nil(t, err) 2735 assert.True(t, resCreate.RequeueAfter == 0) 2736 2737 var app v1alpha1.Application 2738 2739 // make sure good app got created 2740 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app) 2741 assert.Nil(t, err) 2742 assert.Equal(t, app.Name, "good-cluster") 2743 2744 // Update resource 2745 var retrievedApplicationSet v1alpha1.ApplicationSet 2746 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet) 2747 assert.Nil(t, err) 2748 retrievedApplicationSet.Spec.Generators = []v1alpha1.ApplicationSetGenerator{ 2749 { 2750 List: &v1alpha1.ListGenerator{ 2751 Elements: []apiextensionsv1.JSON{}, 2752 }, 2753 }, 2754 } 2755 2756 err = r.Client.Update(context.TODO(), &retrievedApplicationSet) 2757 assert.Nil(t, err) 2758 2759 resUpdate, err := r.Reconcile(context.Background(), req) 2760 assert.Nil(t, err) 2761 2762 var apps v1alpha1.ApplicationList 2763 2764 err = r.Client.List(context.TODO(), &apps) 2765 assert.Nil(t, err) 2766 assert.True(t, resUpdate.RequeueAfter == 0) 2767 2768 return apps 2769 } 2770 2771 func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) { 2772 2773 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2774 2775 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 1, true) 2776 2777 assert.Equal(t, "good-cluster", apps.Items[0].Name) 2778 } 2779 2780 func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) { 2781 2782 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate 2783 2784 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 2, true) 2785 2786 assert.Equal(t, "good-cluster", apps.Items[0].Name) 2787 } 2788 2789 func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) { 2790 2791 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete 2792 2793 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true) 2794 2795 assert.Equal(t, 0, len(apps.Items)) 2796 } 2797 2798 func TestDeletePerformedWithSyncPolicySync(t *testing.T) { 2799 2800 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync 2801 2802 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true) 2803 2804 assert.Equal(t, 0, len(apps.Items)) 2805 } 2806 2807 func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) { 2808 2809 applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly 2810 2811 apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false) 2812 2813 assert.Equal(t, 0, len(apps.Items)) 2814 } 2815 2816 // Test app generation from a go template application set using a pull request generator 2817 func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) { 2818 scheme := runtime.NewScheme() 2819 client := fake.NewClientBuilder().WithScheme(scheme).Build() 2820 2821 for _, cases := range []struct { 2822 name string 2823 params []map[string]interface{} 2824 template v1alpha1.ApplicationSetTemplate 2825 expectedApp []v1alpha1.Application 2826 }{ 2827 { 2828 name: "Generate an application from a go template application set manifest using a pull request generator", 2829 params: []map[string]interface{}{{ 2830 "number": "1", 2831 "branch": "branch1", 2832 "branch_slug": "branchSlug1", 2833 "head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958", 2834 "head_short_sha": "089d92cb", 2835 "branch_slugify_default": "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", 2836 "branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature", 2837 "branch_slugify_smarttruncate_enabled": "feat/testwithsmarttruncateenabledramdomlonglistofcharacters", 2838 "labels": []string{"label1"}}, 2839 }, 2840 template: v1alpha1.ApplicationSetTemplate{ 2841 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 2842 Name: "AppSet-{{.branch}}-{{.number}}", 2843 Labels: map[string]string{ 2844 "app1": "{{index .labels 0}}", 2845 "branch-test1": "AppSet-{{.branch_slugify_default | slugify }}", 2846 "branch-test2": "AppSet-{{.branch_slugify_smarttruncate_disabled | slugify 49 false }}", 2847 "branch-test3": "AppSet-{{.branch_slugify_smarttruncate_enabled | slugify 50 true }}", 2848 }, 2849 }, 2850 Spec: v1alpha1.ApplicationSpec{ 2851 Source: &v1alpha1.ApplicationSource{ 2852 RepoURL: "https://testurl/testRepo", 2853 TargetRevision: "{{.head_short_sha}}", 2854 }, 2855 Destination: v1alpha1.ApplicationDestination{ 2856 Server: "https://kubernetes.default.svc", 2857 Namespace: "AppSet-{{.branch_slug}}-{{.head_sha}}", 2858 }, 2859 }, 2860 }, 2861 expectedApp: []v1alpha1.Application{ 2862 { 2863 ObjectMeta: metav1.ObjectMeta{ 2864 Name: "AppSet-branch1-1", 2865 Labels: map[string]string{ 2866 "app1": "label1", 2867 "branch-test1": "AppSet-feat-a-really-long-pull-request-name-to-test-argo", 2868 "branch-test2": "AppSet-feat-areallylongpullrequestnametotestargoslugific", 2869 "branch-test3": "AppSet-feat", 2870 }, 2871 }, 2872 Spec: v1alpha1.ApplicationSpec{ 2873 Source: &v1alpha1.ApplicationSource{ 2874 RepoURL: "https://testurl/testRepo", 2875 TargetRevision: "089d92cb", 2876 }, 2877 Destination: v1alpha1.ApplicationDestination{ 2878 Server: "https://kubernetes.default.svc", 2879 Namespace: "AppSet-branchSlug1-089d92cbf9ff857a39e6feccd32798ca700fb958", 2880 }, 2881 }, 2882 }, 2883 }, 2884 }, 2885 } { 2886 2887 t.Run(cases.name, func(t *testing.T) { 2888 2889 generatorMock := generatorMock{} 2890 generator := v1alpha1.ApplicationSetGenerator{ 2891 PullRequest: &v1alpha1.PullRequestGenerator{}, 2892 } 2893 2894 generatorMock.On("GenerateParams", &generator). 2895 Return(cases.params, nil) 2896 2897 generatorMock.On("GetTemplate", &generator). 2898 Return(&cases.template, nil) 2899 2900 appSetReconciler := ApplicationSetReconciler{ 2901 Client: client, 2902 Scheme: scheme, 2903 Recorder: record.NewFakeRecorder(1), 2904 Cache: &fakeCache{}, 2905 Generators: map[string]generators.Generator{ 2906 "PullRequest": &generatorMock, 2907 }, 2908 Renderer: &utils.Render{}, 2909 KubeClientset: kubefake.NewSimpleClientset(), 2910 } 2911 2912 gotApp, _, _ := appSetReconciler.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{ 2913 Spec: v1alpha1.ApplicationSetSpec{ 2914 GoTemplate: true, 2915 Generators: []v1alpha1.ApplicationSetGenerator{{ 2916 PullRequest: &v1alpha1.PullRequestGenerator{}, 2917 }}, 2918 Template: cases.template, 2919 }, 2920 }, 2921 ) 2922 assert.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name) 2923 assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision) 2924 assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace) 2925 assert.True(t, collections.StringMapsEqual(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels)) 2926 }) 2927 } 2928 } 2929 2930 func TestPolicies(t *testing.T) { 2931 scheme := runtime.NewScheme() 2932 err := v1alpha1.AddToScheme(scheme) 2933 assert.Nil(t, err) 2934 2935 err = v1alpha1.AddToScheme(scheme) 2936 assert.Nil(t, err) 2937 2938 defaultProject := v1alpha1.AppProject{ 2939 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, 2940 Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://kubernetes.default.svc"}}}, 2941 } 2942 myCluster := v1alpha1.Cluster{ 2943 Server: "https://kubernetes.default.svc", 2944 Name: "my-cluster", 2945 } 2946 2947 kubeclientset := kubefake.NewSimpleClientset() 2948 argoDBMock := dbmocks.ArgoDB{} 2949 argoDBMock.On("GetCluster", mock.Anything, "https://kubernetes.default.svc").Return(&myCluster, nil) 2950 argoObjs := []runtime.Object{&defaultProject} 2951 2952 for _, c := range []struct { 2953 name string 2954 policyName string 2955 allowedUpdate bool 2956 allowedDelete bool 2957 }{ 2958 { 2959 name: "Apps are allowed to update and delete", 2960 policyName: "sync", 2961 allowedUpdate: true, 2962 allowedDelete: true, 2963 }, 2964 { 2965 name: "Apps are not allowed to update and delete", 2966 policyName: "create-only", 2967 allowedUpdate: false, 2968 allowedDelete: false, 2969 }, 2970 { 2971 name: "Apps are allowed to update, not allowed to delete", 2972 policyName: "create-update", 2973 allowedUpdate: true, 2974 allowedDelete: false, 2975 }, 2976 { 2977 name: "Apps are allowed to delete, not allowed to update", 2978 policyName: "create-delete", 2979 allowedUpdate: false, 2980 allowedDelete: true, 2981 }, 2982 } { 2983 t.Run(c.name, func(t *testing.T) { 2984 policy := utils.Policies[c.policyName] 2985 assert.NotNil(t, policy) 2986 2987 appSet := v1alpha1.ApplicationSet{ 2988 ObjectMeta: metav1.ObjectMeta{ 2989 Name: "name", 2990 Namespace: "argocd", 2991 }, 2992 Spec: v1alpha1.ApplicationSetSpec{ 2993 GoTemplate: true, 2994 Generators: []v1alpha1.ApplicationSetGenerator{ 2995 { 2996 List: &v1alpha1.ListGenerator{ 2997 Elements: []apiextensionsv1.JSON{ 2998 { 2999 Raw: []byte(`{"name": "my-app"}`), 3000 }, 3001 }, 3002 }, 3003 }, 3004 }, 3005 Template: v1alpha1.ApplicationSetTemplate{ 3006 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ 3007 Name: "{{.name}}", 3008 Namespace: "argocd", 3009 Annotations: map[string]string{ 3010 "key": "value", 3011 }, 3012 }, 3013 Spec: v1alpha1.ApplicationSpec{ 3014 Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"}, 3015 Project: "default", 3016 Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"}, 3017 }, 3018 }, 3019 }, 3020 } 3021 3022 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() 3023 3024 r := ApplicationSetReconciler{ 3025 Client: client, 3026 Scheme: scheme, 3027 Renderer: &utils.Render{}, 3028 Recorder: record.NewFakeRecorder(10), 3029 Cache: &fakeCache{}, 3030 Generators: map[string]generators.Generator{ 3031 "List": generators.NewListGenerator(), 3032 }, 3033 ArgoDB: &argoDBMock, 3034 ArgoCDNamespace: "argocd", 3035 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 3036 KubeClientset: kubeclientset, 3037 Policy: policy, 3038 } 3039 3040 req := ctrl.Request{ 3041 NamespacedName: types.NamespacedName{ 3042 Namespace: "argocd", 3043 Name: "name", 3044 }, 3045 } 3046 3047 // Check if Application is created 3048 res, err := r.Reconcile(context.Background(), req) 3049 assert.Nil(t, err) 3050 assert.True(t, res.RequeueAfter == 0) 3051 3052 var app v1alpha1.Application 3053 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app) 3054 assert.NoError(t, err) 3055 assert.Equal(t, app.Annotations["key"], "value") 3056 3057 // Check if Application is updated 3058 app.Annotations["key"] = "edited" 3059 err = r.Client.Update(context.TODO(), &app) 3060 assert.NoError(t, err) 3061 3062 res, err = r.Reconcile(context.Background(), req) 3063 assert.Nil(t, err) 3064 assert.True(t, res.RequeueAfter == 0) 3065 3066 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app) 3067 assert.NoError(t, err) 3068 3069 if c.allowedUpdate { 3070 assert.Equal(t, app.Annotations["key"], "value") 3071 } else { 3072 assert.Equal(t, app.Annotations["key"], "edited") 3073 } 3074 3075 // Check if Application is deleted 3076 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &appSet) 3077 assert.NoError(t, err) 3078 appSet.Spec.Generators[0] = v1alpha1.ApplicationSetGenerator{ 3079 List: &v1alpha1.ListGenerator{ 3080 Elements: []apiextensionsv1.JSON{}, 3081 }, 3082 } 3083 err = r.Client.Update(context.TODO(), &appSet) 3084 assert.NoError(t, err) 3085 3086 res, err = r.Reconcile(context.Background(), req) 3087 assert.Nil(t, err) 3088 assert.True(t, res.RequeueAfter == 0) 3089 3090 err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app) 3091 assert.NoError(t, err) 3092 if c.allowedDelete { 3093 assert.NotNil(t, app.DeletionTimestamp) 3094 } else { 3095 assert.Nil(t, app.DeletionTimestamp) 3096 } 3097 }) 3098 } 3099 } 3100 3101 func TestSetApplicationSetApplicationStatus(t *testing.T) { 3102 scheme := runtime.NewScheme() 3103 err := v1alpha1.AddToScheme(scheme) 3104 assert.Nil(t, err) 3105 err = v1alpha1.AddToScheme(scheme) 3106 assert.Nil(t, err) 3107 3108 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 3109 argoDBMock := dbmocks.ArgoDB{} 3110 argoObjs := []runtime.Object{} 3111 3112 for _, cc := range []struct { 3113 name string 3114 appSet v1alpha1.ApplicationSet 3115 appStatuses []v1alpha1.ApplicationSetApplicationStatus 3116 expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus 3117 }{ 3118 { 3119 name: "sets a single appstatus", 3120 appSet: v1alpha1.ApplicationSet{ 3121 ObjectMeta: metav1.ObjectMeta{ 3122 Name: "name", 3123 Namespace: "argocd", 3124 }, 3125 Spec: v1alpha1.ApplicationSetSpec{ 3126 Generators: []v1alpha1.ApplicationSetGenerator{ 3127 {List: &v1alpha1.ListGenerator{ 3128 Elements: []apiextensionsv1.JSON{{ 3129 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 3130 }}, 3131 }}, 3132 }, 3133 Template: v1alpha1.ApplicationSetTemplate{}, 3134 }, 3135 }, 3136 appStatuses: []v1alpha1.ApplicationSetApplicationStatus{ 3137 { 3138 Application: "app1", 3139 Message: "testing SetApplicationSetApplicationStatus to Healthy", 3140 Status: "Healthy", 3141 }, 3142 }, 3143 expectedAppStatuses: []v1alpha1.ApplicationSetApplicationStatus{ 3144 { 3145 Application: "app1", 3146 Message: "testing SetApplicationSetApplicationStatus to Healthy", 3147 Status: "Healthy", 3148 }, 3149 }, 3150 }, 3151 { 3152 name: "removes an appstatus", 3153 appSet: v1alpha1.ApplicationSet{ 3154 ObjectMeta: metav1.ObjectMeta{ 3155 Name: "name", 3156 Namespace: "argocd", 3157 }, 3158 Spec: v1alpha1.ApplicationSetSpec{ 3159 Generators: []v1alpha1.ApplicationSetGenerator{ 3160 {List: &v1alpha1.ListGenerator{ 3161 Elements: []apiextensionsv1.JSON{{ 3162 Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`), 3163 }}, 3164 }}, 3165 }, 3166 Template: v1alpha1.ApplicationSetTemplate{}, 3167 }, 3168 Status: v1alpha1.ApplicationSetStatus{ 3169 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 3170 { 3171 Application: "app1", 3172 Message: "testing SetApplicationSetApplicationStatus to Healthy", 3173 Status: "Healthy", 3174 }, 3175 }, 3176 }, 3177 }, 3178 appStatuses: []v1alpha1.ApplicationSetApplicationStatus{}, 3179 expectedAppStatuses: nil, 3180 }, 3181 } { 3182 3183 t.Run(cc.name, func(t *testing.T) { 3184 3185 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build() 3186 3187 r := ApplicationSetReconciler{ 3188 Client: client, 3189 Scheme: scheme, 3190 Renderer: &utils.Render{}, 3191 Recorder: record.NewFakeRecorder(1), 3192 Cache: &fakeCache{}, 3193 Generators: map[string]generators.Generator{ 3194 "List": generators.NewListGenerator(), 3195 }, 3196 ArgoDB: &argoDBMock, 3197 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 3198 KubeClientset: kubeclientset, 3199 } 3200 3201 err = r.setAppSetApplicationStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appStatuses) 3202 assert.Nil(t, err) 3203 3204 assert.Equal(t, cc.expectedAppStatuses, cc.appSet.Status.ApplicationStatus) 3205 }) 3206 } 3207 } 3208 3209 func TestBuildAppDependencyList(t *testing.T) { 3210 3211 scheme := runtime.NewScheme() 3212 err := v1alpha1.AddToScheme(scheme) 3213 assert.Nil(t, err) 3214 3215 err = v1alpha1.AddToScheme(scheme) 3216 assert.Nil(t, err) 3217 3218 client := fake.NewClientBuilder().WithScheme(scheme).Build() 3219 3220 for _, cc := range []struct { 3221 name string 3222 appSet v1alpha1.ApplicationSet 3223 apps []v1alpha1.Application 3224 expectedList [][]string 3225 expectedStepMap map[string]int 3226 }{ 3227 { 3228 name: "handles an empty set of applications and no strategy", 3229 appSet: v1alpha1.ApplicationSet{ 3230 ObjectMeta: metav1.ObjectMeta{ 3231 Name: "name", 3232 Namespace: "argocd", 3233 }, 3234 Spec: v1alpha1.ApplicationSetSpec{}, 3235 }, 3236 apps: []v1alpha1.Application{}, 3237 expectedList: [][]string{}, 3238 expectedStepMap: map[string]int{}, 3239 }, 3240 { 3241 name: "handles an empty set of applications and ignores AllAtOnce strategy", 3242 appSet: v1alpha1.ApplicationSet{ 3243 ObjectMeta: metav1.ObjectMeta{ 3244 Name: "name", 3245 Namespace: "argocd", 3246 }, 3247 Spec: v1alpha1.ApplicationSetSpec{ 3248 Strategy: &v1alpha1.ApplicationSetStrategy{ 3249 Type: "AllAtOnce", 3250 }, 3251 }, 3252 }, 3253 apps: []v1alpha1.Application{}, 3254 expectedList: [][]string{}, 3255 expectedStepMap: map[string]int{}, 3256 }, 3257 { 3258 name: "handles an empty set of applications with good 'In' selectors", 3259 appSet: v1alpha1.ApplicationSet{ 3260 ObjectMeta: metav1.ObjectMeta{ 3261 Name: "name", 3262 Namespace: "argocd", 3263 }, 3264 Spec: v1alpha1.ApplicationSetSpec{ 3265 Strategy: &v1alpha1.ApplicationSetStrategy{ 3266 Type: "RollingSync", 3267 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3268 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3269 { 3270 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3271 { 3272 Key: "env", 3273 Operator: "In", 3274 Values: []string{ 3275 "dev", 3276 }, 3277 }, 3278 }, 3279 }, 3280 }, 3281 }, 3282 }, 3283 }, 3284 }, 3285 apps: []v1alpha1.Application{}, 3286 expectedList: [][]string{ 3287 {}, 3288 }, 3289 expectedStepMap: map[string]int{}, 3290 }, 3291 { 3292 name: "handles selecting 1 application with 1 'In' selector", 3293 appSet: v1alpha1.ApplicationSet{ 3294 ObjectMeta: metav1.ObjectMeta{ 3295 Name: "name", 3296 Namespace: "argocd", 3297 }, 3298 Spec: v1alpha1.ApplicationSetSpec{ 3299 Strategy: &v1alpha1.ApplicationSetStrategy{ 3300 Type: "RollingSync", 3301 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3302 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3303 { 3304 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3305 { 3306 Key: "env", 3307 Operator: "In", 3308 Values: []string{ 3309 "dev", 3310 }, 3311 }, 3312 }, 3313 }, 3314 }, 3315 }, 3316 }, 3317 }, 3318 }, 3319 apps: []v1alpha1.Application{ 3320 { 3321 ObjectMeta: metav1.ObjectMeta{ 3322 Name: "app-dev", 3323 Labels: map[string]string{ 3324 "env": "dev", 3325 }, 3326 }, 3327 }, 3328 }, 3329 expectedList: [][]string{ 3330 {"app-dev"}, 3331 }, 3332 expectedStepMap: map[string]int{ 3333 "app-dev": 0, 3334 }, 3335 }, 3336 { 3337 name: "handles 'In' selectors that select no applications", 3338 appSet: v1alpha1.ApplicationSet{ 3339 ObjectMeta: metav1.ObjectMeta{ 3340 Name: "name", 3341 Namespace: "argocd", 3342 }, 3343 Spec: v1alpha1.ApplicationSetSpec{ 3344 Strategy: &v1alpha1.ApplicationSetStrategy{ 3345 Type: "RollingSync", 3346 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3347 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3348 { 3349 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3350 { 3351 Key: "env", 3352 Operator: "In", 3353 Values: []string{ 3354 "dev", 3355 }, 3356 }, 3357 }, 3358 }, 3359 { 3360 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3361 { 3362 Key: "env", 3363 Operator: "In", 3364 Values: []string{ 3365 "qa", 3366 }, 3367 }, 3368 }, 3369 }, 3370 { 3371 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3372 { 3373 Key: "env", 3374 Operator: "In", 3375 Values: []string{ 3376 "prod", 3377 }, 3378 }, 3379 }, 3380 }, 3381 }, 3382 }, 3383 }, 3384 }, 3385 }, 3386 apps: []v1alpha1.Application{ 3387 { 3388 ObjectMeta: metav1.ObjectMeta{ 3389 Name: "app-qa", 3390 Labels: map[string]string{ 3391 "env": "qa", 3392 }, 3393 }, 3394 }, 3395 { 3396 ObjectMeta: metav1.ObjectMeta{ 3397 Name: "app-prod", 3398 Labels: map[string]string{ 3399 "env": "prod", 3400 }, 3401 }, 3402 }, 3403 }, 3404 expectedList: [][]string{ 3405 {}, 3406 {"app-qa"}, 3407 {"app-prod"}, 3408 }, 3409 expectedStepMap: map[string]int{ 3410 "app-qa": 1, 3411 "app-prod": 2, 3412 }, 3413 }, 3414 { 3415 name: "multiple 'In' selectors in the same matchExpression only select Applications that match all selectors", 3416 appSet: v1alpha1.ApplicationSet{ 3417 ObjectMeta: metav1.ObjectMeta{ 3418 Name: "name", 3419 Namespace: "argocd", 3420 }, 3421 Spec: v1alpha1.ApplicationSetSpec{ 3422 Strategy: &v1alpha1.ApplicationSetStrategy{ 3423 Type: "RollingSync", 3424 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3425 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3426 { 3427 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3428 { 3429 Key: "region", 3430 Operator: "In", 3431 Values: []string{ 3432 "us-east-2", 3433 }, 3434 }, 3435 { 3436 Key: "env", 3437 Operator: "In", 3438 Values: []string{ 3439 "qa", 3440 }, 3441 }, 3442 }, 3443 }, 3444 }, 3445 }, 3446 }, 3447 }, 3448 }, 3449 apps: []v1alpha1.Application{ 3450 { 3451 ObjectMeta: metav1.ObjectMeta{ 3452 Name: "app-qa1", 3453 Labels: map[string]string{ 3454 "env": "qa", 3455 }, 3456 }, 3457 }, 3458 { 3459 ObjectMeta: metav1.ObjectMeta{ 3460 Name: "app-qa2", 3461 Labels: map[string]string{ 3462 "env": "qa", 3463 "region": "us-east-2", 3464 }, 3465 }, 3466 }, 3467 }, 3468 expectedList: [][]string{ 3469 {"app-qa2"}, 3470 }, 3471 expectedStepMap: map[string]int{ 3472 "app-qa2": 0, 3473 }, 3474 }, 3475 { 3476 name: "multiple values in the same 'In' matchExpression can match on any value", 3477 appSet: v1alpha1.ApplicationSet{ 3478 ObjectMeta: metav1.ObjectMeta{ 3479 Name: "name", 3480 Namespace: "argocd", 3481 }, 3482 Spec: v1alpha1.ApplicationSetSpec{ 3483 Strategy: &v1alpha1.ApplicationSetStrategy{ 3484 Type: "RollingSync", 3485 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3486 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3487 { 3488 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3489 { 3490 Key: "env", 3491 Operator: "In", 3492 Values: []string{ 3493 "qa", 3494 "prod", 3495 }, 3496 }, 3497 }, 3498 }, 3499 }, 3500 }, 3501 }, 3502 }, 3503 }, 3504 apps: []v1alpha1.Application{ 3505 { 3506 ObjectMeta: metav1.ObjectMeta{ 3507 Name: "app-dev", 3508 Labels: map[string]string{ 3509 "env": "dev", 3510 }, 3511 }, 3512 }, 3513 { 3514 ObjectMeta: metav1.ObjectMeta{ 3515 Name: "app-qa", 3516 Labels: map[string]string{ 3517 "env": "qa", 3518 }, 3519 }, 3520 }, 3521 { 3522 ObjectMeta: metav1.ObjectMeta{ 3523 Name: "app-prod", 3524 Labels: map[string]string{ 3525 "env": "prod", 3526 "region": "us-east-2", 3527 }, 3528 }, 3529 }, 3530 }, 3531 expectedList: [][]string{ 3532 {"app-qa", "app-prod"}, 3533 }, 3534 expectedStepMap: map[string]int{ 3535 "app-qa": 0, 3536 "app-prod": 0, 3537 }, 3538 }, 3539 { 3540 name: "handles an empty set of applications with good 'NotIn' selectors", 3541 appSet: v1alpha1.ApplicationSet{ 3542 ObjectMeta: metav1.ObjectMeta{ 3543 Name: "name", 3544 Namespace: "argocd", 3545 }, 3546 Spec: v1alpha1.ApplicationSetSpec{ 3547 Strategy: &v1alpha1.ApplicationSetStrategy{ 3548 Type: "RollingSync", 3549 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3550 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3551 { 3552 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3553 { 3554 Key: "env", 3555 Operator: "In", 3556 Values: []string{ 3557 "dev", 3558 }, 3559 }, 3560 }, 3561 }, 3562 }, 3563 }, 3564 }, 3565 }, 3566 }, 3567 apps: []v1alpha1.Application{}, 3568 expectedList: [][]string{ 3569 {}, 3570 }, 3571 expectedStepMap: map[string]int{}, 3572 }, 3573 { 3574 name: "selects 1 application with 1 'NotIn' selector", 3575 appSet: v1alpha1.ApplicationSet{ 3576 ObjectMeta: metav1.ObjectMeta{ 3577 Name: "name", 3578 Namespace: "argocd", 3579 }, 3580 Spec: v1alpha1.ApplicationSetSpec{ 3581 Strategy: &v1alpha1.ApplicationSetStrategy{ 3582 Type: "RollingSync", 3583 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3584 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3585 { 3586 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3587 { 3588 Key: "env", 3589 Operator: "NotIn", 3590 Values: []string{ 3591 "qa", 3592 }, 3593 }, 3594 }, 3595 }, 3596 }, 3597 }, 3598 }, 3599 }, 3600 }, 3601 apps: []v1alpha1.Application{ 3602 { 3603 ObjectMeta: metav1.ObjectMeta{ 3604 Name: "app-dev", 3605 Labels: map[string]string{ 3606 "env": "dev", 3607 }, 3608 }, 3609 }, 3610 }, 3611 expectedList: [][]string{ 3612 {"app-dev"}, 3613 }, 3614 expectedStepMap: map[string]int{ 3615 "app-dev": 0, 3616 }, 3617 }, 3618 { 3619 name: "'NotIn' selectors that select no applications", 3620 appSet: v1alpha1.ApplicationSet{ 3621 ObjectMeta: metav1.ObjectMeta{ 3622 Name: "name", 3623 Namespace: "argocd", 3624 }, 3625 Spec: v1alpha1.ApplicationSetSpec{ 3626 Strategy: &v1alpha1.ApplicationSetStrategy{ 3627 Type: "RollingSync", 3628 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3629 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3630 { 3631 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3632 { 3633 Key: "env", 3634 Operator: "NotIn", 3635 Values: []string{ 3636 "dev", 3637 }, 3638 }, 3639 }, 3640 }, 3641 }, 3642 }, 3643 }, 3644 }, 3645 }, 3646 apps: []v1alpha1.Application{ 3647 { 3648 ObjectMeta: metav1.ObjectMeta{ 3649 Name: "app-qa", 3650 Labels: map[string]string{ 3651 "env": "qa", 3652 }, 3653 }, 3654 }, 3655 { 3656 ObjectMeta: metav1.ObjectMeta{ 3657 Name: "app-prod", 3658 Labels: map[string]string{ 3659 "env": "prod", 3660 }, 3661 }, 3662 }, 3663 }, 3664 expectedList: [][]string{ 3665 {"app-qa", "app-prod"}, 3666 }, 3667 expectedStepMap: map[string]int{ 3668 "app-qa": 0, 3669 "app-prod": 0, 3670 }, 3671 }, 3672 { 3673 name: "multiple 'NotIn' selectors remove Applications with mising labels on any match", 3674 appSet: v1alpha1.ApplicationSet{ 3675 ObjectMeta: metav1.ObjectMeta{ 3676 Name: "name", 3677 Namespace: "argocd", 3678 }, 3679 Spec: v1alpha1.ApplicationSetSpec{ 3680 Strategy: &v1alpha1.ApplicationSetStrategy{ 3681 Type: "RollingSync", 3682 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3683 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3684 { 3685 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3686 { 3687 Key: "region", 3688 Operator: "NotIn", 3689 Values: []string{ 3690 "us-east-2", 3691 }, 3692 }, 3693 { 3694 Key: "env", 3695 Operator: "NotIn", 3696 Values: []string{ 3697 "qa", 3698 }, 3699 }, 3700 }, 3701 }, 3702 }, 3703 }, 3704 }, 3705 }, 3706 }, 3707 apps: []v1alpha1.Application{ 3708 { 3709 ObjectMeta: metav1.ObjectMeta{ 3710 Name: "app-qa1", 3711 Labels: map[string]string{ 3712 "env": "qa", 3713 }, 3714 }, 3715 }, 3716 { 3717 ObjectMeta: metav1.ObjectMeta{ 3718 Name: "app-qa2", 3719 Labels: map[string]string{ 3720 "env": "qa", 3721 "region": "us-east-2", 3722 }, 3723 }, 3724 }, 3725 }, 3726 expectedList: [][]string{ 3727 {}, 3728 }, 3729 expectedStepMap: map[string]int{}, 3730 }, 3731 { 3732 name: "multiple 'NotIn' selectors filter all matching Applications", 3733 appSet: v1alpha1.ApplicationSet{ 3734 ObjectMeta: metav1.ObjectMeta{ 3735 Name: "name", 3736 Namespace: "argocd", 3737 }, 3738 Spec: v1alpha1.ApplicationSetSpec{ 3739 Strategy: &v1alpha1.ApplicationSetStrategy{ 3740 Type: "RollingSync", 3741 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3742 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3743 { 3744 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3745 { 3746 Key: "region", 3747 Operator: "NotIn", 3748 Values: []string{ 3749 "us-east-2", 3750 }, 3751 }, 3752 { 3753 Key: "env", 3754 Operator: "NotIn", 3755 Values: []string{ 3756 "qa", 3757 }, 3758 }, 3759 }, 3760 }, 3761 }, 3762 }, 3763 }, 3764 }, 3765 }, 3766 apps: []v1alpha1.Application{ 3767 { 3768 ObjectMeta: metav1.ObjectMeta{ 3769 Name: "app-qa1", 3770 Labels: map[string]string{ 3771 "env": "qa", 3772 "region": "us-east-1", 3773 }, 3774 }, 3775 }, 3776 { 3777 ObjectMeta: metav1.ObjectMeta{ 3778 Name: "app-qa2", 3779 Labels: map[string]string{ 3780 "env": "qa", 3781 "region": "us-east-2", 3782 }, 3783 }, 3784 }, 3785 { 3786 ObjectMeta: metav1.ObjectMeta{ 3787 Name: "app-prod1", 3788 Labels: map[string]string{ 3789 "env": "prod", 3790 "region": "us-east-1", 3791 }, 3792 }, 3793 }, 3794 { 3795 ObjectMeta: metav1.ObjectMeta{ 3796 Name: "app-prod2", 3797 Labels: map[string]string{ 3798 "env": "prod", 3799 "region": "us-east-2", 3800 }, 3801 }, 3802 }, 3803 }, 3804 expectedList: [][]string{ 3805 {"app-prod1"}, 3806 }, 3807 expectedStepMap: map[string]int{ 3808 "app-prod1": 0, 3809 }, 3810 }, 3811 { 3812 name: "multiple values in the same 'NotIn' matchExpression exclude a match from any value", 3813 appSet: v1alpha1.ApplicationSet{ 3814 ObjectMeta: metav1.ObjectMeta{ 3815 Name: "name", 3816 Namespace: "argocd", 3817 }, 3818 Spec: v1alpha1.ApplicationSetSpec{ 3819 Strategy: &v1alpha1.ApplicationSetStrategy{ 3820 Type: "RollingSync", 3821 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3822 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3823 { 3824 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3825 { 3826 Key: "env", 3827 Operator: "NotIn", 3828 Values: []string{ 3829 "qa", 3830 "prod", 3831 }, 3832 }, 3833 }, 3834 }, 3835 }, 3836 }, 3837 }, 3838 }, 3839 }, 3840 apps: []v1alpha1.Application{ 3841 { 3842 ObjectMeta: metav1.ObjectMeta{ 3843 Name: "app-dev", 3844 Labels: map[string]string{ 3845 "env": "dev", 3846 }, 3847 }, 3848 }, 3849 { 3850 ObjectMeta: metav1.ObjectMeta{ 3851 Name: "app-qa", 3852 Labels: map[string]string{ 3853 "env": "qa", 3854 }, 3855 }, 3856 }, 3857 { 3858 ObjectMeta: metav1.ObjectMeta{ 3859 Name: "app-prod", 3860 Labels: map[string]string{ 3861 "env": "prod", 3862 "region": "us-east-2", 3863 }, 3864 }, 3865 }, 3866 }, 3867 expectedList: [][]string{ 3868 {"app-dev"}, 3869 }, 3870 expectedStepMap: map[string]int{ 3871 "app-dev": 0, 3872 }, 3873 }, 3874 { 3875 name: "in a mix of 'In' and 'NotIn' selectors, 'NotIn' takes precedence", 3876 appSet: v1alpha1.ApplicationSet{ 3877 ObjectMeta: metav1.ObjectMeta{ 3878 Name: "name", 3879 Namespace: "argocd", 3880 }, 3881 Spec: v1alpha1.ApplicationSetSpec{ 3882 Strategy: &v1alpha1.ApplicationSetStrategy{ 3883 Type: "RollingSync", 3884 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 3885 Steps: []v1alpha1.ApplicationSetRolloutStep{ 3886 { 3887 MatchExpressions: []v1alpha1.ApplicationMatchExpression{ 3888 { 3889 Key: "env", 3890 Operator: "In", 3891 Values: []string{ 3892 "qa", 3893 "prod", 3894 }, 3895 }, 3896 { 3897 Key: "region", 3898 Operator: "NotIn", 3899 Values: []string{ 3900 "us-west-2", 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-qa1", 3922 Labels: map[string]string{ 3923 "env": "qa", 3924 "region": "us-west-2", 3925 }, 3926 }, 3927 }, 3928 { 3929 ObjectMeta: metav1.ObjectMeta{ 3930 Name: "app-qa2", 3931 Labels: map[string]string{ 3932 "env": "qa", 3933 "region": "us-east-2", 3934 }, 3935 }, 3936 }, 3937 }, 3938 expectedList: [][]string{ 3939 {"app-qa2"}, 3940 }, 3941 expectedStepMap: map[string]int{ 3942 "app-qa2": 0, 3943 }, 3944 }, 3945 } { 3946 3947 t.Run(cc.name, func(t *testing.T) { 3948 3949 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 3950 argoDBMock := dbmocks.ArgoDB{} 3951 argoObjs := []runtime.Object{} 3952 3953 r := ApplicationSetReconciler{ 3954 Client: client, 3955 Scheme: scheme, 3956 Recorder: record.NewFakeRecorder(1), 3957 Cache: &fakeCache{}, 3958 Generators: map[string]generators.Generator{}, 3959 ArgoDB: &argoDBMock, 3960 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 3961 KubeClientset: kubeclientset, 3962 } 3963 3964 appDependencyList, appStepMap, err := r.buildAppDependencyList(log.NewEntry(log.StandardLogger()), cc.appSet, cc.apps) 3965 assert.Equal(t, err, nil, "expected no errors, but errors occured") 3966 assert.Equal(t, cc.expectedList, appDependencyList, "expected appDependencyList did not match actual") 3967 assert.Equal(t, cc.expectedStepMap, appStepMap, "expected appStepMap did not match actual") 3968 }) 3969 } 3970 } 3971 3972 func TestBuildAppSyncMap(t *testing.T) { 3973 3974 scheme := runtime.NewScheme() 3975 err := v1alpha1.AddToScheme(scheme) 3976 assert.Nil(t, err) 3977 3978 err = v1alpha1.AddToScheme(scheme) 3979 assert.Nil(t, err) 3980 3981 client := fake.NewClientBuilder().WithScheme(scheme).Build() 3982 3983 for _, cc := range []struct { 3984 name string 3985 appSet v1alpha1.ApplicationSet 3986 appMap map[string]v1alpha1.Application 3987 appDependencyList [][]string 3988 expectedMap map[string]bool 3989 }{ 3990 { 3991 name: "handles an empty app dependency list", 3992 appSet: v1alpha1.ApplicationSet{ 3993 ObjectMeta: metav1.ObjectMeta{ 3994 Name: "name", 3995 Namespace: "argocd", 3996 }, 3997 Spec: v1alpha1.ApplicationSetSpec{ 3998 Strategy: &v1alpha1.ApplicationSetStrategy{ 3999 Type: "RollingSync", 4000 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4001 }, 4002 }, 4003 }, 4004 appDependencyList: [][]string{}, 4005 expectedMap: map[string]bool{}, 4006 }, 4007 { 4008 name: "handles two applications with no statuses", 4009 appSet: v1alpha1.ApplicationSet{ 4010 ObjectMeta: metav1.ObjectMeta{ 4011 Name: "name", 4012 Namespace: "argocd", 4013 }, 4014 Spec: v1alpha1.ApplicationSetSpec{ 4015 Strategy: &v1alpha1.ApplicationSetStrategy{ 4016 Type: "RollingSync", 4017 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4018 }, 4019 }, 4020 }, 4021 appDependencyList: [][]string{ 4022 {"app1"}, 4023 {"app2"}, 4024 }, 4025 expectedMap: map[string]bool{ 4026 "app1": true, 4027 "app2": false, 4028 }, 4029 }, 4030 { 4031 name: "handles applications after an empty selection", 4032 appSet: v1alpha1.ApplicationSet{ 4033 ObjectMeta: metav1.ObjectMeta{ 4034 Name: "name", 4035 Namespace: "argocd", 4036 }, 4037 Spec: v1alpha1.ApplicationSetSpec{ 4038 Strategy: &v1alpha1.ApplicationSetStrategy{ 4039 Type: "RollingSync", 4040 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4041 }, 4042 }, 4043 }, 4044 appDependencyList: [][]string{ 4045 {}, 4046 {"app1", "app2"}, 4047 }, 4048 expectedMap: map[string]bool{ 4049 "app1": true, 4050 "app2": true, 4051 }, 4052 }, 4053 { 4054 name: "handles RollingSync applications that are healthy and have no changes", 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 }, 4065 }, 4066 Status: v1alpha1.ApplicationSetStatus{ 4067 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4068 { 4069 Application: "app1", 4070 Status: "Healthy", 4071 }, 4072 { 4073 Application: "app2", 4074 Status: "Healthy", 4075 }, 4076 }, 4077 }, 4078 }, 4079 appMap: map[string]v1alpha1.Application{ 4080 "app1": { 4081 ObjectMeta: metav1.ObjectMeta{ 4082 Name: "app1", 4083 }, 4084 Status: v1alpha1.ApplicationStatus{ 4085 Health: v1alpha1.HealthStatus{ 4086 Status: health.HealthStatusHealthy, 4087 }, 4088 OperationState: &v1alpha1.OperationState{ 4089 Phase: common.OperationSucceeded, 4090 }, 4091 Sync: v1alpha1.SyncStatus{ 4092 Status: v1alpha1.SyncStatusCodeSynced, 4093 }, 4094 }, 4095 }, 4096 "app2": { 4097 ObjectMeta: metav1.ObjectMeta{ 4098 Name: "app2", 4099 }, 4100 Status: v1alpha1.ApplicationStatus{ 4101 Health: v1alpha1.HealthStatus{ 4102 Status: health.HealthStatusHealthy, 4103 }, 4104 OperationState: &v1alpha1.OperationState{ 4105 Phase: common.OperationSucceeded, 4106 }, 4107 Sync: v1alpha1.SyncStatus{ 4108 Status: v1alpha1.SyncStatusCodeSynced, 4109 }, 4110 }, 4111 }, 4112 }, 4113 appDependencyList: [][]string{ 4114 {"app1"}, 4115 {"app2"}, 4116 }, 4117 expectedMap: map[string]bool{ 4118 "app1": true, 4119 "app2": true, 4120 }, 4121 }, 4122 { 4123 name: "blocks RollingSync applications that are healthy and have no changes, but are still pending", 4124 appSet: v1alpha1.ApplicationSet{ 4125 ObjectMeta: metav1.ObjectMeta{ 4126 Name: "name", 4127 Namespace: "argocd", 4128 }, 4129 Spec: v1alpha1.ApplicationSetSpec{ 4130 Strategy: &v1alpha1.ApplicationSetStrategy{ 4131 Type: "RollingSync", 4132 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4133 }, 4134 }, 4135 Status: v1alpha1.ApplicationSetStatus{ 4136 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4137 { 4138 Application: "app1", 4139 Status: "Pending", 4140 }, 4141 { 4142 Application: "app2", 4143 Status: "Healthy", 4144 }, 4145 }, 4146 }, 4147 }, 4148 appMap: map[string]v1alpha1.Application{ 4149 "app1": { 4150 ObjectMeta: metav1.ObjectMeta{ 4151 Name: "app1", 4152 }, 4153 Status: v1alpha1.ApplicationStatus{ 4154 Health: v1alpha1.HealthStatus{ 4155 Status: health.HealthStatusHealthy, 4156 }, 4157 OperationState: &v1alpha1.OperationState{ 4158 Phase: common.OperationSucceeded, 4159 }, 4160 Sync: v1alpha1.SyncStatus{ 4161 Status: v1alpha1.SyncStatusCodeSynced, 4162 }, 4163 }, 4164 }, 4165 "app2": { 4166 ObjectMeta: metav1.ObjectMeta{ 4167 Name: "app2", 4168 }, 4169 Status: v1alpha1.ApplicationStatus{ 4170 Health: v1alpha1.HealthStatus{ 4171 Status: health.HealthStatusHealthy, 4172 }, 4173 OperationState: &v1alpha1.OperationState{ 4174 Phase: common.OperationSucceeded, 4175 }, 4176 Sync: v1alpha1.SyncStatus{ 4177 Status: v1alpha1.SyncStatusCodeSynced, 4178 }, 4179 }, 4180 }, 4181 }, 4182 appDependencyList: [][]string{ 4183 {"app1"}, 4184 {"app2"}, 4185 }, 4186 expectedMap: map[string]bool{ 4187 "app1": true, 4188 "app2": false, 4189 }, 4190 }, 4191 { 4192 name: "handles RollingSync applications that are up to date and healthy, but still syncing", 4193 appSet: v1alpha1.ApplicationSet{ 4194 ObjectMeta: metav1.ObjectMeta{ 4195 Name: "name", 4196 Namespace: "argocd", 4197 }, 4198 Spec: v1alpha1.ApplicationSetSpec{ 4199 Strategy: &v1alpha1.ApplicationSetStrategy{ 4200 Type: "RollingSync", 4201 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4202 }, 4203 }, 4204 Status: v1alpha1.ApplicationSetStatus{ 4205 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4206 { 4207 Application: "app1", 4208 Status: "Progressing", 4209 }, 4210 { 4211 Application: "app2", 4212 Status: "Progressing", 4213 }, 4214 }, 4215 }, 4216 }, 4217 appMap: map[string]v1alpha1.Application{ 4218 "app1": { 4219 ObjectMeta: metav1.ObjectMeta{ 4220 Name: "app1", 4221 }, 4222 Status: v1alpha1.ApplicationStatus{ 4223 Health: v1alpha1.HealthStatus{ 4224 Status: health.HealthStatusHealthy, 4225 }, 4226 OperationState: &v1alpha1.OperationState{ 4227 Phase: common.OperationRunning, 4228 }, 4229 Sync: v1alpha1.SyncStatus{ 4230 Status: v1alpha1.SyncStatusCodeSynced, 4231 }, 4232 }, 4233 }, 4234 "app2": { 4235 ObjectMeta: metav1.ObjectMeta{ 4236 Name: "app2", 4237 }, 4238 Status: v1alpha1.ApplicationStatus{ 4239 Health: v1alpha1.HealthStatus{ 4240 Status: health.HealthStatusHealthy, 4241 }, 4242 OperationState: &v1alpha1.OperationState{ 4243 Phase: common.OperationRunning, 4244 }, 4245 Sync: v1alpha1.SyncStatus{ 4246 Status: v1alpha1.SyncStatusCodeSynced, 4247 }, 4248 }, 4249 }, 4250 }, 4251 appDependencyList: [][]string{ 4252 {"app1"}, 4253 {"app2"}, 4254 }, 4255 expectedMap: map[string]bool{ 4256 "app1": true, 4257 "app2": false, 4258 }, 4259 }, 4260 { 4261 name: "handles RollingSync applications that are up to date and synced, but degraded", 4262 appSet: v1alpha1.ApplicationSet{ 4263 ObjectMeta: metav1.ObjectMeta{ 4264 Name: "name", 4265 Namespace: "argocd", 4266 }, 4267 Spec: v1alpha1.ApplicationSetSpec{ 4268 Strategy: &v1alpha1.ApplicationSetStrategy{ 4269 Type: "RollingSync", 4270 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4271 }, 4272 }, 4273 Status: v1alpha1.ApplicationSetStatus{ 4274 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4275 { 4276 Application: "app1", 4277 Status: "Progressing", 4278 }, 4279 { 4280 Application: "app2", 4281 Status: "Progressing", 4282 }, 4283 }, 4284 }, 4285 }, 4286 appMap: map[string]v1alpha1.Application{ 4287 "app1": { 4288 ObjectMeta: metav1.ObjectMeta{ 4289 Name: "app1", 4290 }, 4291 Status: v1alpha1.ApplicationStatus{ 4292 Health: v1alpha1.HealthStatus{ 4293 Status: health.HealthStatusDegraded, 4294 }, 4295 OperationState: &v1alpha1.OperationState{ 4296 Phase: common.OperationRunning, 4297 }, 4298 Sync: v1alpha1.SyncStatus{ 4299 Status: v1alpha1.SyncStatusCodeSynced, 4300 }, 4301 }, 4302 }, 4303 "app2": { 4304 ObjectMeta: metav1.ObjectMeta{ 4305 Name: "app2", 4306 }, 4307 Status: v1alpha1.ApplicationStatus{ 4308 Health: v1alpha1.HealthStatus{ 4309 Status: health.HealthStatusDegraded, 4310 }, 4311 OperationState: &v1alpha1.OperationState{ 4312 Phase: common.OperationRunning, 4313 }, 4314 Sync: v1alpha1.SyncStatus{ 4315 Status: v1alpha1.SyncStatusCodeSynced, 4316 }, 4317 }, 4318 }, 4319 }, 4320 appDependencyList: [][]string{ 4321 {"app1"}, 4322 {"app2"}, 4323 }, 4324 expectedMap: map[string]bool{ 4325 "app1": true, 4326 "app2": false, 4327 }, 4328 }, 4329 { 4330 name: "handles RollingSync applications that are OutOfSync and healthy", 4331 appSet: v1alpha1.ApplicationSet{ 4332 ObjectMeta: metav1.ObjectMeta{ 4333 Name: "name", 4334 Namespace: "argocd", 4335 }, 4336 Spec: v1alpha1.ApplicationSetSpec{ 4337 Strategy: &v1alpha1.ApplicationSetStrategy{ 4338 Type: "RollingSync", 4339 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4340 }, 4341 }, 4342 Status: v1alpha1.ApplicationSetStatus{ 4343 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4344 { 4345 Application: "app1", 4346 Status: "Healthy", 4347 }, 4348 { 4349 Application: "app2", 4350 Status: "Healthy", 4351 }, 4352 }, 4353 }, 4354 }, 4355 appDependencyList: [][]string{ 4356 {"app1"}, 4357 {"app2"}, 4358 }, 4359 appMap: map[string]v1alpha1.Application{ 4360 "app1": { 4361 ObjectMeta: metav1.ObjectMeta{ 4362 Name: "app1", 4363 }, 4364 Status: v1alpha1.ApplicationStatus{ 4365 Health: v1alpha1.HealthStatus{ 4366 Status: health.HealthStatusHealthy, 4367 }, 4368 OperationState: &v1alpha1.OperationState{ 4369 Phase: common.OperationSucceeded, 4370 }, 4371 Sync: v1alpha1.SyncStatus{ 4372 Status: v1alpha1.SyncStatusCodeOutOfSync, 4373 }, 4374 }, 4375 }, 4376 "app2": { 4377 ObjectMeta: metav1.ObjectMeta{ 4378 Name: "app2", 4379 }, 4380 Status: v1alpha1.ApplicationStatus{ 4381 Health: v1alpha1.HealthStatus{ 4382 Status: health.HealthStatusHealthy, 4383 }, 4384 OperationState: &v1alpha1.OperationState{ 4385 Phase: common.OperationSucceeded, 4386 }, 4387 Sync: v1alpha1.SyncStatus{ 4388 Status: v1alpha1.SyncStatusCodeOutOfSync, 4389 }, 4390 }, 4391 }, 4392 }, 4393 expectedMap: map[string]bool{ 4394 "app1": true, 4395 "app2": false, 4396 }, 4397 }, 4398 { 4399 name: "handles a lot of applications", 4400 appSet: v1alpha1.ApplicationSet{ 4401 ObjectMeta: metav1.ObjectMeta{ 4402 Name: "name", 4403 Namespace: "argocd", 4404 }, 4405 Spec: v1alpha1.ApplicationSetSpec{ 4406 Strategy: &v1alpha1.ApplicationSetStrategy{ 4407 Type: "RollingSync", 4408 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4409 }, 4410 }, 4411 Status: v1alpha1.ApplicationSetStatus{ 4412 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4413 { 4414 Application: "app1", 4415 Status: "Healthy", 4416 }, 4417 { 4418 Application: "app2", 4419 Status: "Healthy", 4420 }, 4421 { 4422 Application: "app3", 4423 Status: "Healthy", 4424 }, 4425 { 4426 Application: "app4", 4427 Status: "Healthy", 4428 }, 4429 { 4430 Application: "app5", 4431 Status: "Healthy", 4432 }, 4433 { 4434 Application: "app7", 4435 Status: "Healthy", 4436 }, 4437 }, 4438 }, 4439 }, 4440 appMap: map[string]v1alpha1.Application{ 4441 "app1": { 4442 ObjectMeta: metav1.ObjectMeta{ 4443 Name: "app1", 4444 }, 4445 Status: v1alpha1.ApplicationStatus{ 4446 Health: v1alpha1.HealthStatus{ 4447 Status: health.HealthStatusHealthy, 4448 }, 4449 OperationState: &v1alpha1.OperationState{ 4450 Phase: common.OperationSucceeded, 4451 }, 4452 Sync: v1alpha1.SyncStatus{ 4453 Status: v1alpha1.SyncStatusCodeSynced, 4454 }, 4455 }, 4456 }, 4457 "app2": { 4458 ObjectMeta: metav1.ObjectMeta{ 4459 Name: "app2", 4460 }, 4461 Status: v1alpha1.ApplicationStatus{ 4462 Health: v1alpha1.HealthStatus{ 4463 Status: health.HealthStatusHealthy, 4464 }, 4465 OperationState: &v1alpha1.OperationState{ 4466 Phase: common.OperationSucceeded, 4467 }, 4468 Sync: v1alpha1.SyncStatus{ 4469 Status: v1alpha1.SyncStatusCodeSynced, 4470 }, 4471 }, 4472 }, 4473 "app3": { 4474 ObjectMeta: metav1.ObjectMeta{ 4475 Name: "app3", 4476 }, 4477 Status: v1alpha1.ApplicationStatus{ 4478 Health: v1alpha1.HealthStatus{ 4479 Status: health.HealthStatusHealthy, 4480 }, 4481 OperationState: &v1alpha1.OperationState{ 4482 Phase: common.OperationSucceeded, 4483 }, 4484 Sync: v1alpha1.SyncStatus{ 4485 Status: v1alpha1.SyncStatusCodeSynced, 4486 }, 4487 }, 4488 }, 4489 "app5": { 4490 ObjectMeta: metav1.ObjectMeta{ 4491 Name: "app5", 4492 }, 4493 Status: v1alpha1.ApplicationStatus{ 4494 Health: v1alpha1.HealthStatus{ 4495 Status: health.HealthStatusHealthy, 4496 }, 4497 OperationState: &v1alpha1.OperationState{ 4498 Phase: common.OperationSucceeded, 4499 }, 4500 Sync: v1alpha1.SyncStatus{ 4501 Status: v1alpha1.SyncStatusCodeSynced, 4502 }, 4503 }, 4504 }, 4505 "app6": { 4506 ObjectMeta: metav1.ObjectMeta{ 4507 Name: "app6", 4508 }, 4509 Status: v1alpha1.ApplicationStatus{ 4510 Health: v1alpha1.HealthStatus{ 4511 Status: health.HealthStatusDegraded, 4512 }, 4513 OperationState: &v1alpha1.OperationState{ 4514 Phase: common.OperationSucceeded, 4515 }, 4516 Sync: v1alpha1.SyncStatus{ 4517 Status: v1alpha1.SyncStatusCodeSynced, 4518 }, 4519 }, 4520 }, 4521 }, 4522 appDependencyList: [][]string{ 4523 {"app1", "app2", "app3"}, 4524 {"app4", "app5", "app6"}, 4525 {"app7", "app8", "app9"}, 4526 }, 4527 expectedMap: map[string]bool{ 4528 "app1": true, 4529 "app2": true, 4530 "app3": true, 4531 "app4": true, 4532 "app5": true, 4533 "app6": true, 4534 "app7": false, 4535 "app8": false, 4536 "app9": false, 4537 }, 4538 }, 4539 } { 4540 4541 t.Run(cc.name, func(t *testing.T) { 4542 4543 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 4544 argoDBMock := dbmocks.ArgoDB{} 4545 argoObjs := []runtime.Object{} 4546 4547 r := ApplicationSetReconciler{ 4548 Client: client, 4549 Scheme: scheme, 4550 Recorder: record.NewFakeRecorder(1), 4551 Cache: &fakeCache{}, 4552 Generators: map[string]generators.Generator{}, 4553 ArgoDB: &argoDBMock, 4554 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 4555 KubeClientset: kubeclientset, 4556 } 4557 4558 appSyncMap, err := r.buildAppSyncMap(context.TODO(), cc.appSet, cc.appDependencyList, cc.appMap) 4559 assert.Equal(t, err, nil, "expected no errors, but errors occured") 4560 assert.Equal(t, cc.expectedMap, appSyncMap, "expected appSyncMap did not match actual") 4561 }) 4562 } 4563 } 4564 4565 func TestUpdateApplicationSetApplicationStatus(t *testing.T) { 4566 4567 scheme := runtime.NewScheme() 4568 err := v1alpha1.AddToScheme(scheme) 4569 assert.Nil(t, err) 4570 4571 err = v1alpha1.AddToScheme(scheme) 4572 assert.Nil(t, err) 4573 4574 for _, cc := range []struct { 4575 name string 4576 appSet v1alpha1.ApplicationSet 4577 apps []v1alpha1.Application 4578 appStepMap map[string]int 4579 expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus 4580 }{ 4581 { 4582 name: "handles a nil list of statuses and no applications", 4583 appSet: v1alpha1.ApplicationSet{ 4584 ObjectMeta: metav1.ObjectMeta{ 4585 Name: "name", 4586 Namespace: "argocd", 4587 }, 4588 Spec: v1alpha1.ApplicationSetSpec{ 4589 Strategy: &v1alpha1.ApplicationSetStrategy{ 4590 Type: "RollingSync", 4591 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4592 }, 4593 }, 4594 }, 4595 apps: []v1alpha1.Application{}, 4596 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 4597 }, 4598 { 4599 name: "handles a nil list of statuses with a healthy application", 4600 appSet: v1alpha1.ApplicationSet{ 4601 ObjectMeta: metav1.ObjectMeta{ 4602 Name: "name", 4603 Namespace: "argocd", 4604 }, 4605 Spec: v1alpha1.ApplicationSetSpec{ 4606 Strategy: &v1alpha1.ApplicationSetStrategy{ 4607 Type: "RollingSync", 4608 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4609 }, 4610 }, 4611 }, 4612 apps: []v1alpha1.Application{ 4613 { 4614 ObjectMeta: metav1.ObjectMeta{ 4615 Name: "app1", 4616 }, 4617 Status: v1alpha1.ApplicationStatus{ 4618 Health: v1alpha1.HealthStatus{ 4619 Status: health.HealthStatusHealthy, 4620 }, 4621 OperationState: &v1alpha1.OperationState{ 4622 Phase: common.OperationSucceeded, 4623 }, 4624 Sync: v1alpha1.SyncStatus{ 4625 Status: v1alpha1.SyncStatusCodeSynced, 4626 }, 4627 }, 4628 }, 4629 }, 4630 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4631 { 4632 Application: "app1", 4633 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4634 Status: "Healthy", 4635 Step: "1", 4636 }, 4637 }, 4638 }, 4639 { 4640 name: "handles an empty list of statuses with a healthy application", 4641 appSet: v1alpha1.ApplicationSet{ 4642 ObjectMeta: metav1.ObjectMeta{ 4643 Name: "name", 4644 Namespace: "argocd", 4645 }, 4646 Spec: v1alpha1.ApplicationSetSpec{ 4647 Strategy: &v1alpha1.ApplicationSetStrategy{ 4648 Type: "RollingSync", 4649 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4650 }, 4651 }, 4652 Status: v1alpha1.ApplicationSetStatus{}, 4653 }, 4654 apps: []v1alpha1.Application{ 4655 { 4656 ObjectMeta: metav1.ObjectMeta{ 4657 Name: "app1", 4658 }, 4659 Status: v1alpha1.ApplicationStatus{ 4660 Health: v1alpha1.HealthStatus{ 4661 Status: health.HealthStatusHealthy, 4662 }, 4663 OperationState: &v1alpha1.OperationState{ 4664 Phase: common.OperationSucceeded, 4665 }, 4666 Sync: v1alpha1.SyncStatus{ 4667 Status: v1alpha1.SyncStatusCodeSynced, 4668 }, 4669 }, 4670 }, 4671 }, 4672 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4673 { 4674 Application: "app1", 4675 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4676 Status: "Healthy", 4677 Step: "1", 4678 }, 4679 }, 4680 }, 4681 { 4682 name: "progresses an OutOfSync RollingSync application to waiting", 4683 appSet: v1alpha1.ApplicationSet{ 4684 ObjectMeta: metav1.ObjectMeta{ 4685 Name: "name", 4686 Namespace: "argocd", 4687 }, 4688 Spec: v1alpha1.ApplicationSetSpec{ 4689 Strategy: &v1alpha1.ApplicationSetStrategy{ 4690 Type: "RollingSync", 4691 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4692 }, 4693 }, 4694 Status: v1alpha1.ApplicationSetStatus{ 4695 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4696 { 4697 Application: "app1", 4698 Message: "", 4699 Status: "Healthy", 4700 Step: "1", 4701 }, 4702 }, 4703 }, 4704 }, 4705 apps: []v1alpha1.Application{ 4706 { 4707 ObjectMeta: metav1.ObjectMeta{ 4708 Name: "app1", 4709 }, 4710 Status: v1alpha1.ApplicationStatus{ 4711 Sync: v1alpha1.SyncStatus{ 4712 Status: v1alpha1.SyncStatusCodeOutOfSync, 4713 }, 4714 }, 4715 }, 4716 }, 4717 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4718 { 4719 Application: "app1", 4720 Message: "Application has pending changes, setting status to Waiting.", 4721 Status: "Waiting", 4722 Step: "1", 4723 }, 4724 }, 4725 }, 4726 { 4727 name: "progresses a pending progressing application to progressing", 4728 appSet: v1alpha1.ApplicationSet{ 4729 ObjectMeta: metav1.ObjectMeta{ 4730 Name: "name", 4731 Namespace: "argocd", 4732 }, 4733 Spec: v1alpha1.ApplicationSetSpec{ 4734 Strategy: &v1alpha1.ApplicationSetStrategy{ 4735 Type: "RollingSync", 4736 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4737 }, 4738 }, 4739 Status: v1alpha1.ApplicationSetStatus{ 4740 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4741 { 4742 Application: "app1", 4743 Message: "", 4744 Status: "Pending", 4745 Step: "1", 4746 }, 4747 }, 4748 }, 4749 }, 4750 apps: []v1alpha1.Application{ 4751 { 4752 ObjectMeta: metav1.ObjectMeta{ 4753 Name: "app1", 4754 }, 4755 Status: v1alpha1.ApplicationStatus{ 4756 Health: v1alpha1.HealthStatus{ 4757 Status: health.HealthStatusProgressing, 4758 }, 4759 }, 4760 }, 4761 }, 4762 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4763 { 4764 Application: "app1", 4765 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 4766 Status: "Progressing", 4767 Step: "1", 4768 }, 4769 }, 4770 }, 4771 { 4772 name: "progresses a pending syncing application to progressing", 4773 appSet: v1alpha1.ApplicationSet{ 4774 ObjectMeta: metav1.ObjectMeta{ 4775 Name: "name", 4776 Namespace: "argocd", 4777 }, 4778 Spec: v1alpha1.ApplicationSetSpec{ 4779 Strategy: &v1alpha1.ApplicationSetStrategy{ 4780 Type: "RollingSync", 4781 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4782 }, 4783 }, 4784 Status: v1alpha1.ApplicationSetStatus{ 4785 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4786 { 4787 Application: "app1", 4788 Message: "", 4789 Status: "Pending", 4790 Step: "1", 4791 }, 4792 }, 4793 }, 4794 }, 4795 apps: []v1alpha1.Application{ 4796 { 4797 ObjectMeta: metav1.ObjectMeta{ 4798 Name: "app1", 4799 }, 4800 Status: v1alpha1.ApplicationStatus{ 4801 Health: v1alpha1.HealthStatus{ 4802 Status: health.HealthStatusHealthy, 4803 }, 4804 OperationState: &v1alpha1.OperationState{ 4805 Phase: common.OperationRunning, 4806 }, 4807 Sync: v1alpha1.SyncStatus{ 4808 Status: v1alpha1.SyncStatusCodeSynced, 4809 }, 4810 }, 4811 }, 4812 }, 4813 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4814 { 4815 Application: "app1", 4816 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 4817 Status: "Progressing", 4818 Step: "1", 4819 }, 4820 }, 4821 }, 4822 { 4823 name: "progresses a progressing application to healthy", 4824 appSet: v1alpha1.ApplicationSet{ 4825 ObjectMeta: metav1.ObjectMeta{ 4826 Name: "name", 4827 Namespace: "argocd", 4828 }, 4829 Spec: v1alpha1.ApplicationSetSpec{ 4830 Strategy: &v1alpha1.ApplicationSetStrategy{ 4831 Type: "RollingSync", 4832 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4833 }, 4834 }, 4835 Status: v1alpha1.ApplicationSetStatus{ 4836 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4837 { 4838 Application: "app1", 4839 Message: "", 4840 Status: "Progressing", 4841 Step: "1", 4842 }, 4843 }, 4844 }, 4845 }, 4846 apps: []v1alpha1.Application{ 4847 { 4848 ObjectMeta: metav1.ObjectMeta{ 4849 Name: "app1", 4850 }, 4851 Status: v1alpha1.ApplicationStatus{ 4852 Health: v1alpha1.HealthStatus{ 4853 Status: health.HealthStatusHealthy, 4854 }, 4855 OperationState: &v1alpha1.OperationState{ 4856 Phase: common.OperationSucceeded, 4857 }, 4858 Sync: v1alpha1.SyncStatus{ 4859 Status: v1alpha1.SyncStatusCodeSynced, 4860 }, 4861 }, 4862 }, 4863 }, 4864 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4865 { 4866 Application: "app1", 4867 Message: "Application resource became Healthy, updating status from Progressing to Healthy.", 4868 Status: "Healthy", 4869 Step: "1", 4870 }, 4871 }, 4872 }, 4873 { 4874 name: "progresses a waiting healthy application to healthy", 4875 appSet: v1alpha1.ApplicationSet{ 4876 ObjectMeta: metav1.ObjectMeta{ 4877 Name: "name", 4878 Namespace: "argocd", 4879 }, 4880 Spec: v1alpha1.ApplicationSetSpec{ 4881 Strategy: &v1alpha1.ApplicationSetStrategy{ 4882 Type: "RollingSync", 4883 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4884 }, 4885 }, 4886 Status: v1alpha1.ApplicationSetStatus{ 4887 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4888 { 4889 Application: "app1", 4890 Message: "", 4891 Status: "Waiting", 4892 Step: "1", 4893 }, 4894 }, 4895 }, 4896 }, 4897 apps: []v1alpha1.Application{ 4898 { 4899 ObjectMeta: metav1.ObjectMeta{ 4900 Name: "app1", 4901 }, 4902 Status: v1alpha1.ApplicationStatus{ 4903 Health: v1alpha1.HealthStatus{ 4904 Status: health.HealthStatusHealthy, 4905 }, 4906 OperationState: &v1alpha1.OperationState{ 4907 Phase: common.OperationSucceeded, 4908 }, 4909 Sync: v1alpha1.SyncStatus{ 4910 Status: v1alpha1.SyncStatusCodeSynced, 4911 }, 4912 }, 4913 }, 4914 }, 4915 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4916 { 4917 Application: "app1", 4918 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 4919 Status: "Healthy", 4920 Step: "1", 4921 }, 4922 }, 4923 }, 4924 { 4925 name: "progresses a new outofsync application in a later step to waiting", 4926 appSet: v1alpha1.ApplicationSet{ 4927 ObjectMeta: metav1.ObjectMeta{ 4928 Name: "name", 4929 Namespace: "argocd", 4930 }, 4931 Spec: v1alpha1.ApplicationSetSpec{ 4932 Strategy: &v1alpha1.ApplicationSetStrategy{ 4933 Type: "RollingSync", 4934 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4935 }, 4936 }, 4937 }, 4938 apps: []v1alpha1.Application{ 4939 { 4940 ObjectMeta: metav1.ObjectMeta{ 4941 Name: "app1", 4942 }, 4943 Status: v1alpha1.ApplicationStatus{ 4944 Health: v1alpha1.HealthStatus{ 4945 Status: health.HealthStatusHealthy, 4946 }, 4947 OperationState: &v1alpha1.OperationState{ 4948 Phase: common.OperationSucceeded, 4949 }, 4950 Sync: v1alpha1.SyncStatus{ 4951 Status: v1alpha1.SyncStatusCodeOutOfSync, 4952 }, 4953 }, 4954 }, 4955 }, 4956 appStepMap: map[string]int{ 4957 "app1": 1, 4958 "app2": 0, 4959 }, 4960 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4961 { 4962 Application: "app1", 4963 Message: "No Application status found, defaulting status to Waiting.", 4964 Status: "Waiting", 4965 Step: "2", 4966 }, 4967 }, 4968 }, 4969 { 4970 name: "progresses a pending application with a successful sync to progressing", 4971 appSet: v1alpha1.ApplicationSet{ 4972 ObjectMeta: metav1.ObjectMeta{ 4973 Name: "name", 4974 Namespace: "argocd", 4975 }, 4976 Spec: v1alpha1.ApplicationSetSpec{ 4977 Strategy: &v1alpha1.ApplicationSetStrategy{ 4978 Type: "RollingSync", 4979 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 4980 }, 4981 }, 4982 Status: v1alpha1.ApplicationSetStatus{ 4983 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 4984 { 4985 Application: "app1", 4986 LastTransitionTime: &metav1.Time{ 4987 Time: time.Now().Add(time.Duration(-1) * time.Minute), 4988 }, 4989 Message: "", 4990 Status: "Pending", 4991 Step: "1", 4992 }, 4993 }, 4994 }, 4995 }, 4996 apps: []v1alpha1.Application{ 4997 { 4998 ObjectMeta: metav1.ObjectMeta{ 4999 Name: "app1", 5000 }, 5001 Status: v1alpha1.ApplicationStatus{ 5002 Health: v1alpha1.HealthStatus{ 5003 Status: health.HealthStatusDegraded, 5004 }, 5005 OperationState: &v1alpha1.OperationState{ 5006 Phase: common.OperationSucceeded, 5007 StartedAt: metav1.Time{ 5008 Time: time.Now(), 5009 }, 5010 }, 5011 Sync: v1alpha1.SyncStatus{ 5012 Status: v1alpha1.SyncStatusCodeSynced, 5013 }, 5014 }, 5015 }, 5016 }, 5017 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5018 { 5019 Application: "app1", 5020 Message: "Application resource completed a sync successfully, updating status from Pending to Progressing.", 5021 Status: "Progressing", 5022 Step: "1", 5023 }, 5024 }, 5025 }, 5026 { 5027 name: "progresses a pending application with a successful sync <1s ago to progressing", 5028 appSet: v1alpha1.ApplicationSet{ 5029 ObjectMeta: metav1.ObjectMeta{ 5030 Name: "name", 5031 Namespace: "argocd", 5032 }, 5033 Spec: v1alpha1.ApplicationSetSpec{ 5034 Strategy: &v1alpha1.ApplicationSetStrategy{ 5035 Type: "RollingSync", 5036 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 5037 }, 5038 }, 5039 Status: v1alpha1.ApplicationSetStatus{ 5040 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5041 { 5042 Application: "app1", 5043 LastTransitionTime: &metav1.Time{ 5044 Time: time.Now(), 5045 }, 5046 Message: "", 5047 Status: "Pending", 5048 Step: "1", 5049 }, 5050 }, 5051 }, 5052 }, 5053 apps: []v1alpha1.Application{ 5054 { 5055 ObjectMeta: metav1.ObjectMeta{ 5056 Name: "app1", 5057 }, 5058 Status: v1alpha1.ApplicationStatus{ 5059 Health: v1alpha1.HealthStatus{ 5060 Status: health.HealthStatusDegraded, 5061 }, 5062 OperationState: &v1alpha1.OperationState{ 5063 Phase: common.OperationSucceeded, 5064 StartedAt: metav1.Time{ 5065 Time: time.Now().Add(time.Duration(-1) * time.Second), 5066 }, 5067 }, 5068 Sync: v1alpha1.SyncStatus{ 5069 Status: v1alpha1.SyncStatusCodeSynced, 5070 }, 5071 }, 5072 }, 5073 }, 5074 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5075 { 5076 Application: "app1", 5077 Message: "Application resource completed a sync successfully, updating status from Pending to Progressing.", 5078 Status: "Progressing", 5079 Step: "1", 5080 }, 5081 }, 5082 }, 5083 { 5084 name: "does not progresses a pending application with an old successful sync to progressing", 5085 appSet: v1alpha1.ApplicationSet{ 5086 ObjectMeta: metav1.ObjectMeta{ 5087 Name: "name", 5088 Namespace: "argocd", 5089 }, 5090 Spec: v1alpha1.ApplicationSetSpec{ 5091 Strategy: &v1alpha1.ApplicationSetStrategy{ 5092 Type: "RollingSync", 5093 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 5094 }, 5095 }, 5096 Status: v1alpha1.ApplicationSetStatus{ 5097 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5098 { 5099 Application: "app1", 5100 LastTransitionTime: &metav1.Time{ 5101 Time: time.Now(), 5102 }, 5103 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5104 Status: "Pending", 5105 Step: "1", 5106 }, 5107 }, 5108 }, 5109 }, 5110 apps: []v1alpha1.Application{ 5111 { 5112 ObjectMeta: metav1.ObjectMeta{ 5113 Name: "app1", 5114 }, 5115 Status: v1alpha1.ApplicationStatus{ 5116 Health: v1alpha1.HealthStatus{ 5117 Status: health.HealthStatusDegraded, 5118 }, 5119 OperationState: &v1alpha1.OperationState{ 5120 Phase: common.OperationSucceeded, 5121 StartedAt: metav1.Time{ 5122 Time: time.Now().Add(time.Duration(-11) * time.Second), 5123 }, 5124 }, 5125 Sync: v1alpha1.SyncStatus{ 5126 Status: v1alpha1.SyncStatusCodeSynced, 5127 }, 5128 }, 5129 }, 5130 }, 5131 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5132 { 5133 Application: "app1", 5134 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5135 Status: "Pending", 5136 Step: "1", 5137 }, 5138 }, 5139 }, 5140 { 5141 name: "removes the appStatus for applications that no longer exist", 5142 appSet: v1alpha1.ApplicationSet{ 5143 ObjectMeta: metav1.ObjectMeta{ 5144 Name: "name", 5145 Namespace: "argocd", 5146 }, 5147 Spec: v1alpha1.ApplicationSetSpec{ 5148 Strategy: &v1alpha1.ApplicationSetStrategy{ 5149 Type: "RollingSync", 5150 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{}, 5151 }, 5152 }, 5153 Status: v1alpha1.ApplicationSetStatus{ 5154 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5155 { 5156 Application: "app1", 5157 Message: "Application has pending changes, setting status to Waiting.", 5158 Status: "Waiting", 5159 Step: "1", 5160 }, 5161 { 5162 Application: "app2", 5163 Message: "Application has pending changes, setting status to Waiting.", 5164 Status: "Waiting", 5165 Step: "1", 5166 }, 5167 }, 5168 }, 5169 }, 5170 apps: []v1alpha1.Application{ 5171 { 5172 ObjectMeta: metav1.ObjectMeta{ 5173 Name: "app1", 5174 }, 5175 Status: v1alpha1.ApplicationStatus{ 5176 Health: v1alpha1.HealthStatus{ 5177 Status: health.HealthStatusHealthy, 5178 }, 5179 OperationState: &v1alpha1.OperationState{ 5180 Phase: common.OperationSucceeded, 5181 }, 5182 Sync: v1alpha1.SyncStatus{ 5183 Status: v1alpha1.SyncStatusCodeSynced, 5184 }, 5185 }, 5186 }, 5187 }, 5188 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5189 { 5190 Application: "app1", 5191 Message: "Application resource is already Healthy, updating status from Waiting to Healthy.", 5192 Status: "Healthy", 5193 Step: "1", 5194 }, 5195 }, 5196 }, 5197 } { 5198 5199 t.Run(cc.name, func(t *testing.T) { 5200 5201 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 5202 argoDBMock := dbmocks.ArgoDB{} 5203 argoObjs := []runtime.Object{} 5204 5205 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build() 5206 5207 r := ApplicationSetReconciler{ 5208 Client: client, 5209 Scheme: scheme, 5210 Recorder: record.NewFakeRecorder(1), 5211 Cache: &fakeCache{}, 5212 Generators: map[string]generators.Generator{}, 5213 ArgoDB: &argoDBMock, 5214 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 5215 KubeClientset: kubeclientset, 5216 } 5217 5218 appStatuses, err := r.updateApplicationSetApplicationStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps, cc.appStepMap) 5219 5220 // opt out of testing the LastTransitionTime is accurate 5221 for i := range appStatuses { 5222 appStatuses[i].LastTransitionTime = nil 5223 } 5224 5225 assert.Equal(t, err, nil, "expected no errors, but errors occured") 5226 assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual") 5227 }) 5228 } 5229 } 5230 5231 func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) { 5232 5233 scheme := runtime.NewScheme() 5234 err := v1alpha1.AddToScheme(scheme) 5235 assert.Nil(t, err) 5236 5237 err = v1alpha1.AddToScheme(scheme) 5238 assert.Nil(t, err) 5239 5240 for _, cc := range []struct { 5241 name string 5242 appSet v1alpha1.ApplicationSet 5243 appSyncMap map[string]bool 5244 appStepMap map[string]int 5245 appMap map[string]v1alpha1.Application 5246 expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus 5247 }{ 5248 { 5249 name: "handles an empty appSync and appStepMap", 5250 appSet: v1alpha1.ApplicationSet{ 5251 ObjectMeta: metav1.ObjectMeta{ 5252 Name: "name", 5253 Namespace: "argocd", 5254 }, 5255 Spec: v1alpha1.ApplicationSetSpec{ 5256 Strategy: &v1alpha1.ApplicationSetStrategy{ 5257 Type: "RollingSync", 5258 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5259 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5260 { 5261 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5262 }, 5263 { 5264 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5265 }, 5266 }, 5267 }, 5268 }, 5269 }, 5270 Status: v1alpha1.ApplicationSetStatus{ 5271 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5272 }, 5273 }, 5274 appSyncMap: map[string]bool{}, 5275 appStepMap: map[string]int{}, 5276 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5277 }, 5278 { 5279 name: "handles an empty strategy", 5280 appSet: v1alpha1.ApplicationSet{ 5281 ObjectMeta: metav1.ObjectMeta{ 5282 Name: "name", 5283 Namespace: "argocd", 5284 }, 5285 Spec: v1alpha1.ApplicationSetSpec{}, 5286 Status: v1alpha1.ApplicationSetStatus{ 5287 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5288 }, 5289 }, 5290 appSyncMap: map[string]bool{}, 5291 appStepMap: map[string]int{}, 5292 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5293 }, 5294 { 5295 name: "handles an empty applicationset strategy", 5296 appSet: v1alpha1.ApplicationSet{ 5297 ObjectMeta: metav1.ObjectMeta{ 5298 Name: "name", 5299 Namespace: "argocd", 5300 }, 5301 Spec: v1alpha1.ApplicationSetSpec{ 5302 Strategy: &v1alpha1.ApplicationSetStrategy{}, 5303 }, 5304 Status: v1alpha1.ApplicationSetStatus{ 5305 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5306 }, 5307 }, 5308 appSyncMap: map[string]bool{}, 5309 appStepMap: map[string]int{}, 5310 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5311 }, 5312 { 5313 name: "handles an appSyncMap with no existing statuses", 5314 appSet: v1alpha1.ApplicationSet{ 5315 ObjectMeta: metav1.ObjectMeta{ 5316 Name: "name", 5317 Namespace: "argocd", 5318 }, 5319 Status: v1alpha1.ApplicationSetStatus{ 5320 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5321 }, 5322 }, 5323 appSyncMap: map[string]bool{ 5324 "app1": true, 5325 "app2": false, 5326 }, 5327 appStepMap: map[string]int{ 5328 "app1": 0, 5329 "app2": 1, 5330 }, 5331 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{}, 5332 }, 5333 { 5334 name: "handles updating a RollingSync status from Waiting to Pending", 5335 appSet: v1alpha1.ApplicationSet{ 5336 ObjectMeta: metav1.ObjectMeta{ 5337 Name: "name", 5338 Namespace: "argocd", 5339 }, 5340 Spec: v1alpha1.ApplicationSetSpec{ 5341 Strategy: &v1alpha1.ApplicationSetStrategy{ 5342 Type: "RollingSync", 5343 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5344 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5345 { 5346 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5347 }, 5348 { 5349 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5350 }, 5351 }, 5352 }, 5353 }, 5354 }, 5355 Status: v1alpha1.ApplicationSetStatus{ 5356 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5357 { 5358 Application: "app1", 5359 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5360 Status: "Waiting", 5361 }, 5362 }, 5363 }, 5364 }, 5365 appSyncMap: map[string]bool{ 5366 "app1": true, 5367 }, 5368 appStepMap: map[string]int{ 5369 "app1": 0, 5370 }, 5371 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5372 { 5373 Application: "app1", 5374 LastTransitionTime: nil, 5375 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5376 Status: "Pending", 5377 Step: "1", 5378 }, 5379 }, 5380 }, 5381 { 5382 name: "does not update a RollingSync status if appSyncMap is false", 5383 appSet: v1alpha1.ApplicationSet{ 5384 ObjectMeta: metav1.ObjectMeta{ 5385 Name: "name", 5386 Namespace: "argocd", 5387 }, 5388 Spec: v1alpha1.ApplicationSetSpec{ 5389 Strategy: &v1alpha1.ApplicationSetStrategy{ 5390 Type: "RollingSync", 5391 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5392 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5393 { 5394 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5395 }, 5396 { 5397 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5398 }, 5399 }, 5400 }, 5401 }, 5402 }, 5403 Status: v1alpha1.ApplicationSetStatus{ 5404 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5405 { 5406 Application: "app1", 5407 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5408 Status: "Waiting", 5409 Step: "1", 5410 }, 5411 }, 5412 }, 5413 }, 5414 appSyncMap: map[string]bool{ 5415 "app1": false, 5416 }, 5417 appStepMap: map[string]int{ 5418 "app1": 0, 5419 }, 5420 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5421 { 5422 Application: "app1", 5423 LastTransitionTime: nil, 5424 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5425 Status: "Waiting", 5426 Step: "1", 5427 }, 5428 }, 5429 }, 5430 { 5431 name: "does not update a status if status is not pending", 5432 appSet: v1alpha1.ApplicationSet{ 5433 ObjectMeta: metav1.ObjectMeta{ 5434 Name: "name", 5435 Namespace: "argocd", 5436 }, 5437 Spec: v1alpha1.ApplicationSetSpec{ 5438 Strategy: &v1alpha1.ApplicationSetStrategy{ 5439 Type: "RollingSync", 5440 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5441 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5442 { 5443 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5444 }, 5445 { 5446 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5447 }, 5448 }, 5449 }, 5450 }, 5451 }, 5452 Status: v1alpha1.ApplicationSetStatus{ 5453 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5454 { 5455 Application: "app1", 5456 Message: "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.", 5457 Status: "Healthy", 5458 Step: "1", 5459 }, 5460 }, 5461 }, 5462 }, 5463 appSyncMap: map[string]bool{ 5464 "app1": true, 5465 }, 5466 appStepMap: map[string]int{ 5467 "app1": 0, 5468 }, 5469 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5470 { 5471 Application: "app1", 5472 LastTransitionTime: nil, 5473 Message: "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.", 5474 Status: "Healthy", 5475 Step: "1", 5476 }, 5477 }, 5478 }, 5479 { 5480 name: "does not update a status if maxUpdate has already been reached with RollingSync", 5481 appSet: v1alpha1.ApplicationSet{ 5482 ObjectMeta: metav1.ObjectMeta{ 5483 Name: "name", 5484 Namespace: "argocd", 5485 }, 5486 Spec: v1alpha1.ApplicationSetSpec{ 5487 Strategy: &v1alpha1.ApplicationSetStrategy{ 5488 Type: "RollingSync", 5489 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5490 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5491 { 5492 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5493 MaxUpdate: &intstr.IntOrString{ 5494 Type: intstr.Int, 5495 IntVal: 3, 5496 }, 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 resource became Progressing, updating status from Pending to Progressing.", 5510 Status: "Progressing", 5511 Step: "1", 5512 }, 5513 { 5514 Application: "app2", 5515 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5516 Status: "Waiting", 5517 Step: "1", 5518 }, 5519 { 5520 Application: "app3", 5521 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5522 Status: "Waiting", 5523 Step: "1", 5524 }, 5525 { 5526 Application: "app4", 5527 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5528 Status: "Pending", 5529 Step: "1", 5530 }, 5531 }, 5532 }, 5533 }, 5534 appSyncMap: map[string]bool{ 5535 "app1": true, 5536 "app2": true, 5537 "app3": true, 5538 "app4": true, 5539 }, 5540 appStepMap: map[string]int{ 5541 "app1": 0, 5542 "app2": 0, 5543 "app3": 0, 5544 "app4": 0, 5545 }, 5546 appMap: map[string]v1alpha1.Application{ 5547 "app1": { 5548 ObjectMeta: metav1.ObjectMeta{ 5549 Name: "app1", 5550 }, 5551 Status: v1alpha1.ApplicationStatus{ 5552 Sync: v1alpha1.SyncStatus{ 5553 Status: v1alpha1.SyncStatusCodeOutOfSync, 5554 }, 5555 }, 5556 }, 5557 "app2": { 5558 ObjectMeta: metav1.ObjectMeta{ 5559 Name: "app2", 5560 }, 5561 Status: v1alpha1.ApplicationStatus{ 5562 Sync: v1alpha1.SyncStatus{ 5563 Status: v1alpha1.SyncStatusCodeOutOfSync, 5564 }, 5565 }, 5566 }, 5567 "app3": { 5568 ObjectMeta: metav1.ObjectMeta{ 5569 Name: "app3", 5570 }, 5571 Status: v1alpha1.ApplicationStatus{ 5572 Sync: v1alpha1.SyncStatus{ 5573 Status: v1alpha1.SyncStatusCodeOutOfSync, 5574 }, 5575 }, 5576 }, 5577 "app4": { 5578 ObjectMeta: metav1.ObjectMeta{ 5579 Name: "app4", 5580 }, 5581 Status: v1alpha1.ApplicationStatus{ 5582 Sync: v1alpha1.SyncStatus{ 5583 Status: v1alpha1.SyncStatusCodeOutOfSync, 5584 }, 5585 }, 5586 }, 5587 }, 5588 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5589 { 5590 Application: "app1", 5591 LastTransitionTime: nil, 5592 Message: "Application resource became Progressing, updating status from Pending to Progressing.", 5593 Status: "Progressing", 5594 Step: "1", 5595 }, 5596 { 5597 Application: "app2", 5598 LastTransitionTime: nil, 5599 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5600 Status: "Pending", 5601 Step: "1", 5602 }, 5603 { 5604 Application: "app3", 5605 LastTransitionTime: nil, 5606 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5607 Status: "Waiting", 5608 Step: "1", 5609 }, 5610 { 5611 Application: "app4", 5612 LastTransitionTime: nil, 5613 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5614 Status: "Pending", 5615 Step: "1", 5616 }, 5617 }, 5618 }, 5619 { 5620 name: "rounds down for maxUpdate set to percentage string", 5621 appSet: v1alpha1.ApplicationSet{ 5622 ObjectMeta: metav1.ObjectMeta{ 5623 Name: "name", 5624 Namespace: "argocd", 5625 }, 5626 Spec: v1alpha1.ApplicationSetSpec{ 5627 Strategy: &v1alpha1.ApplicationSetStrategy{ 5628 Type: "RollingSync", 5629 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5630 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5631 { 5632 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5633 MaxUpdate: &intstr.IntOrString{ 5634 Type: intstr.String, 5635 StrVal: "50%", 5636 }, 5637 }, 5638 { 5639 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5640 }, 5641 }, 5642 }, 5643 }, 5644 }, 5645 Status: v1alpha1.ApplicationSetStatus{ 5646 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5647 { 5648 Application: "app1", 5649 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5650 Status: "Waiting", 5651 Step: "1", 5652 }, 5653 { 5654 Application: "app2", 5655 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5656 Status: "Waiting", 5657 Step: "1", 5658 }, 5659 { 5660 Application: "app3", 5661 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5662 Status: "Waiting", 5663 Step: "1", 5664 }, 5665 }, 5666 }, 5667 }, 5668 appSyncMap: map[string]bool{ 5669 "app1": true, 5670 "app2": true, 5671 "app3": true, 5672 }, 5673 appStepMap: map[string]int{ 5674 "app1": 0, 5675 "app2": 0, 5676 "app3": 0, 5677 }, 5678 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5679 { 5680 Application: "app1", 5681 LastTransitionTime: nil, 5682 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5683 Status: "Pending", 5684 Step: "1", 5685 }, 5686 { 5687 Application: "app2", 5688 LastTransitionTime: nil, 5689 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5690 Status: "Waiting", 5691 Step: "1", 5692 }, 5693 { 5694 Application: "app3", 5695 LastTransitionTime: nil, 5696 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5697 Status: "Waiting", 5698 Step: "1", 5699 }, 5700 }, 5701 }, 5702 { 5703 name: "does not update any applications with maxUpdate set to 0", 5704 appSet: v1alpha1.ApplicationSet{ 5705 ObjectMeta: metav1.ObjectMeta{ 5706 Name: "name", 5707 Namespace: "argocd", 5708 }, 5709 Spec: v1alpha1.ApplicationSetSpec{ 5710 Strategy: &v1alpha1.ApplicationSetStrategy{ 5711 Type: "RollingSync", 5712 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5713 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5714 { 5715 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5716 MaxUpdate: &intstr.IntOrString{ 5717 Type: intstr.Int, 5718 IntVal: 0, 5719 }, 5720 }, 5721 { 5722 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5723 }, 5724 }, 5725 }, 5726 }, 5727 }, 5728 Status: v1alpha1.ApplicationSetStatus{ 5729 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5730 { 5731 Application: "app1", 5732 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5733 Status: "Waiting", 5734 Step: "1", 5735 }, 5736 { 5737 Application: "app2", 5738 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5739 Status: "Waiting", 5740 Step: "1", 5741 }, 5742 { 5743 Application: "app3", 5744 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5745 Status: "Waiting", 5746 Step: "1", 5747 }, 5748 }, 5749 }, 5750 }, 5751 appSyncMap: map[string]bool{ 5752 "app1": true, 5753 "app2": true, 5754 "app3": true, 5755 }, 5756 appStepMap: map[string]int{ 5757 "app1": 0, 5758 "app2": 0, 5759 "app3": 0, 5760 }, 5761 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5762 { 5763 Application: "app1", 5764 LastTransitionTime: nil, 5765 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5766 Status: "Waiting", 5767 Step: "1", 5768 }, 5769 { 5770 Application: "app2", 5771 LastTransitionTime: nil, 5772 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5773 Status: "Waiting", 5774 Step: "1", 5775 }, 5776 { 5777 Application: "app3", 5778 LastTransitionTime: nil, 5779 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5780 Status: "Waiting", 5781 Step: "1", 5782 }, 5783 }, 5784 }, 5785 { 5786 name: "updates all applications with maxUpdate set to 100%", 5787 appSet: v1alpha1.ApplicationSet{ 5788 ObjectMeta: metav1.ObjectMeta{ 5789 Name: "name", 5790 Namespace: "argocd", 5791 }, 5792 Spec: v1alpha1.ApplicationSetSpec{ 5793 Strategy: &v1alpha1.ApplicationSetStrategy{ 5794 Type: "RollingSync", 5795 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5796 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5797 { 5798 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5799 MaxUpdate: &intstr.IntOrString{ 5800 Type: intstr.String, 5801 StrVal: "100%", 5802 }, 5803 }, 5804 { 5805 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5806 }, 5807 }, 5808 }, 5809 }, 5810 }, 5811 Status: v1alpha1.ApplicationSetStatus{ 5812 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5813 { 5814 Application: "app1", 5815 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5816 Status: "Waiting", 5817 Step: "1", 5818 }, 5819 { 5820 Application: "app2", 5821 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5822 Status: "Waiting", 5823 Step: "1", 5824 }, 5825 { 5826 Application: "app3", 5827 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5828 Status: "Waiting", 5829 Step: "1", 5830 }, 5831 }, 5832 }, 5833 }, 5834 appSyncMap: map[string]bool{ 5835 "app1": true, 5836 "app2": true, 5837 "app3": true, 5838 }, 5839 appStepMap: map[string]int{ 5840 "app1": 0, 5841 "app2": 0, 5842 "app3": 0, 5843 }, 5844 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5845 { 5846 Application: "app1", 5847 LastTransitionTime: nil, 5848 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5849 Status: "Pending", 5850 Step: "1", 5851 }, 5852 { 5853 Application: "app2", 5854 LastTransitionTime: nil, 5855 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5856 Status: "Pending", 5857 Step: "1", 5858 }, 5859 { 5860 Application: "app3", 5861 LastTransitionTime: nil, 5862 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5863 Status: "Pending", 5864 Step: "1", 5865 }, 5866 }, 5867 }, 5868 { 5869 name: "updates at least 1 application with maxUpdate >0%", 5870 appSet: v1alpha1.ApplicationSet{ 5871 ObjectMeta: metav1.ObjectMeta{ 5872 Name: "name", 5873 Namespace: "argocd", 5874 }, 5875 Spec: v1alpha1.ApplicationSetSpec{ 5876 Strategy: &v1alpha1.ApplicationSetStrategy{ 5877 Type: "RollingSync", 5878 RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ 5879 Steps: []v1alpha1.ApplicationSetRolloutStep{ 5880 { 5881 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5882 MaxUpdate: &intstr.IntOrString{ 5883 Type: intstr.String, 5884 StrVal: "1%", 5885 }, 5886 }, 5887 { 5888 MatchExpressions: []v1alpha1.ApplicationMatchExpression{}, 5889 }, 5890 }, 5891 }, 5892 }, 5893 }, 5894 Status: v1alpha1.ApplicationSetStatus{ 5895 ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5896 { 5897 Application: "app1", 5898 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5899 Status: "Waiting", 5900 Step: "1", 5901 }, 5902 { 5903 Application: "app2", 5904 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5905 Status: "Waiting", 5906 Step: "1", 5907 }, 5908 { 5909 Application: "app3", 5910 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5911 Status: "Waiting", 5912 Step: "1", 5913 }, 5914 }, 5915 }, 5916 }, 5917 appSyncMap: map[string]bool{ 5918 "app1": true, 5919 "app2": true, 5920 "app3": true, 5921 }, 5922 appStepMap: map[string]int{ 5923 "app1": 0, 5924 "app2": 0, 5925 "app3": 0, 5926 }, 5927 expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{ 5928 { 5929 Application: "app1", 5930 LastTransitionTime: nil, 5931 Message: "Application moved to Pending status, watching for the Application resource to start Progressing.", 5932 Status: "Pending", 5933 Step: "1", 5934 }, 5935 { 5936 Application: "app2", 5937 LastTransitionTime: nil, 5938 Message: "Application is out of date with the current AppSet generation, setting status to Waiting.", 5939 Status: "Waiting", 5940 Step: "1", 5941 }, 5942 { 5943 Application: "app3", 5944 LastTransitionTime: nil, 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 }, 5951 } { 5952 5953 t.Run(cc.name, func(t *testing.T) { 5954 5955 kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) 5956 argoDBMock := dbmocks.ArgoDB{} 5957 argoObjs := []runtime.Object{} 5958 5959 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build() 5960 5961 r := ApplicationSetReconciler{ 5962 Client: client, 5963 Scheme: scheme, 5964 Recorder: record.NewFakeRecorder(1), 5965 Cache: &fakeCache{}, 5966 Generators: map[string]generators.Generator{}, 5967 ArgoDB: &argoDBMock, 5968 ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...), 5969 KubeClientset: kubeclientset, 5970 } 5971 5972 appStatuses, err := r.updateApplicationSetApplicationStatusProgress(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appSyncMap, cc.appStepMap, cc.appMap) 5973 5974 // opt out of testing the LastTransitionTime is accurate 5975 for i := range appStatuses { 5976 appStatuses[i].LastTransitionTime = nil 5977 } 5978 5979 assert.Equal(t, err, nil, "expected no errors, but errors occured") 5980 assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual") 5981 }) 5982 } 5983 } 5984 5985 func TestOwnsHandler(t *testing.T) { 5986 // progressive syncs do not affect create, delete, or generic 5987 ownsHandler := getOwnsHandlerPredicates(true) 5988 assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{})) 5989 assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{})) 5990 assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{})) 5991 ownsHandler = getOwnsHandlerPredicates(false) 5992 assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{})) 5993 assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{})) 5994 assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{})) 5995 5996 now := metav1.Now() 5997 type args struct { 5998 e event.UpdateEvent 5999 enableProgressiveSyncs bool 6000 } 6001 tests := []struct { 6002 name string 6003 args args 6004 want bool 6005 }{ 6006 {name: "SameApplicationReconciledAtDiff", args: args{e: event.UpdateEvent{ 6007 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}}, 6008 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}}, 6009 }}, want: false}, 6010 {name: "SameApplicationResourceVersionDiff", args: args{e: event.UpdateEvent{ 6011 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6012 ResourceVersion: "foo", 6013 }}, 6014 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6015 ResourceVersion: "bar", 6016 }}, 6017 }}, want: false}, 6018 {name: "ApplicationHealthStatusDiff", args: args{e: event.UpdateEvent{ 6019 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6020 Health: v1alpha1.HealthStatus{ 6021 Status: "Unknown", 6022 }, 6023 }}, 6024 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6025 Health: v1alpha1.HealthStatus{ 6026 Status: "Healthy", 6027 }, 6028 }}, 6029 }, 6030 enableProgressiveSyncs: true, 6031 }, want: true}, 6032 {name: "ApplicationSyncStatusDiff", args: args{e: event.UpdateEvent{ 6033 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6034 Sync: v1alpha1.SyncStatus{ 6035 Status: "OutOfSync", 6036 }, 6037 }}, 6038 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6039 Sync: v1alpha1.SyncStatus{ 6040 Status: "Synced", 6041 }, 6042 }}, 6043 }, 6044 enableProgressiveSyncs: true, 6045 }, want: true}, 6046 {name: "ApplicationOperationStateDiff", args: args{e: event.UpdateEvent{ 6047 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6048 OperationState: &v1alpha1.OperationState{ 6049 Phase: "foo", 6050 }, 6051 }}, 6052 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6053 OperationState: &v1alpha1.OperationState{ 6054 Phase: "bar", 6055 }, 6056 }}, 6057 }, 6058 enableProgressiveSyncs: true, 6059 }, want: true}, 6060 {name: "ApplicationOperationStartedAtDiff", args: args{e: event.UpdateEvent{ 6061 ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6062 OperationState: &v1alpha1.OperationState{ 6063 StartedAt: now, 6064 }, 6065 }}, 6066 ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ 6067 OperationState: &v1alpha1.OperationState{ 6068 StartedAt: metav1.NewTime(now.Add(time.Minute * 1)), 6069 }, 6070 }}, 6071 }, 6072 enableProgressiveSyncs: true, 6073 }, want: true}, 6074 {name: "SameApplicationGeneration", args: args{e: event.UpdateEvent{ 6075 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6076 Generation: 1, 6077 }}, 6078 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{ 6079 Generation: 2, 6080 }}, 6081 }}, want: false}, 6082 {name: "DifferentApplicationSpec", args: args{e: event.UpdateEvent{ 6083 ObjectOld: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "default"}}, 6084 ObjectNew: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "not-default"}}, 6085 }}, want: true}, 6086 {name: "DifferentApplicationLabels", args: args{e: event.UpdateEvent{ 6087 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}}, 6088 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}}, 6089 }}, want: true}, 6090 {name: "DifferentApplicationAnnotations", args: args{e: event.UpdateEvent{ 6091 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "bar"}}}, 6092 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"bar": "foo"}}}, 6093 }}, want: true}, 6094 {name: "DifferentApplicationFinalizers", args: args{e: event.UpdateEvent{ 6095 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"argo"}}}, 6096 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"none"}}}, 6097 }}, want: true}, 6098 {name: "NotAnAppOld", args: args{e: event.UpdateEvent{ 6099 ObjectOld: &v1alpha1.AppProject{}, 6100 ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}}, 6101 }}, want: false}, 6102 {name: "NotAnAppNew", args: args{e: event.UpdateEvent{ 6103 ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}}, 6104 ObjectNew: &v1alpha1.AppProject{}, 6105 }}, want: false}, 6106 } 6107 for _, tt := range tests { 6108 t.Run(tt.name, func(t *testing.T) { 6109 ownsHandler = getOwnsHandlerPredicates(tt.args.enableProgressiveSyncs) 6110 assert.Equalf(t, tt.want, ownsHandler.UpdateFunc(tt.args.e), "UpdateFunc(%v)", tt.args.e) 6111 }) 6112 } 6113 }