github.com/argoproj/argo-cd/v3@v3.2.1/server/applicationset/applicationset_test.go (about) 1 package applicationset 2 3 import ( 4 "sort" 5 "testing" 6 7 "sigs.k8s.io/controller-runtime/pkg/client" 8 cr_fake "sigs.k8s.io/controller-runtime/pkg/client/fake" 9 10 "github.com/argoproj/gitops-engine/pkg/health" 11 "github.com/argoproj/pkg/v2/sync" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 corev1 "k8s.io/api/core/v1" 15 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/runtime" 18 "k8s.io/client-go/kubernetes/fake" 19 k8scache "k8s.io/client-go/tools/cache" 20 21 "github.com/argoproj/argo-cd/v3/common" 22 "github.com/argoproj/argo-cd/v3/pkg/apiclient/applicationset" 23 appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 24 apps "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake" 25 appinformer "github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions" 26 "github.com/argoproj/argo-cd/v3/server/rbacpolicy" 27 "github.com/argoproj/argo-cd/v3/util/argo" 28 "github.com/argoproj/argo-cd/v3/util/assets" 29 "github.com/argoproj/argo-cd/v3/util/db" 30 "github.com/argoproj/argo-cd/v3/util/rbac" 31 "github.com/argoproj/argo-cd/v3/util/settings" 32 ) 33 34 const ( 35 testNamespace = "default" 36 fakeRepoURL = "https://git.com/repo.git" 37 ) 38 39 var testEnableEventList []string = argo.DefaultEnableEventList() 40 41 func fakeRepo() *appsv1.Repository { 42 return &appsv1.Repository{ 43 Repo: fakeRepoURL, 44 } 45 } 46 47 func fakeCluster() *appsv1.Cluster { 48 return &appsv1.Cluster{ 49 Server: "https://cluster-api.example.com", 50 Name: "fake-cluster", 51 Config: appsv1.ClusterConfig{}, 52 } 53 } 54 55 // return an ApplicationServiceServer which returns fake data 56 func newTestAppSetServer(t *testing.T, objects ...client.Object) *Server { 57 t.Helper() 58 f := func(enf *rbac.Enforcer) { 59 _ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV) 60 enf.SetDefaultRole("role:admin") 61 } 62 scopedNamespaces := "" 63 return newTestAppSetServerWithEnforcerConfigure(t, f, scopedNamespaces, objects...) 64 } 65 66 // return an ApplicationServiceServer which returns fake data 67 func newTestNamespacedAppSetServer(t *testing.T, objects ...client.Object) *Server { 68 t.Helper() 69 f := func(enf *rbac.Enforcer) { 70 _ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV) 71 enf.SetDefaultRole("role:admin") 72 } 73 scopedNamespaces := "argocd" 74 return newTestAppSetServerWithEnforcerConfigure(t, f, scopedNamespaces, objects...) 75 } 76 77 func newTestAppSetServerWithEnforcerConfigure(t *testing.T, f func(*rbac.Enforcer), namespace string, objects ...client.Object) *Server { 78 t.Helper() 79 kubeclientset := fake.NewClientset(&corev1.ConfigMap{ 80 ObjectMeta: metav1.ObjectMeta{ 81 Namespace: testNamespace, 82 Name: "argocd-cm", 83 Labels: map[string]string{ 84 "app.kubernetes.io/part-of": "argocd", 85 }, 86 }, 87 }, &corev1.Secret{ 88 ObjectMeta: metav1.ObjectMeta{ 89 Name: "argocd-secret", 90 Namespace: testNamespace, 91 }, 92 Data: map[string][]byte{ 93 "admin.password": []byte("test"), 94 "server.secretkey": []byte("test"), 95 }, 96 }) 97 ctx := t.Context() 98 db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset) 99 _, err := db.CreateRepository(ctx, fakeRepo()) 100 require.NoError(t, err) 101 _, err = db.CreateCluster(ctx, fakeCluster()) 102 require.NoError(t, err) 103 104 defaultProj := &appsv1.AppProject{ 105 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"}, 106 Spec: appsv1.AppProjectSpec{ 107 SourceRepos: []string{"*"}, 108 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 109 }, 110 } 111 myProj := &appsv1.AppProject{ 112 ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"}, 113 Spec: appsv1.AppProjectSpec{ 114 SourceRepos: []string{"*"}, 115 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 116 }, 117 } 118 119 objects = append(objects, defaultProj, myProj) 120 121 runtimeObjects := make([]runtime.Object, len(objects)) 122 for i := range objects { 123 runtimeObjects[i] = objects[i] 124 } 125 fakeAppsClientset := apps.NewSimpleClientset(runtimeObjects...) 126 factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(namespace), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {})) 127 fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace) 128 129 enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil) 130 f(enforcer) 131 enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims) 132 133 // populate the app informer with the fake objects 134 appInformer := factory.Argoproj().V1alpha1().Applications().Informer() 135 // TODO(jessesuen): probably should return cancel function so tests can stop background informer 136 // ctx, cancel := context.WithCancel(context.Background()) 137 go appInformer.Run(ctx.Done()) 138 if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) { 139 panic("Timed out waiting for caches to sync") 140 } 141 // populate the appset informer with the fake objects 142 appsetInformer := factory.Argoproj().V1alpha1().ApplicationSets().Informer() 143 go appsetInformer.Run(ctx.Done()) 144 if !k8scache.WaitForCacheSync(ctx.Done(), appsetInformer.HasSynced) { 145 panic("Timed out waiting for caches to sync") 146 } 147 148 scheme := runtime.NewScheme() 149 err = appsv1.AddToScheme(scheme) 150 require.NoError(t, err) 151 err = corev1.AddToScheme(scheme) 152 require.NoError(t, err) 153 crClient := cr_fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() 154 155 projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer() 156 go projInformer.Run(ctx.Done()) 157 if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) { 158 panic("Timed out waiting for caches to sync") 159 } 160 161 server := NewServer( 162 db, 163 kubeclientset, 164 nil, 165 crClient, 166 enforcer, 167 nil, 168 fakeAppsClientset, 169 appInformer, 170 factory.Argoproj().V1alpha1().ApplicationSets().Lister(), 171 testNamespace, 172 sync.NewKeyLock(), 173 []string{testNamespace, "external-namespace"}, 174 true, 175 true, 176 "", 177 []string{}, 178 true, 179 true, 180 testEnableEventList, 181 ) 182 return server.(*Server) 183 } 184 185 func newTestAppSet(opts ...func(appset *appsv1.ApplicationSet)) *appsv1.ApplicationSet { 186 appset := appsv1.ApplicationSet{ 187 ObjectMeta: metav1.ObjectMeta{ 188 Namespace: testNamespace, 189 }, 190 Spec: appsv1.ApplicationSetSpec{ 191 Template: appsv1.ApplicationSetTemplate{ 192 Spec: appsv1.ApplicationSpec{ 193 Project: "default", 194 }, 195 }, 196 }, 197 } 198 for i := range opts { 199 opts[i](&appset) 200 } 201 return &appset 202 } 203 204 func testListAppsetsWithLabels(t *testing.T, appsetQuery applicationset.ApplicationSetListQuery, appServer *Server) { 205 t.Helper() 206 validTests := []struct { 207 testName string 208 label string 209 expectedResult []string 210 }{ 211 { 212 testName: "Equality based filtering using '=' operator", 213 label: "key1=value1", 214 expectedResult: []string{"AppSet1"}, 215 }, 216 { 217 testName: "Equality based filtering using '==' operator", 218 label: "key1==value1", 219 expectedResult: []string{"AppSet1"}, 220 }, 221 { 222 testName: "Equality based filtering using '!=' operator", 223 label: "key1!=value1", 224 expectedResult: []string{"AppSet2", "AppSet3"}, 225 }, 226 { 227 testName: "Set based filtering using 'in' operator", 228 label: "key1 in (value1, value3)", 229 expectedResult: []string{"AppSet1", "AppSet3"}, 230 }, 231 { 232 testName: "Set based filtering using 'notin' operator", 233 label: "key1 notin (value1, value3)", 234 expectedResult: []string{"AppSet2"}, 235 }, 236 { 237 testName: "Set based filtering using 'exists' operator", 238 label: "key1", 239 expectedResult: []string{"AppSet1", "AppSet2", "AppSet3"}, 240 }, 241 { 242 testName: "Set based filtering using 'not exists' operator", 243 label: "!key2", 244 expectedResult: []string{"AppSet2", "AppSet3"}, 245 }, 246 } 247 // test valid scenarios 248 for _, validTest := range validTests { 249 t.Run(validTest.testName, func(t *testing.T) { 250 appsetQuery.Selector = validTest.label 251 res, err := appServer.List(t.Context(), &appsetQuery) 252 require.NoError(t, err) 253 apps := []string{} 254 for i := range res.Items { 255 apps = append(apps, res.Items[i].Name) 256 } 257 assert.Equal(t, validTest.expectedResult, apps) 258 }) 259 } 260 261 invalidTests := []struct { 262 testName string 263 label string 264 errorMesage string 265 }{ 266 { 267 testName: "Set based filtering using '>' operator", 268 label: "key1>value1", 269 errorMesage: "error parsing the selector", 270 }, 271 { 272 testName: "Set based filtering using '<' operator", 273 label: "key1<value1", 274 errorMesage: "error parsing the selector", 275 }, 276 } 277 // test invalid scenarios 278 for _, invalidTest := range invalidTests { 279 t.Run(invalidTest.testName, func(t *testing.T) { 280 appsetQuery.Selector = invalidTest.label 281 _, err := appServer.List(t.Context(), &appsetQuery) 282 assert.ErrorContains(t, err, invalidTest.errorMesage) 283 }) 284 } 285 } 286 287 func TestListAppSetsInNamespaceWithLabels(t *testing.T) { 288 testNamespace := "test-namespace" 289 appSetServer := newTestAppSetServer(t, newTestAppSet(func(appset *appsv1.ApplicationSet) { 290 appset.Name = "AppSet1" 291 appset.Namespace = testNamespace 292 appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"}) 293 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 294 appset.Name = "AppSet2" 295 appset.Namespace = testNamespace 296 appset.SetLabels(map[string]string{"key1": "value2"}) 297 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 298 appset.Name = "AppSet3" 299 appset.Namespace = testNamespace 300 appset.SetLabels(map[string]string{"key1": "value3"}) 301 })) 302 appSetServer.enabledNamespaces = []string{testNamespace} 303 appsetQuery := applicationset.ApplicationSetListQuery{AppsetNamespace: testNamespace} 304 305 testListAppsetsWithLabels(t, appsetQuery, appSetServer) 306 } 307 308 func TestListAppSetsInDefaultNSWithLabels(t *testing.T) { 309 appSetServer := newTestAppSetServer(t, newTestAppSet(func(appset *appsv1.ApplicationSet) { 310 appset.Name = "AppSet1" 311 appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"}) 312 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 313 appset.Name = "AppSet2" 314 appset.SetLabels(map[string]string{"key1": "value2"}) 315 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 316 appset.Name = "AppSet3" 317 appset.SetLabels(map[string]string{"key1": "value3"}) 318 })) 319 appsetQuery := applicationset.ApplicationSetListQuery{} 320 321 testListAppsetsWithLabels(t, appsetQuery, appSetServer) 322 } 323 324 // This test covers https://github.com/argoproj/argo-cd/issues/15429 325 // If the namespace isn't provided during listing action, argocd's 326 // default namespace must be used and not all the namespaces 327 func TestListAppSetsWithoutNamespace(t *testing.T) { 328 testNamespace := "test-namespace" 329 appSetServer := newTestNamespacedAppSetServer(t, newTestAppSet(func(appset *appsv1.ApplicationSet) { 330 appset.Name = "AppSet1" 331 appset.Namespace = testNamespace 332 appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"}) 333 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 334 appset.Name = "AppSet2" 335 appset.Namespace = testNamespace 336 appset.SetLabels(map[string]string{"key1": "value2"}) 337 }), newTestAppSet(func(appset *appsv1.ApplicationSet) { 338 appset.Name = "AppSet3" 339 appset.Namespace = testNamespace 340 appset.SetLabels(map[string]string{"key1": "value3"}) 341 })) 342 appSetServer.enabledNamespaces = []string{testNamespace} 343 appsetQuery := applicationset.ApplicationSetListQuery{} 344 345 res, err := appSetServer.List(t.Context(), &appsetQuery) 346 require.NoError(t, err) 347 assert.Empty(t, res.Items) 348 } 349 350 func TestCreateAppSet(t *testing.T) { 351 testAppSet := newTestAppSet() 352 appServer := newTestAppSetServer(t) 353 testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{ 354 { 355 List: &appsv1.ListGenerator{}, 356 }, 357 } 358 createReq := applicationset.ApplicationSetCreateRequest{ 359 Applicationset: testAppSet, 360 } 361 _, err := appServer.Create(t.Context(), &createReq) 362 require.NoError(t, err) 363 } 364 365 func TestCreateAppSetTemplatedProject(t *testing.T) { 366 testAppSet := newTestAppSet() 367 appServer := newTestAppSetServer(t) 368 testAppSet.Spec.Template.Spec.Project = "{{ .project }}" 369 createReq := applicationset.ApplicationSetCreateRequest{ 370 Applicationset: testAppSet, 371 } 372 _, err := appServer.Create(t.Context(), &createReq) 373 assert.EqualError(t, err, "error validating ApplicationSets: the Argo CD API does not currently support creating ApplicationSets with templated `project` fields") 374 } 375 376 func TestCreateAppSetWrongNamespace(t *testing.T) { 377 testAppSet := newTestAppSet() 378 appServer := newTestAppSetServer(t) 379 testAppSet.Namespace = "NOT-ALLOWED" 380 createReq := applicationset.ApplicationSetCreateRequest{ 381 Applicationset: testAppSet, 382 } 383 _, err := appServer.Create(t.Context(), &createReq) 384 385 assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted") 386 } 387 388 func TestCreateAppSetDryRun(t *testing.T) { 389 testAppSet := newTestAppSet() 390 appServer := newTestAppSetServer(t) 391 testAppSet.Spec.Template.Name = "{{name}}" 392 testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{ 393 { 394 List: &appsv1.ListGenerator{ 395 Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"name": "a"}`)}, {Raw: []byte(`{"name": "b"}`)}}, 396 }, 397 }, 398 } 399 createReq := applicationset.ApplicationSetCreateRequest{ 400 Applicationset: testAppSet, 401 DryRun: true, 402 } 403 result, err := appServer.Create(t.Context(), &createReq) 404 405 require.NoError(t, err) 406 assert.Len(t, result.Status.Resources, 2) 407 408 // Sort resulting application by name 409 sort.Slice(result.Status.Resources, func(i, j int) bool { 410 return result.Status.Resources[i].Name < result.Status.Resources[j].Name 411 }) 412 413 assert.Equal(t, "a", result.Status.Resources[0].Name) 414 assert.Equal(t, testAppSet.Namespace, result.Status.Resources[0].Namespace) 415 assert.Equal(t, "b", result.Status.Resources[1].Name) 416 assert.Equal(t, testAppSet.Namespace, result.Status.Resources[1].Namespace) 417 } 418 419 func TestCreateAppSetDryRunWithDuplicate(t *testing.T) { 420 testAppSet := newTestAppSet() 421 appServer := newTestAppSetServer(t) 422 testAppSet.Spec.Template.Name = "{{name}}" 423 testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{ 424 { 425 List: &appsv1.ListGenerator{ 426 Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"name": "a"}`)}, {Raw: []byte(`{"name": "a"}`)}}, 427 }, 428 }, 429 } 430 createReq := applicationset.ApplicationSetCreateRequest{ 431 Applicationset: testAppSet, 432 DryRun: true, 433 } 434 result, err := appServer.Create(t.Context(), &createReq) 435 436 require.NoError(t, err) 437 assert.Len(t, result.Status.Resources, 1) 438 assert.Equal(t, "a", result.Status.Resources[0].Name) 439 assert.Equal(t, testAppSet.Namespace, result.Status.Resources[0].Namespace) 440 } 441 442 func TestGetAppSet(t *testing.T) { 443 appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 444 appset.Name = "AppSet1" 445 }) 446 447 appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 448 appset.Name = "AppSet2" 449 }) 450 451 appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 452 appset.Name = "AppSet3" 453 }) 454 455 t.Run("Get in default namespace", func(t *testing.T) { 456 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 457 458 appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1"} 459 460 res, err := appSetServer.Get(t.Context(), &appsetQuery) 461 require.NoError(t, err) 462 assert.Equal(t, "AppSet1", res.Name) 463 }) 464 465 t.Run("Get in named namespace", func(t *testing.T) { 466 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 467 468 appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1", AppsetNamespace: testNamespace} 469 470 res, err := appSetServer.Get(t.Context(), &appsetQuery) 471 require.NoError(t, err) 472 assert.Equal(t, "AppSet1", res.Name) 473 }) 474 475 t.Run("Get in not allowed namespace", func(t *testing.T) { 476 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 477 478 appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1", AppsetNamespace: "NOT-ALLOWED"} 479 480 _, err := appSetServer.Get(t.Context(), &appsetQuery) 481 assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted") 482 }) 483 } 484 485 func TestDeleteAppSet(t *testing.T) { 486 appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 487 appset.Name = "AppSet1" 488 }) 489 490 appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 491 appset.Name = "AppSet2" 492 }) 493 494 appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 495 appset.Name = "AppSet3" 496 }) 497 498 t.Run("Delete in default namespace", func(t *testing.T) { 499 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 500 501 appsetQuery := applicationset.ApplicationSetDeleteRequest{Name: "AppSet1"} 502 503 res, err := appSetServer.Delete(t.Context(), &appsetQuery) 504 require.NoError(t, err) 505 assert.Equal(t, &applicationset.ApplicationSetResponse{}, res) 506 }) 507 508 t.Run("Delete in named namespace", func(t *testing.T) { 509 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 510 511 appsetQuery := applicationset.ApplicationSetDeleteRequest{Name: "AppSet1", AppsetNamespace: testNamespace} 512 513 res, err := appSetServer.Delete(t.Context(), &appsetQuery) 514 require.NoError(t, err) 515 assert.Equal(t, &applicationset.ApplicationSetResponse{}, res) 516 }) 517 } 518 519 func TestUpdateAppSet(t *testing.T) { 520 appSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { 521 appset.Annotations = map[string]string{ 522 "annotation-key1": "annotation-value1", 523 "annotation-key2": "annotation-value2", 524 } 525 appset.Labels = map[string]string{ 526 "label-key1": "label-value1", 527 "label-key2": "label-value2", 528 } 529 appset.Finalizers = []string{"finalizer"} 530 }) 531 532 newAppSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { 533 appset.Annotations = map[string]string{ 534 "annotation-key1": "annotation-value1-updated", 535 } 536 appset.Labels = map[string]string{ 537 "label-key1": "label-value1-updated", 538 } 539 appset.Finalizers = []string{"finalizer-updated"} 540 }) 541 542 t.Run("Update merge", func(t *testing.T) { 543 appServer := newTestAppSetServer(t, appSet) 544 545 updated, err := appServer.updateAppSet(t.Context(), appSet, newAppSet, true) 546 547 require.NoError(t, err) 548 assert.Equal(t, map[string]string{ 549 "annotation-key1": "annotation-value1-updated", 550 "annotation-key2": "annotation-value2", 551 }, updated.Annotations) 552 assert.Equal(t, map[string]string{ 553 "label-key1": "label-value1-updated", 554 "label-key2": "label-value2", 555 }, updated.Labels) 556 assert.Equal(t, []string{"finalizer-updated"}, updated.Finalizers) 557 }) 558 559 t.Run("Update no merge", func(t *testing.T) { 560 appServer := newTestAppSetServer(t, appSet) 561 562 updated, err := appServer.updateAppSet(t.Context(), appSet, newAppSet, false) 563 564 require.NoError(t, err) 565 assert.Equal(t, map[string]string{ 566 "annotation-key1": "annotation-value1-updated", 567 }, updated.Annotations) 568 assert.Equal(t, map[string]string{ 569 "label-key1": "label-value1-updated", 570 }, updated.Labels) 571 }) 572 } 573 574 func TestUpsertAppSet(t *testing.T) { 575 name := "test" 576 ns := "external-namespace" 577 appSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { 578 appset.Annotations = map[string]string{ 579 "annotation-key1": "annotation-value1", 580 "annotation-key2": "annotation-value2", 581 } 582 appset.Labels = map[string]string{ 583 "label-key1": "label-value1", 584 "label-key2": "label-value2", 585 } 586 appset.Name = name 587 appset.Namespace = ns 588 appset.Finalizers = []string{"finalizer"} 589 }) 590 591 updatedAppSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { 592 appset.Annotations = map[string]string{ 593 "annotation-key1": "annotation-value1-updated", 594 } 595 appset.Labels = map[string]string{ 596 "label-key1": "label-value1-updated", 597 } 598 appset.Name = name 599 appset.Namespace = ns 600 appset.Finalizers = []string{"finalizer-updated"} 601 }) 602 603 t.Run("Upsert", func(t *testing.T) { 604 appServer := newTestAppSetServer(t, appSet) 605 606 updated, err := appServer.Create(t.Context(), &applicationset.ApplicationSetCreateRequest{ 607 Applicationset: updatedAppSet, 608 Upsert: true, 609 }) 610 611 require.NoError(t, err) 612 assert.Equal(t, map[string]string{ 613 "annotation-key1": "annotation-value1-updated", 614 "annotation-key2": "annotation-value2", 615 }, updated.Annotations) 616 assert.Equal(t, map[string]string{ 617 "label-key1": "label-value1-updated", 618 "label-key2": "label-value2", 619 }, updated.Labels) 620 assert.Equal(t, []string{"finalizer-updated"}, updated.Finalizers) 621 }) 622 } 623 624 func TestResourceTree(t *testing.T) { 625 appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 626 appset.Name = "AppSet1" 627 appset.Status.Resources = []appsv1.ResourceStatus{ 628 { 629 Name: "app1", 630 Kind: "Application", 631 Group: "argoproj.io", 632 Version: "v1alpha1", 633 Namespace: "default", 634 Health: &appsv1.HealthStatus{ 635 Status: health.HealthStatusHealthy, 636 Message: "OK", 637 }, 638 Status: appsv1.SyncStatusCodeSynced, 639 }, 640 } 641 }) 642 643 appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 644 appset.Name = "AppSet2" 645 }) 646 647 appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 648 appset.Name = "AppSet3" 649 }) 650 651 expectedTree := &appsv1.ApplicationSetTree{ 652 Nodes: []appsv1.ResourceNode{ 653 { 654 ResourceRef: appsv1.ResourceRef{ 655 Kind: "Application", 656 Group: "argoproj.io", 657 Version: "v1alpha1", 658 Namespace: "default", 659 Name: "app1", 660 }, 661 ParentRefs: []appsv1.ResourceRef{ 662 { 663 Kind: "ApplicationSet", 664 Group: "argoproj.io", 665 Version: "v1alpha1", 666 Namespace: "default", 667 Name: "AppSet1", 668 }, 669 }, 670 Health: &appsv1.HealthStatus{ 671 Status: health.HealthStatusHealthy, 672 Message: "OK", 673 }, 674 }, 675 }, 676 } 677 678 t.Run("ResourceTree in default namespace", func(t *testing.T) { 679 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 680 681 appsetQuery := applicationset.ApplicationSetTreeQuery{Name: "AppSet1"} 682 683 res, err := appSetServer.ResourceTree(t.Context(), &appsetQuery) 684 require.NoError(t, err) 685 assert.Equal(t, expectedTree, res) 686 }) 687 688 t.Run("ResourceTree in named namespace", func(t *testing.T) { 689 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 690 691 appsetQuery := applicationset.ApplicationSetTreeQuery{Name: "AppSet1", AppsetNamespace: testNamespace} 692 693 res, err := appSetServer.ResourceTree(t.Context(), &appsetQuery) 694 require.NoError(t, err) 695 assert.Equal(t, expectedTree, res) 696 }) 697 698 t.Run("ResourceTree in not allowed namespace", func(t *testing.T) { 699 appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3) 700 701 appsetQuery := applicationset.ApplicationSetTreeQuery{Name: "AppSet1", AppsetNamespace: "NOT-ALLOWED"} 702 703 _, err := appSetServer.ResourceTree(t.Context(), &appsetQuery) 704 assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted") 705 }) 706 } 707 708 func TestAppSet_Generate_Cluster(t *testing.T) { 709 appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { 710 appset.Name = "AppSet1" 711 appset.Spec.Template.Name = "{{name}}" 712 appset.Spec.Generators = []appsv1.ApplicationSetGenerator{ 713 { 714 Clusters: &appsv1.ClusterGenerator{}, 715 }, 716 } 717 }) 718 719 t.Run("Generate in default namespace", func(t *testing.T) { 720 appSetServer := newTestAppSetServer(t, appSet1) 721 appsetQuery := applicationset.ApplicationSetGenerateRequest{ 722 ApplicationSet: appSet1, 723 } 724 725 res, err := appSetServer.Generate(t.Context(), &appsetQuery) 726 require.NoError(t, err) 727 require.Len(t, res.Applications, 2) 728 assert.Equal(t, "fake-cluster", res.Applications[0].Name) 729 assert.Equal(t, "in-cluster", res.Applications[1].Name) 730 }) 731 732 t.Run("Generate in different namespace", func(t *testing.T) { 733 appSetServer := newTestAppSetServer(t, appSet1) 734 735 appSet1Ns := appSet1.DeepCopy() 736 appSet1Ns.Namespace = "external-namespace" 737 appsetQuery := applicationset.ApplicationSetGenerateRequest{ApplicationSet: appSet1Ns} 738 739 res, err := appSetServer.Generate(t.Context(), &appsetQuery) 740 require.NoError(t, err) 741 require.Len(t, res.Applications, 2) 742 assert.Equal(t, "fake-cluster", res.Applications[0].Name) 743 assert.Equal(t, "in-cluster", res.Applications[1].Name) 744 }) 745 746 t.Run("Generate in not allowed namespace", func(t *testing.T) { 747 appSetServer := newTestAppSetServer(t, appSet1) 748 749 appSet1Ns := appSet1.DeepCopy() 750 appSet1Ns.Namespace = "NOT-ALLOWED" 751 752 appsetQuery := applicationset.ApplicationSetGenerateRequest{ApplicationSet: appSet1Ns} 753 754 _, err := appSetServer.Generate(t.Context(), &appsetQuery) 755 assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted") 756 }) 757 }