github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/applicationset_test.go (about) 1 // Copyright 2021 ArgoCD Operator Developers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package argocd 16 17 import ( 18 "context" 19 "os" 20 "sort" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/stretchr/testify/assert" 25 appsv1 "k8s.io/api/apps/v1" 26 corev1 "k8s.io/api/core/v1" 27 v1 "k8s.io/api/core/v1" 28 rbacv1 "k8s.io/api/rbac/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 logf "sigs.k8s.io/controller-runtime/pkg/log" 34 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" 37 38 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 39 "github.com/argoproj-labs/argocd-operator/common" 40 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 41 ) 42 43 func applicationSetDefaultVolumeMounts() []corev1.VolumeMount { 44 repoMounts := repoServerDefaultVolumeMounts() 45 ignoredMounts := map[string]bool{ 46 "plugins": true, 47 "argocd-repo-server-tls": true, 48 common.ArgoCDRedisServerTLSSecretName: true, 49 } 50 mounts := make([]corev1.VolumeMount, len(repoMounts)-len(ignoredMounts), len(repoMounts)-len(ignoredMounts)) 51 j := 0 52 for _, mount := range repoMounts { 53 if !ignoredMounts[mount.Name] { 54 mounts[j] = mount 55 j += 1 56 } 57 } 58 return mounts 59 } 60 61 func applicationSetDefaultVolumes() []corev1.Volume { 62 repoVolumes := repoServerDefaultVolumes() 63 ignoredVolumes := map[string]bool{ 64 "var-files": true, 65 "plugins": true, 66 "argocd-repo-server-tls": true, 67 common.ArgoCDRedisServerTLSSecretName: true, 68 } 69 volumes := make([]corev1.Volume, len(repoVolumes)-len(ignoredVolumes), len(repoVolumes)-len(ignoredVolumes)) 70 j := 0 71 for _, volume := range repoVolumes { 72 if !ignoredVolumes[volume.Name] { 73 volumes[j] = volume 74 j += 1 75 } 76 } 77 return volumes 78 } 79 80 func TestReconcileApplicationSet_CreateDeployments(t *testing.T) { 81 logf.SetLogger(ZapLogger(true)) 82 a := makeTestArgoCD() 83 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{} 84 85 resObjs := []client.Object{a} 86 subresObjs := []client.Object{a} 87 runtimeObjs := []runtime.Object{} 88 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 89 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 90 r := makeTestReconciler(cl, sch) 91 92 sa := corev1.ServiceAccount{} 93 94 assert.NoError(t, r.reconcileApplicationSetDeployment(a, &sa)) 95 96 deployment := &appsv1.Deployment{} 97 assert.NoError(t, r.Client.Get( 98 context.TODO(), 99 types.NamespacedName{ 100 Name: "argocd-applicationset-controller", 101 Namespace: a.Namespace, 102 }, 103 deployment)) 104 105 // Ensure the created Deployment has the expected properties 106 checkExpectedDeploymentValues(t, r, deployment, &sa, a) 107 } 108 109 func checkExpectedDeploymentValues(t *testing.T, r *ReconcileArgoCD, deployment *appsv1.Deployment, sa *corev1.ServiceAccount, a *argoproj.ArgoCD) { 110 assert.Equal(t, deployment.Spec.Template.Spec.ServiceAccountName, sa.ObjectMeta.Name) 111 appsetAssertExpectedLabels(t, &deployment.ObjectMeta) 112 113 want := []corev1.Container{r.applicationSetContainer(a, false)} 114 115 if diff := cmp.Diff(want, deployment.Spec.Template.Spec.Containers); diff != "" { 116 t.Fatalf("failed to reconcile applicationset-controller deployment containers:\n%s", diff) 117 } 118 119 volumes := []corev1.Volume{ 120 { 121 Name: "ssh-known-hosts", 122 VolumeSource: corev1.VolumeSource{ 123 ConfigMap: &corev1.ConfigMapVolumeSource{ 124 LocalObjectReference: corev1.LocalObjectReference{ 125 Name: common.ArgoCDKnownHostsConfigMapName, 126 }, 127 }, 128 }, 129 }, 130 { 131 Name: "tls-certs", 132 VolumeSource: corev1.VolumeSource{ 133 ConfigMap: &corev1.ConfigMapVolumeSource{ 134 LocalObjectReference: corev1.LocalObjectReference{ 135 Name: common.ArgoCDTLSCertsConfigMapName, 136 }, 137 }, 138 }, 139 }, 140 { 141 Name: "gpg-keys", 142 VolumeSource: corev1.VolumeSource{ 143 ConfigMap: &corev1.ConfigMapVolumeSource{ 144 LocalObjectReference: corev1.LocalObjectReference{ 145 Name: common.ArgoCDGPGKeysConfigMapName, 146 }, 147 }, 148 }, 149 }, 150 { 151 Name: "gpg-keyring", 152 VolumeSource: corev1.VolumeSource{ 153 EmptyDir: &corev1.EmptyDirVolumeSource{}, 154 }, 155 }, 156 { 157 Name: "tmp", 158 VolumeSource: corev1.VolumeSource{ 159 EmptyDir: &corev1.EmptyDirVolumeSource{}, 160 }, 161 }, 162 } 163 164 if a.Spec.ApplicationSet.SCMRootCAConfigMap != "" && argoutil.IsObjectFound(r.Client, a.Namespace, common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName, a) { 165 volumes = append(volumes, corev1.Volume{ 166 Name: "appset-gitlab-scm-tls-cert", 167 VolumeSource: corev1.VolumeSource{ 168 ConfigMap: &corev1.ConfigMapVolumeSource{ 169 LocalObjectReference: corev1.LocalObjectReference{ 170 Name: common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName, 171 }, 172 }, 173 }, 174 }) 175 } 176 177 if diff := cmp.Diff(volumes, deployment.Spec.Template.Spec.Volumes); diff != "" { 178 t.Fatalf("failed to reconcile applicationset-controller deployment volumes:\n%s", diff) 179 } 180 181 expectedSelector := &metav1.LabelSelector{ 182 MatchLabels: map[string]string{ 183 common.ArgoCDKeyName: deployment.Name, 184 }, 185 } 186 187 if diff := cmp.Diff(expectedSelector, deployment.Spec.Selector); diff != "" { 188 t.Fatalf("failed to reconcile applicationset-controller label selector:\n%s", diff) 189 } 190 } 191 192 func TestReconcileApplicationSetProxyConfiguration(t *testing.T) { 193 logf.SetLogger(ZapLogger(true)) 194 195 // Proxy Env vars 196 setProxyEnvVars(t) 197 198 a := makeTestArgoCD() 199 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{} 200 201 resObjs := []client.Object{a} 202 subresObjs := []client.Object{a} 203 runtimeObjs := []runtime.Object{} 204 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 205 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 206 r := makeTestReconciler(cl, sch) 207 208 sa := corev1.ServiceAccount{} 209 210 r.reconcileApplicationSetDeployment(a, &sa) 211 212 want := []corev1.EnvVar{ 213 { 214 Name: "HTTPS_PROXY", 215 Value: "https://example.com", 216 }, 217 { 218 Name: "HTTP_PROXY", 219 Value: "http://example.com", 220 }, 221 { 222 Name: "NAMESPACE", 223 ValueFrom: &corev1.EnvVarSource{ 224 FieldRef: &corev1.ObjectFieldSelector{ 225 FieldPath: "metadata.namespace", 226 }, 227 }, 228 }, 229 { 230 Name: "NO_PROXY", 231 Value: ".cluster.local", 232 }, 233 } 234 235 deployment := &appsv1.Deployment{} 236 237 // reconcile ApplicationSets 238 r.Client.Get( 239 context.TODO(), 240 types.NamespacedName{ 241 Name: "argocd-applicationset-controller", 242 Namespace: a.Namespace, 243 }, 244 deployment) 245 246 if diff := cmp.Diff(want, deployment.Spec.Template.Spec.Containers[0].Env); diff != "" { 247 t.Fatalf("failed to reconcile applicationset-controller deployment containers:\n%s", diff) 248 } 249 250 } 251 252 func TestReconcileApplicationSet_UpdateExistingDeployments(t *testing.T) { 253 logf.SetLogger(ZapLogger(true)) 254 a := makeTestArgoCD() 255 256 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{} 257 258 existingDeployment := &appsv1.Deployment{ 259 ObjectMeta: metav1.ObjectMeta{ 260 Name: a.Name + "-applicationset-controller", 261 Namespace: a.Namespace, 262 }, 263 Spec: appsv1.DeploymentSpec{ 264 Template: corev1.PodTemplateSpec{ 265 Spec: corev1.PodSpec{ 266 Containers: []corev1.Container{ 267 { 268 Name: "fake-container", 269 }, 270 }, 271 }, 272 }, 273 }, 274 } 275 276 resObjs := []client.Object{a, existingDeployment} 277 subresObjs := []client.Object{a, existingDeployment} 278 runtimeObjs := []runtime.Object{} 279 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 280 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 281 r := makeTestReconciler(cl, sch) 282 283 sa := corev1.ServiceAccount{} 284 285 assert.NoError(t, r.reconcileApplicationSetDeployment(a, &sa)) 286 287 deployment := &appsv1.Deployment{} 288 assert.NoError(t, r.Client.Get( 289 context.TODO(), 290 types.NamespacedName{ 291 Name: "argocd-applicationset-controller", 292 Namespace: a.Namespace, 293 }, 294 deployment)) 295 296 // Ensure the updated Deployment has the expected properties 297 checkExpectedDeploymentValues(t, r, deployment, &sa, a) 298 299 } 300 301 func TestReconcileApplicationSet_Deployments_resourceRequirements(t *testing.T) { 302 logf.SetLogger(ZapLogger(true)) 303 a := makeTestArgoCDWithResources() 304 305 resObjs := []client.Object{a} 306 subresObjs := []client.Object{a} 307 runtimeObjs := []runtime.Object{} 308 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 309 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 310 r := makeTestReconciler(cl, sch) 311 312 sa := corev1.ServiceAccount{} 313 314 assert.NoError(t, r.reconcileApplicationSetDeployment(a, &sa)) 315 316 deployment := &appsv1.Deployment{} 317 assert.NoError(t, r.Client.Get( 318 context.TODO(), 319 types.NamespacedName{ 320 Name: "argocd-applicationset-controller", 321 Namespace: a.Namespace, 322 }, 323 deployment)) 324 325 assert.Equal(t, deployment.Spec.Template.Spec.ServiceAccountName, sa.ObjectMeta.Name) 326 appsetAssertExpectedLabels(t, &deployment.ObjectMeta) 327 328 containerWant := []corev1.Container{r.applicationSetContainer(a, false)} 329 330 if diff := cmp.Diff(containerWant, deployment.Spec.Template.Spec.Containers); diff != "" { 331 t.Fatalf("failed to reconcile argocd-server deployment:\n%s", diff) 332 } 333 334 volumesWant := applicationSetDefaultVolumes() 335 336 if diff := cmp.Diff(volumesWant, deployment.Spec.Template.Spec.Volumes); diff != "" { 337 t.Fatalf("failed to reconcile argocd-server deployment:\n%s", diff) 338 } 339 } 340 341 func TestReconcileApplicationSet_Deployments_SpecOverride(t *testing.T) { 342 logf.SetLogger(ZapLogger(true)) 343 344 tests := []struct { 345 name string 346 appSetField *argoproj.ArgoCDApplicationSet 347 envVars map[string]string 348 expectedContainerImage string 349 }{ 350 { 351 name: "unspecified fields should use default", 352 appSetField: &argoproj.ArgoCDApplicationSet{}, 353 expectedContainerImage: argoutil.CombineImageTag(common.ArgoCDDefaultArgoImage, common.ArgoCDDefaultArgoVersion), 354 }, 355 { 356 name: "ensure that sha hashes are formatted correctly", 357 appSetField: &argoproj.ArgoCDApplicationSet{ 358 Image: "custom-image", 359 Version: "sha256:b835999eb5cf75d01a2678cd971095926d9c2566c9ffe746d04b83a6a0a2849f", 360 }, 361 expectedContainerImage: "custom-image@sha256:b835999eb5cf75d01a2678cd971095926d9c2566c9ffe746d04b83a6a0a2849f", 362 }, 363 { 364 name: "custom image should properly substitute", 365 appSetField: &argoproj.ArgoCDApplicationSet{ 366 Image: "custom-image", 367 Version: "custom-version", 368 }, 369 expectedContainerImage: "custom-image:custom-version", 370 }, 371 { 372 name: "verify env var substitution overrides default", 373 appSetField: &argoproj.ArgoCDApplicationSet{}, 374 envVars: map[string]string{common.ArgoCDImageEnvName: "custom-env-image"}, 375 expectedContainerImage: "custom-env-image", 376 }, 377 378 { 379 name: "env var should not override spec fields", 380 appSetField: &argoproj.ArgoCDApplicationSet{ 381 Image: "custom-image", 382 Version: "custom-version", 383 }, 384 envVars: map[string]string{common.ArgoCDImageEnvName: "custom-env-image"}, 385 expectedContainerImage: "custom-image:custom-version", 386 }, 387 { 388 name: "ensure scm tls cert mount is present", 389 appSetField: &argoproj.ArgoCDApplicationSet{ 390 SCMRootCAConfigMap: "test-scm-tls-mount", 391 }, 392 envVars: map[string]string{common.ArgoCDImageEnvName: "custom-env-image"}, 393 expectedContainerImage: "custom-env-image", 394 }, 395 } 396 397 for _, test := range tests { 398 t.Run(test.name, func(t *testing.T) { 399 400 for testEnvName, testEnvValue := range test.envVars { 401 t.Setenv(testEnvName, testEnvValue) 402 } 403 404 a := makeTestArgoCD() 405 resObjs := []client.Object{a} 406 subresObjs := []client.Object{a} 407 runtimeObjs := []runtime.Object{} 408 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 409 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 410 r := makeTestReconciler(cl, sch) 411 cm := newConfigMapWithName(getCAConfigMapName(a), a) 412 r.Client.Create(context.Background(), cm, &client.CreateOptions{}) 413 414 a.Spec.ApplicationSet = test.appSetField 415 416 sa := corev1.ServiceAccount{} 417 assert.NoError(t, r.reconcileApplicationSetDeployment(a, &sa)) 418 419 deployment := &appsv1.Deployment{} 420 assert.NoError(t, r.Client.Get( 421 context.TODO(), 422 types.NamespacedName{ 423 Name: "argocd-applicationset-controller", 424 Namespace: a.Namespace, 425 }, 426 deployment)) 427 428 specImage := deployment.Spec.Template.Spec.Containers[0].Image 429 assert.Equal(t, test.expectedContainerImage, specImage) 430 checkExpectedDeploymentValues(t, r, deployment, &sa, a) 431 }) 432 } 433 434 } 435 436 func TestReconcileApplicationSet_Deployments_Command(t *testing.T) { 437 logf.SetLogger(ZapLogger(true)) 438 439 tests := []struct { 440 name string 441 argocdSpec argoproj.ArgoCDSpec 442 expectedCmd []string 443 notExpectedCmd []string 444 }{ 445 { 446 name: "Appset in any namespaces without scm provider list", 447 argocdSpec: argoproj.ArgoCDSpec{ 448 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 449 SourceNamespaces: []string{"foo", "bar"}, 450 }, 451 SourceNamespaces: []string{"foo", "bar"}, 452 }, 453 expectedCmd: []string{"--applicationset-namespaces", "foo,bar", "--enable-scm-providers=false"}, 454 }, 455 { 456 name: "with SCM provider list", 457 argocdSpec: argoproj.ArgoCDSpec{ 458 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 459 SourceNamespaces: []string{"foo"}, 460 SCMProviders: []string{"github.com"}, 461 }, 462 SourceNamespaces: []string{"foo", "bar"}, 463 }, 464 expectedCmd: []string{"--applicationset-namespaces", "foo", "--allowed-scm-providers", "github.com"}, 465 }, 466 { 467 name: "Appsets namespaces without Apps namespaces", 468 argocdSpec: argoproj.ArgoCDSpec{ 469 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 470 SourceNamespaces: []string{"foo"}, 471 SCMProviders: []string{"github.com"}, 472 }, 473 SourceNamespaces: []string{}, 474 }, 475 expectedCmd: []string{"--allowed-scm-providers", "github.com"}, 476 notExpectedCmd: []string{"--applicationset-namespaces", "foo"}, 477 }, 478 } 479 480 for _, test := range tests { 481 t.Run(test.name, func(t *testing.T) { 482 483 a := makeTestArgoCD() 484 ns1 := v1.Namespace{ 485 ObjectMeta: metav1.ObjectMeta{ 486 Name: "foo", 487 }, 488 } 489 ns2 := v1.Namespace{ 490 ObjectMeta: metav1.ObjectMeta{ 491 Name: "bar", 492 }, 493 } 494 resObjs := []client.Object{a, &ns1, &ns2} 495 subresObjs := []client.Object{a} 496 runtimeObjs := []runtime.Object{} 497 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 498 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 499 r := makeTestReconciler(cl, sch) 500 cm := newConfigMapWithName(getCAConfigMapName(a), a) 501 r.Client.Create(context.Background(), cm, &client.CreateOptions{}) 502 503 a.Spec = test.argocdSpec 504 505 sa := corev1.ServiceAccount{} 506 assert.NoError(t, r.reconcileApplicationSetDeployment(a, &sa)) 507 508 deployment := &appsv1.Deployment{} 509 assert.NoError(t, r.Client.Get( 510 context.TODO(), 511 types.NamespacedName{ 512 Name: "argocd-applicationset-controller", 513 Namespace: a.Namespace, 514 }, 515 deployment)) 516 517 cmds := deployment.Spec.Template.Spec.Containers[0].Command 518 for _, c := range test.expectedCmd { 519 assert.True(t, contains(cmds, c)) 520 } 521 for _, c := range test.notExpectedCmd { 522 assert.False(t, contains(cmds, c)) 523 } 524 }) 525 } 526 } 527 528 func TestReconcileApplicationSet_ServiceAccount(t *testing.T) { 529 logf.SetLogger(ZapLogger(true)) 530 a := makeTestArgoCD() 531 resObjs := []client.Object{a} 532 subresObjs := []client.Object{a} 533 runtimeObjs := []runtime.Object{} 534 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 535 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 536 r := makeTestReconciler(cl, sch) 537 538 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{ 539 Enabled: boolPtr(true), 540 } 541 542 retSa, err := r.reconcileApplicationSetServiceAccount(a) 543 assert.NoError(t, err) 544 545 sa := &corev1.ServiceAccount{} 546 assert.NoError(t, r.Client.Get( 547 context.TODO(), 548 types.NamespacedName{ 549 Name: "argocd-applicationset-controller", 550 Namespace: a.Namespace, 551 }, 552 sa)) 553 554 assert.Equal(t, sa.Name, retSa.Name) 555 556 appsetAssertExpectedLabels(t, &sa.ObjectMeta) 557 } 558 559 // Test creation/cleanup of applicationset-controller clusterrole & clusterrolebinding 560 func TestReconcileApplicationSet_ClusterRBACCreationAndCleanup(t *testing.T) { 561 logf.SetLogger(ZapLogger(true)) 562 a := makeTestArgoCD() 563 564 resName := "argocd-argocd-argocd-applicationset-controller" 565 566 resObjs := []client.Object{a} 567 subresObjs := []client.Object{a} 568 runtimeObjs := []runtime.Object{} 569 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 570 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 571 r := makeTestReconciler(cl, sch) 572 573 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{ 574 Enabled: boolPtr(true), 575 } 576 577 sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "sa-name"}} 578 579 // test: ArgoCD is not cluster-scoped, resources shouldn't be created 580 role, err := r.reconcileApplicationSetClusterRole(a) 581 assert.NoError(t, err) 582 err = r.reconcileApplicationSetClusterRoleBinding(a, role, sa) 583 assert.NoError(t, err) 584 585 // clusterrole should not be created 586 cr := &rbacv1.ClusterRole{} 587 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName}, cr) 588 assert.Error(t, err) 589 assert.True(t, apierrors.IsNotFound(err)) 590 591 // clusterrolebinding should not be created 592 crb := &rbacv1.ClusterRoleBinding{} 593 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName}, crb) 594 assert.Error(t, err) 595 assert.True(t, apierrors.IsNotFound(err)) 596 597 // test: make ArgoCD cluster-scoped, resources should be created 598 os.Setenv("ARGOCD_CLUSTER_CONFIG_NAMESPACES", a.Namespace) 599 600 role, err = r.reconcileApplicationSetClusterRole(a) 601 assert.NoError(t, err) 602 err = r.reconcileApplicationSetClusterRoleBinding(a, role, sa) 603 assert.NoError(t, err) 604 605 // clusterrole should be created 606 cr = &rbacv1.ClusterRole{} 607 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName}, cr) 608 assert.NoError(t, err) 609 610 // clusterrolebinding should be created 611 crb = &rbacv1.ClusterRoleBinding{} 612 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName}, crb) 613 assert.NoError(t, err) 614 assert.Equal(t, crb.RoleRef.Name, cr.Name) 615 assert.Equal(t, crb.Subjects[0].Name, sa.Name) 616 617 // test: make ArgoCD namespaced-scope, existing resources should be deleted 618 os.Setenv("ARGOCD_CLUSTER_CONFIG_NAMESPACES", "") 619 role, err = r.reconcileApplicationSetClusterRole(a) 620 assert.NoError(t, err) 621 err = r.reconcileApplicationSetClusterRoleBinding(a, role, sa) 622 assert.NoError(t, err) 623 624 // clusterrole should not exists 625 cr = &rbacv1.ClusterRole{} 626 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName}, cr) 627 assert.Error(t, err) 628 assert.True(t, apierrors.IsNotFound(err)) 629 630 // clusterrolebinding should not exists 631 crb = &rbacv1.ClusterRoleBinding{} 632 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName}, crb) 633 assert.Error(t, err) 634 assert.True(t, apierrors.IsNotFound(err)) 635 } 636 637 // Test creation/cleanup of applicationset-controller role & rolebinding in source namespaces 638 // Appset resources are only created if target source ns is subset of apps source namespaces 639 func TestReconcileApplicationSet_SourceNamespacesRBACCreation(t *testing.T) { 640 logf.SetLogger(ZapLogger(true)) 641 642 tests := []struct { 643 name string 644 argoCDSpec argoproj.ArgoCDSpec 645 expectErr bool 646 existInNs []string 647 notExistInNs []string 648 }{ 649 { 650 name: "No appset & app source namespaces", // no resources should be created 651 argoCDSpec: argoproj.ArgoCDSpec{ 652 ApplicationSet: nil, 653 SourceNamespaces: []string(nil), 654 }, 655 expectErr: false, 656 }, 657 { 658 name: "appset source ns not subset of app source ns", // resources shouldn't be created in allowed namespaces 659 argoCDSpec: argoproj.ArgoCDSpec{ 660 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 661 SourceNamespaces: []string{"foo", "bar"}, 662 }, 663 SourceNamespaces: []string(nil), 664 }, 665 expectErr: false, 666 existInNs: []string{}, 667 notExistInNs: []string{"foo", "bar"}, 668 }, 669 { 670 name: "appset source ns subset of app source ns ", // resources should be created is all appset ns 671 argoCDSpec: argoproj.ArgoCDSpec{ 672 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 673 SourceNamespaces: []string{"foo", "bar"}, 674 }, 675 SourceNamespaces: []string{"foo", "bar"}, 676 }, 677 expectErr: false, 678 existInNs: []string{"foo", "bar"}, 679 notExistInNs: []string{}, 680 }, 681 { 682 name: "appset source ns partial subset of app source ns ", // resources should be created only in ns part of app source ns 683 argoCDSpec: argoproj.ArgoCDSpec{ 684 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 685 SourceNamespaces: []string{"foo", "bar"}, 686 }, 687 SourceNamespaces: []string{"foo"}, 688 }, 689 expectErr: false, 690 existInNs: []string{"foo"}, 691 notExistInNs: []string{"bar"}, 692 }, 693 } 694 695 for _, test := range tests { 696 t.Run(test.name, func(t *testing.T) { 697 698 a := makeTestArgoCD() 699 resObjs := []client.Object{a} 700 subresObjs := []client.Object{a} 701 runtimeObjs := []runtime.Object{} 702 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 703 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 704 r := makeTestReconciler(cl, sch) 705 a.Spec = test.argoCDSpec 706 707 for _, ns := range append(test.existInNs, test.notExistInNs...) { 708 createNamespace(r, ns, "") 709 } 710 711 err := r.reconcileApplicationSetSourceNamespacesResources(a) 712 if test.expectErr { 713 assert.Error(t, err) 714 } 715 716 // resources for applicationset-controller should be created in target ns 717 for _, ns := range test.existInNs { 718 resName := getResourceNameForApplicationSetSourceNamespaces(a) 719 720 role := &rbacv1.Role{} 721 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns}, role) 722 assert.NoError(t, err) 723 724 roleBinding := &rbacv1.RoleBinding{} 725 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns}, roleBinding) 726 assert.NoError(t, err) 727 } 728 729 // appset tracker label should be added on the target namespace 730 for _, ns := range test.existInNs { 731 namespace := &v1.Namespace{} 732 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: ns}, namespace) 733 assert.NoError(t, err) 734 val, found := namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel] 735 assert.True(t, found) 736 assert.Equal(t, a.Namespace, val) 737 } 738 739 // resources for applicationset-controller shouldn't be created in target ns 740 for _, ns := range test.notExistInNs { 741 resName := getResourceNameForApplicationSetSourceNamespaces(a) 742 743 role := &rbacv1.Role{} 744 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns}, role) 745 assert.Error(t, err) 746 assert.True(t, apierrors.IsNotFound(err)) 747 748 roleBinding := &rbacv1.RoleBinding{} 749 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns}, roleBinding) 750 assert.Error(t, err) 751 assert.True(t, apierrors.IsNotFound(err)) 752 } 753 754 // appset tracker label shouldn't be added on the target namespace 755 for _, ns := range test.notExistInNs { 756 namespace := &v1.Namespace{} 757 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: ns}, namespace) 758 assert.NoError(t, err) 759 _, found := namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel] 760 assert.False(t, found) 761 } 762 763 }) 764 } 765 } 766 767 func TestReconcileApplicationSet_Role(t *testing.T) { 768 logf.SetLogger(ZapLogger(true)) 769 a := makeTestArgoCD() 770 771 resObjs := []client.Object{a} 772 subresObjs := []client.Object{a} 773 runtimeObjs := []runtime.Object{} 774 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 775 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 776 r := makeTestReconciler(cl, sch) 777 778 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{ 779 Enabled: boolPtr(true), 780 } 781 782 roleRet, err := r.reconcileApplicationSetRole(a) 783 assert.NoError(t, err) 784 785 role := &rbacv1.Role{} 786 assert.NoError(t, r.Client.Get( 787 context.TODO(), 788 types.NamespacedName{ 789 Name: "argocd-applicationset-controller", 790 Namespace: a.Namespace, 791 }, 792 role)) 793 794 assert.Equal(t, roleRet.Name, role.Name) 795 appsetAssertExpectedLabels(t, &role.ObjectMeta) 796 797 expectedResources := []string{ 798 "deployments", 799 "secrets", 800 "configmaps", 801 "events", 802 "applicationsets/status", 803 "applications", 804 "applicationsets", 805 "appprojects", 806 "applicationsets/finalizers", 807 "leases", 808 } 809 810 foundResources := []string{} 811 812 for _, rule := range role.Rules { 813 for _, resource := range rule.Resources { 814 foundResources = append(foundResources, resource) 815 } 816 } 817 818 sort.Strings(expectedResources) 819 sort.Strings(foundResources) 820 821 assert.Equal(t, expectedResources, foundResources) 822 } 823 824 func TestReconcileApplicationSet_RoleBinding(t *testing.T) { 825 logf.SetLogger(ZapLogger(true)) 826 a := makeTestArgoCD() 827 828 resObjs := []client.Object{a} 829 subresObjs := []client.Object{a} 830 runtimeObjs := []runtime.Object{} 831 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 832 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 833 r := makeTestReconciler(cl, sch) 834 835 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{ 836 Enabled: boolPtr(true), 837 } 838 839 role := &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Name: "role-name"}} 840 sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "sa-name"}} 841 842 err := r.reconcileApplicationSetRoleBinding(a, role, sa) 843 assert.NoError(t, err) 844 845 roleBinding := &rbacv1.RoleBinding{} 846 assert.NoError(t, r.Client.Get( 847 context.TODO(), 848 types.NamespacedName{ 849 Name: "argocd-applicationset-controller", 850 Namespace: a.Namespace, 851 }, 852 roleBinding)) 853 854 appsetAssertExpectedLabels(t, &roleBinding.ObjectMeta) 855 856 assert.Equal(t, roleBinding.RoleRef.Name, role.Name) 857 assert.Equal(t, roleBinding.Subjects[0].Name, sa.Name) 858 859 } 860 861 func appsetAssertExpectedLabels(t *testing.T, meta *metav1.ObjectMeta) { 862 assert.Equal(t, meta.Labels["app.kubernetes.io/name"], "argocd-applicationset-controller") 863 assert.Equal(t, meta.Labels["app.kubernetes.io/part-of"], "argocd-applicationset") 864 assert.Equal(t, meta.Labels["app.kubernetes.io/component"], "controller") 865 } 866 867 func setProxyEnvVars(t *testing.T) { 868 t.Setenv("HTTPS_PROXY", "https://example.com") 869 t.Setenv("HTTP_PROXY", "http://example.com") 870 t.Setenv("NO_PROXY", ".cluster.local") 871 } 872 873 func TestReconcileApplicationSet_Service(t *testing.T) { 874 logf.SetLogger(ZapLogger(true)) 875 a := makeTestArgoCD() 876 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{} 877 878 resObjs := []client.Object{a} 879 subresObjs := []client.Object{a} 880 runtimeObjs := []runtime.Object{} 881 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 882 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 883 r := makeTestReconciler(cl, sch) 884 885 s := newServiceWithSuffix(common.ApplicationSetServiceNameSuffix, common.ApplicationSetServiceNameSuffix, a) 886 887 assert.NoError(t, r.reconcileApplicationSetService(a)) 888 assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Namespace: s.Namespace, Name: s.Name}, s)) 889 } 890 891 func TestArgoCDApplicationSetCommand(t *testing.T) { 892 a := makeTestArgoCD() 893 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{} 894 895 resObjs := []client.Object{a} 896 subresObjs := []client.Object{a} 897 runtimeObjs := []runtime.Object{} 898 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 899 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 900 r := makeTestReconciler(cl, sch) 901 902 baseCommand := []string{ 903 "entrypoint.sh", 904 "argocd-applicationset-controller", 905 "--argocd-repo-server", 906 "argocd-repo-server.argocd.svc.cluster.local:8081", 907 "--loglevel", 908 "info", 909 } 910 911 // When a single command argument is passed 912 a.Spec.ApplicationSet.ExtraCommandArgs = []string{ 913 "--foo", 914 "bar", 915 } 916 917 deployment := &appsv1.Deployment{} 918 assert.NoError(t, r.reconcileApplicationSetController(a)) 919 920 assert.NoError(t, r.Client.Get( 921 context.TODO(), 922 types.NamespacedName{ 923 Name: "argocd-applicationset-controller", 924 Namespace: a.Namespace, 925 }, 926 deployment)) 927 928 cmd := append(baseCommand, "--foo", "bar") 929 assert.Equal(t, cmd, deployment.Spec.Template.Spec.Containers[0].Command) 930 931 // When multiple command arguments are passed 932 a.Spec.ApplicationSet.ExtraCommandArgs = []string{ 933 "--foo", 934 "bar", 935 "--ping", 936 "pong", 937 "test", 938 } 939 940 assert.NoError(t, r.reconcileApplicationSetController(a)) 941 assert.NoError(t, r.Client.Get( 942 context.TODO(), 943 types.NamespacedName{ 944 Name: "argocd-applicationset-controller", 945 Namespace: a.Namespace, 946 }, 947 deployment)) 948 949 cmd = append(cmd, "--ping", "pong", "test") 950 assert.Equal(t, cmd, deployment.Spec.Template.Spec.Containers[0].Command) 951 952 // When one of the ExtraCommandArgs already exists in cmd with same or different value 953 a.Spec.ApplicationSet.ExtraCommandArgs = []string{ 954 "--argocd-repo-server", 955 "foo.scv.cluster.local:6379", 956 } 957 958 assert.NoError(t, r.reconcileApplicationSetController(a)) 959 assert.NoError(t, r.Client.Get( 960 context.TODO(), 961 types.NamespacedName{ 962 Name: "argocd-applicationset-controller", 963 Namespace: a.Namespace, 964 }, 965 deployment)) 966 967 assert.Equal(t, baseCommand, deployment.Spec.Template.Spec.Containers[0].Command) 968 969 // Remove all the command arguments that were added. 970 a.Spec.ApplicationSet.ExtraCommandArgs = []string{} 971 972 assert.NoError(t, r.reconcileApplicationSetController(a)) 973 assert.NoError(t, r.Client.Get( 974 context.TODO(), 975 types.NamespacedName{ 976 Name: "argocd-applicationset-controller", 977 Namespace: a.Namespace, 978 }, 979 deployment)) 980 981 assert.Equal(t, baseCommand, deployment.Spec.Template.Spec.Containers[0].Command) 982 } 983 984 func TestArgoCDApplicationSetEnv(t *testing.T) { 985 a := makeTestArgoCD() 986 a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{} 987 988 resObjs := []client.Object{a} 989 subresObjs := []client.Object{a} 990 runtimeObjs := []runtime.Object{} 991 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 992 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 993 r := makeTestReconciler(cl, sch) 994 995 defaultEnv := []corev1.EnvVar{ 996 { 997 Name: "NAMESPACE", 998 ValueFrom: &corev1.EnvVarSource{ 999 FieldRef: &corev1.ObjectFieldSelector{ 1000 APIVersion: "", 1001 FieldPath: "metadata.namespace", 1002 }, 1003 }, 1004 }, 1005 } 1006 1007 // Pass an environment variable using Argo CD CR. 1008 customEnv := []corev1.EnvVar{ 1009 { 1010 Name: "foo", 1011 Value: "bar", 1012 }, 1013 } 1014 a.Spec.ApplicationSet.Env = customEnv 1015 1016 deployment := &appsv1.Deployment{} 1017 assert.NoError(t, r.reconcileApplicationSetController(a)) 1018 1019 assert.NoError(t, r.Client.Get( 1020 context.TODO(), 1021 types.NamespacedName{ 1022 Name: "argocd-applicationset-controller", 1023 Namespace: a.Namespace, 1024 }, 1025 deployment)) 1026 1027 expectedEnv := append(defaultEnv, customEnv...) 1028 assert.Equal(t, expectedEnv, deployment.Spec.Template.Spec.Containers[0].Env) 1029 1030 // Remove all the env vars that were added. 1031 a.Spec.ApplicationSet.Env = []corev1.EnvVar{} 1032 1033 assert.NoError(t, r.reconcileApplicationSetController(a)) 1034 assert.NoError(t, r.Client.Get( 1035 context.TODO(), 1036 types.NamespacedName{ 1037 Name: "argocd-applicationset-controller", 1038 Namespace: a.Namespace, 1039 }, 1040 deployment)) 1041 1042 assert.Equal(t, defaultEnv, deployment.Spec.Template.Spec.Containers[0].Env) 1043 } 1044 1045 func TestArgoCDApplicationSet_getApplicationSetSourceNamespaces(t *testing.T) { 1046 logf.SetLogger(ZapLogger(true)) 1047 1048 tests := []struct { 1049 name string 1050 appSetField *argoproj.ArgoCDApplicationSet 1051 expected []string 1052 }{ 1053 { 1054 name: "Appsets not enabled", 1055 appSetField: nil, 1056 expected: []string(nil), 1057 }, 1058 { 1059 name: "No appset source namespaces", 1060 appSetField: &argoproj.ArgoCDApplicationSet{ 1061 Enabled: boolPtr(true), 1062 }, 1063 expected: []string(nil), 1064 }, 1065 { 1066 name: "Appset source namespaces", 1067 appSetField: &argoproj.ArgoCDApplicationSet{ 1068 SourceNamespaces: []string{"foo", "bar"}, 1069 }, 1070 expected: []string{"foo", "bar"}, 1071 }, 1072 } 1073 1074 for _, test := range tests { 1075 t.Run(test.name, func(t *testing.T) { 1076 1077 a := makeTestArgoCD() 1078 resObjs := []client.Object{a} 1079 subresObjs := []client.Object{a} 1080 runtimeObjs := []runtime.Object{} 1081 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 1082 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 1083 r := makeTestReconciler(cl, sch) 1084 cm := newConfigMapWithName(getCAConfigMapName(a), a) 1085 r.Client.Create(context.Background(), cm, &client.CreateOptions{}) 1086 1087 a.Spec.ApplicationSet = test.appSetField 1088 1089 actual := r.getApplicationSetSourceNamespaces(a) 1090 assert.Equal(t, test.expected, actual) 1091 }) 1092 } 1093 } 1094 1095 func TestArgoCDApplicationSet_setManagedApplicationSetSourceNamespaces(t *testing.T) { 1096 a := makeTestArgoCD() 1097 ns1 := v1.Namespace{ 1098 ObjectMeta: metav1.ObjectMeta{ 1099 Name: "test-namespace-1", 1100 Labels: map[string]string{ 1101 common.ArgoCDApplicationSetManagedByClusterArgoCDLabel: testNamespace, 1102 }, 1103 }, 1104 } 1105 ns2 := v1.Namespace{ 1106 ObjectMeta: metav1.ObjectMeta{ 1107 Name: "test-namespace-2", 1108 }, 1109 } 1110 1111 resObjs := []client.Object{a, &ns1, &ns2} 1112 subresObjs := []client.Object{a} 1113 runtimeObjs := []runtime.Object{} 1114 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 1115 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 1116 r := makeTestReconciler(cl, sch) 1117 1118 err := r.setManagedApplicationSetSourceNamespaces(a) 1119 assert.NoError(t, err) 1120 1121 assert.Equal(t, 1, len(r.ManagedApplicationSetSourceNamespaces)) 1122 assert.Contains(t, r.ManagedApplicationSetSourceNamespaces, "test-namespace-1") 1123 } 1124 1125 func TestArgoCDApplicationSet_removeUnmanagedApplicationSetSourceNamespaceResources(t *testing.T) { 1126 ns1 := "foo" 1127 ns2 := "bar" 1128 a := makeTestArgoCD() 1129 a.Spec = argoproj.ArgoCDSpec{ 1130 SourceNamespaces: []string{ns1, ns2}, 1131 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 1132 SourceNamespaces: []string{ns1, ns2}, 1133 }, 1134 } 1135 1136 resObjs := []client.Object{a} 1137 subresObjs := []client.Object{a} 1138 runtimeObjs := []runtime.Object{} 1139 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 1140 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 1141 r := makeTestReconciler(cl, sch) 1142 1143 createNamespace(r, ns1, "") 1144 createNamespace(r, ns2, "") 1145 1146 // create resources 1147 err := r.reconcileApplicationSetSourceNamespacesResources(a) 1148 assert.NoError(t, err) 1149 1150 // remove appset ns 1151 a.Spec = argoproj.ArgoCDSpec{ 1152 SourceNamespaces: []string{ns2}, 1153 ApplicationSet: &argoproj.ArgoCDApplicationSet{ 1154 SourceNamespaces: []string{ns1, ns2}, 1155 }, 1156 } 1157 1158 // clean up unmanaged namespaces resources 1159 err = r.removeUnmanagedApplicationSetSourceNamespaceResources(a) 1160 assert.NoError(t, err) 1161 1162 // resources shouldn't exist in ns1 1163 resName := getResourceNameForApplicationSetSourceNamespaces(a) 1164 1165 role := &rbacv1.Role{} 1166 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns1}, role) 1167 assert.Error(t, err) 1168 assert.True(t, apierrors.IsNotFound(err)) 1169 1170 roleBinding := &rbacv1.RoleBinding{} 1171 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns1}, roleBinding) 1172 assert.Error(t, err) 1173 assert.True(t, apierrors.IsNotFound(err)) 1174 1175 // appset tracking label should be removed 1176 namespace := &v1.Namespace{} 1177 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: ns1}, namespace) 1178 assert.NoError(t, err) 1179 _, found := namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel] 1180 assert.False(t, found) 1181 1182 // resources in ns2 shouldn't be touched 1183 1184 role = &rbacv1.Role{} 1185 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns2}, role) 1186 assert.NoError(t, err) 1187 1188 roleBinding = &rbacv1.RoleBinding{} 1189 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: resName, Namespace: ns2}, roleBinding) 1190 assert.NoError(t, err) 1191 1192 namespace = &v1.Namespace{} 1193 err = r.Client.Get(context.TODO(), cntrlClient.ObjectKey{Name: ns2}, namespace) 1194 assert.NoError(t, err) 1195 val, found := namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel] 1196 assert.True(t, found) 1197 assert.Equal(t, a.Namespace, val) 1198 }