github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/configmap_test.go (about) 1 // Copyright 2020 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 "fmt" 20 "reflect" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/stretchr/testify/assert" 25 "gopkg.in/yaml.v2" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/types" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 logf "sigs.k8s.io/controller-runtime/pkg/log" 32 "sigs.k8s.io/controller-runtime/pkg/reconcile" 33 34 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 35 "github.com/argoproj-labs/argocd-operator/common" 36 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 37 ) 38 39 var _ reconcile.Reconciler = &ReconcileArgoCD{} 40 41 func TestReconcileArgoCD_reconcileTLSCerts(t *testing.T) { 42 logf.SetLogger(ZapLogger(true)) 43 a := makeTestArgoCD(initialCerts(t, "root-ca.example.com")) 44 45 resObjs := []client.Object{a} 46 subresObjs := []client.Object{a} 47 runtimeObjs := []runtime.Object{} 48 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 49 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 50 r := makeTestReconciler(cl, sch) 51 52 assert.NoError(t, r.reconcileTLSCerts(a)) 53 54 configMap := &corev1.ConfigMap{} 55 assert.NoError(t, r.Client.Get( 56 context.TODO(), 57 types.NamespacedName{ 58 Name: common.ArgoCDTLSCertsConfigMapName, 59 Namespace: a.Namespace, 60 }, 61 configMap)) 62 63 want := []string{"root-ca.example.com"} 64 if k := stringMapKeys(configMap.Data); !reflect.DeepEqual(want, k) { 65 t.Fatalf("got %#v, want %#v\n", k, want) 66 } 67 } 68 69 func TestReconcileArgoCD_reconcileTLSCerts_configMapUpdate(t *testing.T) { 70 logf.SetLogger(ZapLogger(true)) 71 a := makeTestArgoCD(initialCerts(t, "root-ca.example.com")) 72 73 resObjs := []client.Object{a} 74 subresObjs := []client.Object{a} 75 runtimeObjs := []runtime.Object{} 76 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 77 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 78 r := makeTestReconciler(cl, sch) 79 80 assert.NoError(t, r.reconcileTLSCerts(a)) 81 82 configMap := &corev1.ConfigMap{} 83 assert.NoError(t, r.Client.Get( 84 context.TODO(), 85 types.NamespacedName{ 86 Name: common.ArgoCDTLSCertsConfigMapName, 87 Namespace: a.Namespace, 88 }, 89 configMap)) 90 91 want := []string{"root-ca.example.com"} 92 if k := stringMapKeys(configMap.Data); !reflect.DeepEqual(want, k) { 93 t.Fatalf("got %#v, want %#v\n", k, want) 94 } 95 96 // update a new cert in argocd-tls-certs-cm 97 testPEM := generateEncodedPEM(t, "example.com") 98 99 configMap.Data["example.com"] = string(testPEM) 100 assert.NoError(t, r.Client.Update(context.TODO(), configMap)) 101 102 // verify that a new reconciliation does not remove example.com from 103 // argocd-tls-certs-cm 104 assert.NoError(t, r.reconcileTLSCerts(a)) 105 106 want = []string{"example.com", "root-ca.example.com"} 107 if k := stringMapKeys(configMap.Data); !reflect.DeepEqual(want, k) { 108 t.Fatalf("got %#v, want %#v\n", k, want) 109 } 110 } 111 112 func TestReconcileArgoCD_reconcileTLSCerts_withInitialCertsUpdate(t *testing.T) { 113 logf.SetLogger(ZapLogger(true)) 114 a := makeTestArgoCD() 115 116 resObjs := []client.Object{a} 117 subresObjs := []client.Object{a} 118 runtimeObjs := []runtime.Object{} 119 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 120 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 121 r := makeTestReconciler(cl, sch) 122 123 assert.NoError(t, r.reconcileTLSCerts(a)) 124 125 a = makeTestArgoCD(initialCerts(t, "testing.example.com")) 126 assert.NoError(t, r.reconcileTLSCerts(a)) 127 128 configMap := &corev1.ConfigMap{} 129 assert.NoError(t, r.Client.Get( 130 context.TODO(), 131 types.NamespacedName{ 132 Name: common.ArgoCDTLSCertsConfigMapName, 133 Namespace: a.Namespace, 134 }, 135 configMap)) 136 137 // Any certs added to .spec.tls.intialCerts of Argo CD CR after the cluster creation 138 // should not affect the argocd-tls-certs-cm configmap. 139 want := []string{} 140 if k := stringMapKeys(configMap.Data); !reflect.DeepEqual(want, k) { 141 t.Fatalf("got %#v, want %#v\n", k, want) 142 } 143 } 144 145 func TestReconcileArgoCD_reconcileArgoConfigMap(t *testing.T) { 146 logf.SetLogger(ZapLogger(true)) 147 148 defaultConfigMapData := map[string]string{ 149 "application.instanceLabelKey": common.ArgoCDDefaultApplicationInstanceLabelKey, 150 "application.resourceTrackingMethod": argoproj.ResourceTrackingMethodLabel.String(), 151 "admin.enabled": "true", 152 "configManagementPlugins": "", 153 "dex.config": "", 154 "ga.anonymizeusers": "false", 155 "ga.trackingid": "", 156 "help.chatText": "", 157 "help.chatUrl": "", 158 "kustomize.buildOptions": "", 159 "oidc.config": "", 160 "repositories": "", 161 "repository.credentials": "", 162 "resource.inclusions": "", 163 "resource.exclusions": "", 164 "statusbadge.enabled": "false", 165 "url": "https://argocd-server", 166 "users.anonymous.enabled": "false", 167 } 168 169 cmdTests := []struct { 170 name string 171 opts []argoCDOpt 172 dataDiff map[string]string 173 }{ 174 { 175 "defaults", 176 []argoCDOpt{}, 177 map[string]string{}, 178 }, 179 { 180 "with-banner", 181 []argoCDOpt{func(a *argoproj.ArgoCD) { 182 a.Spec.Banner = &argoproj.Banner{ 183 Content: "Custom Styles - Banners", 184 } 185 }}, 186 map[string]string{ 187 "users.anonymous.enabled": "false", 188 "ui.bannercontent": "Custom Styles - Banners", 189 }, 190 }, 191 { 192 "with-banner-and-url", 193 []argoCDOpt{func(a *argoproj.ArgoCD) { 194 a.Spec.Banner = &argoproj.Banner{ 195 Content: "Custom Styles - Banners", 196 URL: "https://argo-cd.readthedocs.io/en/stable/operator-manual/custom-styles/#banners", 197 } 198 }}, 199 map[string]string{ 200 "ui.bannercontent": "Custom Styles - Banners", 201 "ui.bannerurl": "https://argo-cd.readthedocs.io/en/stable/operator-manual/custom-styles/#banners", 202 }, 203 }, 204 } 205 206 for _, tt := range cmdTests { 207 a := makeTestArgoCD(tt.opts...) 208 a.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 209 Provider: argoproj.SSOProviderTypeDex, 210 } 211 212 resObjs := []client.Object{a} 213 subresObjs := []client.Object{a} 214 runtimeObjs := []runtime.Object{} 215 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 216 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 217 r := makeTestReconciler(cl, sch) 218 219 err := r.reconcileArgoConfigMap(a) 220 assert.NoError(t, err) 221 222 cm := &corev1.ConfigMap{} 223 err = r.Client.Get(context.TODO(), types.NamespacedName{ 224 Name: common.ArgoCDConfigMapName, 225 Namespace: testNamespace, 226 }, cm) 227 assert.NoError(t, err) 228 229 want := merge(defaultConfigMapData, tt.dataDiff) 230 231 if diff := cmp.Diff(want, cm.Data); diff != "" { 232 t.Fatalf("reconcileArgoConfigMap (%s) failed:\n%s", tt.name, diff) 233 } 234 } 235 } 236 237 func TestReconcileArgoCD_reconcileEmptyArgoConfigMap(t *testing.T) { 238 logf.SetLogger(ZapLogger(true)) 239 a := makeTestArgoCD() 240 241 resObjs := []client.Object{a} 242 subresObjs := []client.Object{a} 243 runtimeObjs := []runtime.Object{} 244 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 245 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 246 r := makeTestReconciler(cl, sch) 247 248 // An empty Argo CD Configmap 249 emptyArgoConfigmap := &corev1.ConfigMap{ 250 ObjectMeta: metav1.ObjectMeta{ 251 Name: common.ArgoCDConfigMapName, 252 Namespace: a.Namespace, 253 }, 254 } 255 256 err := r.Client.Create(context.TODO(), emptyArgoConfigmap) 257 assert.NoError(t, err) 258 259 err = r.reconcileArgoConfigMap(a) 260 assert.NoError(t, err) 261 262 cm := &corev1.ConfigMap{} 263 err = r.Client.Get(context.TODO(), types.NamespacedName{ 264 Name: common.ArgoCDConfigMapName, 265 Namespace: testNamespace, 266 }, cm) 267 assert.NoError(t, err) 268 } 269 270 func TestReconcileArgoCDCM_withRepoCredentials(t *testing.T) { 271 logf.SetLogger(ZapLogger(true)) 272 a := makeTestArgoCD() 273 a.Spec.RepositoryCredentials = ` 274 - url: https://github.com/test/gitops.git 275 passwordSecret: 276 name: test 277 key: password 278 usernameSecret: 279 name: test 280 key: username` 281 282 cm := &corev1.ConfigMap{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Name: common.ArgoCDConfigMapName, 285 Namespace: testNamespace, 286 }, 287 Data: map[string]string{ 288 "application.instanceLabelKey": "mycompany.com/appname", 289 "admin.enabled": "true", 290 }, 291 } 292 293 resObjs := []client.Object{a, cm} 294 subresObjs := []client.Object{a} 295 runtimeObjs := []runtime.Object{} 296 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 297 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 298 r := makeTestReconciler(cl, sch) 299 300 err := r.reconcileArgoConfigMap(a) 301 assert.NoError(t, err) 302 303 err = r.Client.Get(context.TODO(), types.NamespacedName{ 304 Name: common.ArgoCDConfigMapName, 305 Namespace: testNamespace, 306 }, cm) 307 assert.NoError(t, err) 308 309 if got := cm.Data[common.ArgoCDKeyRepositoryCredentials]; got != a.Spec.RepositoryCredentials { 310 t.Fatalf("reconcileArgoConfigMap failed: got %s, want %s", got, a.Spec.RepositoryCredentials) 311 } 312 } 313 314 func TestReconcileArgoCD_reconcileArgoConfigMap_withDisableAdmin(t *testing.T) { 315 logf.SetLogger(ZapLogger(true)) 316 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 317 a.Spec.DisableAdmin = true 318 }) 319 320 resObjs := []client.Object{a} 321 subresObjs := []client.Object{a} 322 runtimeObjs := []runtime.Object{} 323 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 324 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 325 r := makeTestReconciler(cl, sch) 326 327 err := r.reconcileArgoConfigMap(a) 328 assert.NoError(t, err) 329 330 cm := &corev1.ConfigMap{} 331 err = r.Client.Get(context.TODO(), types.NamespacedName{ 332 Name: common.ArgoCDConfigMapName, 333 Namespace: testNamespace, 334 }, cm) 335 assert.NoError(t, err) 336 337 if c := cm.Data["admin.enabled"]; c != "false" { 338 t.Fatalf("reconcileArgoConfigMap failed got %q, want %q", c, "false") 339 } 340 } 341 342 func TestReconcileArgoCD_reconcileArgoConfigMap_withDexConnector(t *testing.T) { 343 logf.SetLogger(ZapLogger(true)) 344 345 getSampleDexConfig := func(t *testing.T) []byte { 346 t.Helper() 347 348 type expiry struct { 349 IdTokens string `yaml:"idTokens"` 350 SigningKeys string `yaml:"signingKeys"` 351 } 352 353 dexCfg := map[string]interface{}{ 354 "expiry": expiry{ 355 IdTokens: "1hr", 356 SigningKeys: "12hr", 357 }, 358 } 359 360 dexCfgBytes, err := yaml.Marshal(dexCfg) 361 assert.NoError(t, err) 362 return dexCfgBytes 363 } 364 365 tests := []struct { 366 name string 367 updateCrSpecFunc func(cr *argoproj.ArgoCD) 368 }{ 369 { 370 name: "dex config using .spec.sso.provider=dex + .spec.sso.dex", 371 updateCrSpecFunc: func(cr *argoproj.ArgoCD) { 372 cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 373 Provider: argoproj.SSOProviderTypeDex, 374 Dex: &argoproj.ArgoCDDexSpec{ 375 OpenShiftOAuth: true, 376 }, 377 } 378 }, 379 }, 380 { 381 name: "update .dex.config and verify that the dex connector is not overwritten", 382 updateCrSpecFunc: func(cr *argoproj.ArgoCD) { 383 cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 384 Provider: argoproj.SSOProviderTypeDex, 385 Dex: &argoproj.ArgoCDDexSpec{ 386 OpenShiftOAuth: true, 387 Config: string(getSampleDexConfig(t)), 388 }, 389 } 390 }, 391 }, 392 } 393 394 for _, test := range tests { 395 t.Run(test.name, func(t *testing.T) { 396 sa := &corev1.ServiceAccount{ 397 TypeMeta: metav1.TypeMeta{Kind: "ServiceAccount", APIVersion: "v1"}, 398 ObjectMeta: metav1.ObjectMeta{Name: "argocd-argocd-dex-server", Namespace: "argocd"}, 399 Secrets: []corev1.ObjectReference{{ 400 Name: "token", 401 }}, 402 } 403 404 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 405 a.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 406 Provider: argoproj.SSOProviderTypeDex, 407 Dex: &argoproj.ArgoCDDexSpec{ 408 OpenShiftOAuth: false, 409 }, 410 } 411 }) 412 413 secret := argoutil.NewSecretWithName(a, "token") 414 415 resObjs := []client.Object{a, sa, secret} 416 subresObjs := []client.Object{a} 417 runtimeObjs := []runtime.Object{} 418 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 419 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 420 r := makeTestReconciler(cl, sch) 421 422 if test.updateCrSpecFunc != nil { 423 test.updateCrSpecFunc(a) 424 } 425 err := r.reconcileArgoConfigMap(a) 426 assert.NoError(t, err) 427 428 cm := &corev1.ConfigMap{} 429 err = r.Client.Get(context.TODO(), types.NamespacedName{ 430 Name: common.ArgoCDConfigMapName, 431 Namespace: testNamespace, 432 }, cm) 433 assert.NoError(t, err) 434 435 dex, ok := cm.Data["dex.config"] 436 if !ok { 437 t.Fatal("reconcileArgoConfigMap with dex failed") 438 } 439 440 m := make(map[string]interface{}) 441 err = yaml.Unmarshal([]byte(dex), &m) 442 assert.NoError(t, err, fmt.Sprintf("failed to unmarshal %s", dex)) 443 444 connectors, ok := m["connectors"] 445 if !ok { 446 t.Fatal("no connectors found in dex.config") 447 } 448 dexConnector := connectors.([]interface{})[0].(map[interface{}]interface{}) 449 config := dexConnector["config"] 450 assert.Equal(t, config.(map[interface{}]interface{})["clientID"], "system:serviceaccount:argocd:argocd-argocd-dex-server") 451 452 // verify that the dex config in the CR matches the config from the argocd-cm 453 if a.Spec.SSO.Dex.Config != "" { 454 expectedCfg := make(map[string]interface{}) 455 expectedCfgStr, err := r.getOpenShiftDexConfig(a) 456 assert.NoError(t, err) 457 458 err = yaml.Unmarshal([]byte(expectedCfgStr), expectedCfg) 459 assert.NoError(t, err, fmt.Sprintf("failed to unmarshal %s", dex)) 460 assert.Equal(t, expectedCfg, m) 461 } 462 }) 463 } 464 465 } 466 467 func TestReconcileArgoCD_reconcileArgoConfigMap_withDexDisabled(t *testing.T) { 468 logf.SetLogger(ZapLogger(true)) 469 470 tests := []struct { 471 name string 472 argoCD *argoproj.ArgoCD 473 }{ 474 { 475 name: "dex disabled by removing .spec.sso", 476 argoCD: makeTestArgoCD(func(cr *argoproj.ArgoCD) { 477 cr.Spec.SSO = nil 478 }), 479 }, 480 { 481 name: "dex disabled by switching provider", 482 argoCD: makeTestArgoCD(func(cr *argoproj.ArgoCD) { 483 cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 484 Provider: argoproj.SSOProviderTypeKeycloak, 485 } 486 }), 487 }, 488 } 489 490 for _, test := range tests { 491 t.Run(test.name, func(t *testing.T) { 492 493 resObjs := []client.Object{test.argoCD} 494 subresObjs := []client.Object{test.argoCD} 495 runtimeObjs := []runtime.Object{} 496 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 497 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 498 r := makeTestReconciler(cl, sch) 499 500 err := r.reconcileArgoConfigMap(test.argoCD) 501 assert.NoError(t, err) 502 503 cm := &corev1.ConfigMap{} 504 err = r.Client.Get(context.TODO(), types.NamespacedName{ 505 Name: common.ArgoCDConfigMapName, 506 Namespace: testNamespace, 507 }, cm) 508 assert.NoError(t, err) 509 510 if c, ok := cm.Data["dex.config"]; ok { 511 t.Fatalf("reconcileArgoConfigMap failed, dex.config = %q", c) 512 } 513 }) 514 } 515 } 516 517 // When dex is enabled, dexConfig should be present in argocd-cm, when disabled, it should be removed 518 func TestReconcileArgoCD_reconcileArgoConfigMap_dexConfigDeletedwhenDexDisabled(t *testing.T) { 519 logf.SetLogger(ZapLogger(true)) 520 521 tests := []struct { 522 name string 523 updateCrFunc func(cr *argoproj.ArgoCD) 524 argoCD *argoproj.ArgoCD 525 wantConfigRemoved bool 526 }{ 527 { 528 name: "dex disabled by removing .spec.sso.provider", 529 updateCrFunc: func(cr *argoproj.ArgoCD) { 530 cr.Spec.SSO = nil 531 }, 532 argoCD: makeTestArgoCD(func(cr *argoproj.ArgoCD) { 533 cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 534 Provider: argoproj.SSOProviderTypeDex, 535 Dex: &argoproj.ArgoCDDexSpec{ 536 Config: "test-dex-config", 537 }, 538 } 539 }), 540 wantConfigRemoved: true, 541 }, 542 { 543 name: "dex disabled by switching provider", 544 updateCrFunc: func(cr *argoproj.ArgoCD) { 545 cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 546 Provider: argoproj.SSOProviderTypeKeycloak, 547 } 548 }, 549 argoCD: makeTestArgoCD(func(cr *argoproj.ArgoCD) { 550 cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{ 551 Provider: argoproj.SSOProviderTypeDex, 552 Dex: &argoproj.ArgoCDDexSpec{ 553 OpenShiftOAuth: true, 554 }, 555 } 556 }), 557 wantConfigRemoved: true, 558 }, 559 } 560 561 for _, test := range tests { 562 t.Run(test.name, func(t *testing.T) { 563 sa := &corev1.ServiceAccount{ 564 TypeMeta: metav1.TypeMeta{Kind: "ServiceAccount", APIVersion: "v1"}, 565 ObjectMeta: metav1.ObjectMeta{Name: "argocd-argocd-dex-server", Namespace: "argocd"}, 566 Secrets: []corev1.ObjectReference{{ 567 Name: "token", 568 }}, 569 } 570 secret := argoutil.NewSecretWithName(test.argoCD, "token") 571 572 resObjs := []client.Object{test.argoCD, sa, secret} 573 subresObjs := []client.Object{test.argoCD} 574 runtimeObjs := []runtime.Object{} 575 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 576 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 577 r := makeTestReconciler(cl, sch) 578 579 err := r.reconcileArgoConfigMap(test.argoCD) 580 assert.NoError(t, err) 581 582 cm := &corev1.ConfigMap{} 583 err = r.Client.Get(context.TODO(), types.NamespacedName{ 584 Name: common.ArgoCDConfigMapName, 585 Namespace: testNamespace, 586 }, cm) 587 assert.NoError(t, err) 588 589 if _, ok := cm.Data["dex.config"]; !ok { 590 t.Fatalf("reconcileArgoConfigMap failed,could not find dexConfig") 591 } 592 593 if test.updateCrFunc != nil { 594 test.updateCrFunc(test.argoCD) 595 } 596 597 err = r.reconcileDexConfiguration(cm, test.argoCD) 598 assert.NoError(t, err) 599 600 err = r.Client.Get(context.TODO(), types.NamespacedName{ 601 Name: common.ArgoCDConfigMapName, 602 Namespace: testNamespace, 603 }, cm) 604 assert.NoError(t, err) 605 606 if c, ok := cm.Data["dex.config"]; ok && c != "" { 607 if test.wantConfigRemoved { 608 t.Fatalf("reconcileArgoConfigMap failed, dex.config = %q", c) 609 } 610 } 611 }) 612 } 613 } 614 615 func TestReconcileArgoCD_reconcileArgoConfigMap_withKustomizeVersions(t *testing.T) { 616 logf.SetLogger(ZapLogger(true)) 617 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 618 kv := argoproj.KustomizeVersionSpec{ 619 Version: "v4.1.0", 620 Path: "/path/to/kustomize-4.1", 621 } 622 var kvs []argoproj.KustomizeVersionSpec 623 kvs = append(kvs, kv) 624 a.Spec.KustomizeVersions = kvs 625 }) 626 627 resObjs := []client.Object{a} 628 subresObjs := []client.Object{a} 629 runtimeObjs := []runtime.Object{} 630 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 631 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 632 r := makeTestReconciler(cl, sch) 633 634 err := r.reconcileArgoConfigMap(a) 635 assert.NoError(t, err) 636 637 cm := &corev1.ConfigMap{} 638 err = r.Client.Get(context.TODO(), types.NamespacedName{ 639 Name: common.ArgoCDConfigMapName, 640 Namespace: testNamespace, 641 }, cm) 642 assert.NoError(t, err) 643 644 if diff := cmp.Diff(cm.Data["kustomize.version.v4.1.0"], "/path/to/kustomize-4.1"); diff != "" { 645 t.Fatalf("failed to reconcile configmap:\n%s", diff) 646 } 647 } 648 649 func TestReconcileArgoCD_reconcileGPGKeysConfigMap(t *testing.T) { 650 logf.SetLogger(ZapLogger(true)) 651 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 652 a.Spec.DisableAdmin = true 653 }) 654 655 resObjs := []client.Object{a} 656 subresObjs := []client.Object{a} 657 runtimeObjs := []runtime.Object{} 658 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 659 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 660 r := makeTestReconciler(cl, sch) 661 662 err := r.reconcileGPGKeysConfigMap(a) 663 assert.NoError(t, err) 664 665 cm := &corev1.ConfigMap{} 666 err = r.Client.Get(context.TODO(), types.NamespacedName{ 667 Name: common.ArgoCDGPGKeysConfigMapName, 668 Namespace: testNamespace, 669 }, cm) 670 assert.NoError(t, err) 671 // Currently the gpg keys configmap is empty 672 } 673 674 func TestReconcileArgoCD_reconcileArgoConfigMap_withResourceTrackingMethod(t *testing.T) { 675 logf.SetLogger(ZapLogger(true)) 676 a := makeTestArgoCD() 677 678 resObjs := []client.Object{a} 679 subresObjs := []client.Object{a} 680 runtimeObjs := []runtime.Object{} 681 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 682 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 683 r := makeTestReconciler(cl, sch) 684 685 err := r.reconcileArgoConfigMap(a) 686 assert.NoError(t, err) 687 688 cm := &corev1.ConfigMap{} 689 690 t.Run("Check default tracking method", func(t *testing.T) { 691 err = r.Client.Get(context.TODO(), types.NamespacedName{ 692 Name: common.ArgoCDConfigMapName, 693 Namespace: testNamespace, 694 }, cm) 695 assert.NoError(t, err) 696 697 rtm, ok := cm.Data[common.ArgoCDKeyResourceTrackingMethod] 698 assert.Equal(t, argoproj.ResourceTrackingMethodLabel.String(), rtm) 699 assert.True(t, ok) 700 }) 701 702 t.Run("Tracking method label", func(t *testing.T) { 703 err = r.Client.Get(context.TODO(), types.NamespacedName{ 704 Name: common.ArgoCDConfigMapName, 705 Namespace: testNamespace, 706 }, cm) 707 assert.NoError(t, err) 708 709 rtm, ok := cm.Data[common.ArgoCDKeyResourceTrackingMethod] 710 assert.Equal(t, argoproj.ResourceTrackingMethodLabel.String(), rtm) 711 assert.True(t, ok) 712 }) 713 714 t.Run("Set tracking method to annotation+label", func(t *testing.T) { 715 a.Spec.ResourceTrackingMethod = argoproj.ResourceTrackingMethodAnnotationAndLabel.String() 716 err = r.reconcileArgoConfigMap(a) 717 assert.NoError(t, err) 718 719 err = r.Client.Get(context.TODO(), types.NamespacedName{ 720 Name: common.ArgoCDConfigMapName, 721 Namespace: testNamespace, 722 }, cm) 723 assert.NoError(t, err) 724 725 rtm, ok := cm.Data[common.ArgoCDKeyResourceTrackingMethod] 726 assert.True(t, ok) 727 assert.Equal(t, argoproj.ResourceTrackingMethodAnnotationAndLabel.String(), rtm) 728 }) 729 730 t.Run("Set tracking method to annotation", func(t *testing.T) { 731 a.Spec.ResourceTrackingMethod = argoproj.ResourceTrackingMethodAnnotation.String() 732 err = r.reconcileArgoConfigMap(a) 733 assert.NoError(t, err) 734 735 err = r.Client.Get(context.TODO(), types.NamespacedName{ 736 Name: common.ArgoCDConfigMapName, 737 Namespace: testNamespace, 738 }, cm) 739 assert.NoError(t, err) 740 741 rtm, ok := cm.Data[common.ArgoCDKeyResourceTrackingMethod] 742 assert.True(t, ok) 743 assert.Equal(t, argoproj.ResourceTrackingMethodAnnotation.String(), rtm) 744 }) 745 746 // Invalid value sets the default "label" 747 t.Run("Set tracking method to invalid value", func(t *testing.T) { 748 a.Spec.ResourceTrackingMethod = "anotaions" 749 err = r.reconcileArgoConfigMap(a) 750 assert.NoError(t, err) 751 752 err = r.Client.Get(context.TODO(), types.NamespacedName{ 753 Name: common.ArgoCDConfigMapName, 754 Namespace: testNamespace, 755 }, cm) 756 assert.NoError(t, err) 757 758 rtm, ok := cm.Data[common.ArgoCDKeyResourceTrackingMethod] 759 assert.True(t, ok) 760 assert.Equal(t, argoproj.ResourceTrackingMethodLabel.String(), rtm) 761 }) 762 763 } 764 765 func TestReconcileArgoCD_reconcileArgoConfigMap_withResourceInclusions(t *testing.T) { 766 logf.SetLogger(ZapLogger(true)) 767 customizations := "testing: testing" 768 updatedCustomizations := "updated-testing: updated-testing" 769 770 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 771 a.Spec.ResourceInclusions = customizations 772 }) 773 774 resObjs := []client.Object{a} 775 subresObjs := []client.Object{a} 776 runtimeObjs := []runtime.Object{} 777 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 778 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 779 r := makeTestReconciler(cl, sch) 780 781 err := r.reconcileArgoConfigMap(a) 782 assert.NoError(t, err) 783 784 cm := &corev1.ConfigMap{} 785 err = r.Client.Get(context.TODO(), types.NamespacedName{ 786 Name: common.ArgoCDConfigMapName, 787 Namespace: testNamespace, 788 }, cm) 789 assert.NoError(t, err) 790 791 if c := cm.Data["resource.inclusions"]; c != customizations { 792 t.Fatalf("reconcileArgoConfigMap failed got %q, want %q", c, customizations) 793 } 794 795 a.Spec.ResourceInclusions = updatedCustomizations 796 err = r.reconcileArgoConfigMap(a) 797 assert.NoError(t, err) 798 799 err = r.Client.Get(context.TODO(), types.NamespacedName{ 800 Name: common.ArgoCDConfigMapName, 801 Namespace: testNamespace, 802 }, cm) 803 assert.NoError(t, err) 804 805 if c := cm.Data["resource.inclusions"]; c != updatedCustomizations { 806 t.Fatalf("reconcileArgoConfigMap failed got %q, want %q", c, updatedCustomizations) 807 } 808 809 } 810 811 func TestReconcileArgoCD_reconcileArgoConfigMap_withNewResourceCustomizations(t *testing.T) { 812 logf.SetLogger(ZapLogger(true)) 813 814 desiredIgnoreDifferenceCustomization := 815 `jqpathexpressions: 816 - a 817 - b 818 jsonpointers: 819 - a 820 - b 821 managedfieldsmanagers: 822 - a 823 - b 824 ` 825 826 health := []argoproj.ResourceHealthCheck{ 827 { 828 Group: "healthFoo", 829 Kind: "healthFoo", 830 Check: "healthFoo", 831 }, 832 { 833 Group: "healthBar", 834 Kind: "healthBar", 835 Check: "healthBar", 836 }, 837 { 838 Group: "", 839 Kind: "healthFooBar", 840 Check: "healthFooBar", 841 }, 842 } 843 actions := []argoproj.ResourceAction{ 844 { 845 Group: "actionsFoo", 846 Kind: "actionsFoo", 847 Action: "actionsFoo", 848 }, 849 { 850 Group: "actionsBar", 851 Kind: "actionsBar", 852 Action: "actionsBar", 853 }, 854 { 855 Group: "", 856 Kind: "actionsFooBar", 857 Action: "actionsFooBar", 858 }, 859 } 860 ignoreDifferences := argoproj.ResourceIgnoreDifference{ 861 All: &argoproj.IgnoreDifferenceCustomization{ 862 JqPathExpressions: []string{"a", "b"}, 863 JsonPointers: []string{"a", "b"}, 864 ManagedFieldsManagers: []string{"a", "b"}, 865 }, 866 ResourceIdentifiers: []argoproj.ResourceIdentifiers{ 867 { 868 Group: "ignoreDiffBar", 869 Kind: "ignoreDiffBar", 870 Customization: argoproj.IgnoreDifferenceCustomization{ 871 JqPathExpressions: []string{"a", "b"}, 872 JsonPointers: []string{"a", "b"}, 873 ManagedFieldsManagers: []string{"a", "b"}, 874 }, 875 }, 876 { 877 Group: "", 878 Kind: "ignoreDiffFoo", 879 Customization: argoproj.IgnoreDifferenceCustomization{ 880 JqPathExpressions: []string{"a", "b"}, 881 JsonPointers: []string{"a", "b"}, 882 ManagedFieldsManagers: []string{"a", "b"}, 883 }, 884 }, 885 }, 886 } 887 888 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 889 a.Spec.ResourceHealthChecks = health 890 a.Spec.ResourceActions = actions 891 a.Spec.ResourceIgnoreDifferences = &ignoreDifferences 892 }) 893 894 resObjs := []client.Object{a} 895 subresObjs := []client.Object{a} 896 runtimeObjs := []runtime.Object{} 897 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 898 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 899 r := makeTestReconciler(cl, sch) 900 901 err := r.reconcileArgoConfigMap(a) 902 assert.NoError(t, err) 903 904 cm := &corev1.ConfigMap{} 905 err = r.Client.Get(context.TODO(), types.NamespacedName{ 906 Name: common.ArgoCDConfigMapName, 907 Namespace: testNamespace, 908 }, cm) 909 assert.NoError(t, err) 910 911 desiredCM := make(map[string]string) 912 desiredCM["resource.customizations.health.healthFoo_healthFoo"] = "healthFoo" 913 desiredCM["resource.customizations.health.healthBar_healthBar"] = "healthBar" 914 desiredCM["resource.customizations.health.healthFooBar"] = "healthFooBar" 915 desiredCM["resource.customizations.actions.actionsFoo_actionsFoo"] = "actionsFoo" 916 desiredCM["resource.customizations.actions.actionsBar_actionsBar"] = "actionsBar" 917 desiredCM["resource.customizations.actions.actionsFooBar"] = "actionsFooBar" 918 desiredCM["resource.customizations.ignoreDifferences.all"] = desiredIgnoreDifferenceCustomization 919 desiredCM["resource.customizations.ignoreDifferences.ignoreDiffBar_ignoreDiffBar"] = desiredIgnoreDifferenceCustomization 920 desiredCM["resource.customizations.ignoreDifferences.ignoreDiffFoo"] = desiredIgnoreDifferenceCustomization 921 922 for k, v := range desiredCM { 923 if value, ok := cm.Data[k]; !ok || value != v { 924 t.Fatalf("reconcileArgoConfigMap failed got %q, want %q", value, desiredCM[k]) 925 } 926 } 927 } 928 929 func TestReconcileArgoCD_reconcileArgoConfigMap_withExtraConfig(t *testing.T) { 930 a := makeTestArgoCD() 931 932 resObjs := []client.Object{a} 933 subresObjs := []client.Object{a} 934 runtimeObjs := []runtime.Object{} 935 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 936 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 937 r := makeTestReconciler(cl, sch) 938 939 err := r.reconcileArgoConfigMap(a) 940 assert.NoError(t, err) 941 942 // Verify Argo CD configmap is created. 943 cm := &corev1.ConfigMap{} 944 err = r.Client.Get(context.TODO(), types.NamespacedName{ 945 Name: common.ArgoCDConfigMapName, 946 Namespace: testNamespace, 947 }, cm) 948 assert.NoError(t, err) 949 950 // Verify that updates to the configmap are rejected(reconciled back to default) by the operator. 951 cm.Data["ping"] = "pong" 952 err = r.Client.Update(context.TODO(), cm) 953 assert.NoError(t, err) 954 955 err = r.reconcileArgoConfigMap(a) 956 assert.NoError(t, err) 957 958 err = r.Client.Get(context.TODO(), types.NamespacedName{ 959 Name: common.ArgoCDConfigMapName, 960 Namespace: testNamespace, 961 }, cm) 962 assert.NoError(t, err) 963 964 assert.Equal(t, cm.Data["ping"], "") 965 966 // Verify that operator updates argocd-cm according to ExtraConfig. 967 a.Spec.ExtraConfig = map[string]string{ 968 "foo": "bar", 969 } 970 971 err = r.reconcileArgoConfigMap(a) 972 assert.NoError(t, err) 973 974 err = r.Client.Get(context.TODO(), types.NamespacedName{ 975 Name: common.ArgoCDConfigMapName, 976 Namespace: testNamespace, 977 }, cm) 978 assert.NoError(t, err) 979 980 assert.Equal(t, cm.Data["foo"], "bar") 981 982 // Verify that ExtraConfig overrides FirstClass entries 983 a.Spec.DisableAdmin = true 984 a.Spec.ExtraConfig["admin.enabled"] = "true" 985 986 err = r.reconcileArgoConfigMap(a) 987 assert.NoError(t, err) 988 989 err = r.Client.Get(context.TODO(), types.NamespacedName{ 990 Name: common.ArgoCDConfigMapName, 991 Namespace: testNamespace, 992 }, cm) 993 994 assert.NoError(t, err) 995 assert.Equal(t, cm.Data["admin.enabled"], "true") 996 997 // Verify that deletion of a field from ExtraConfig does not delete any existing configuration 998 // created by FirstClass citizens. 999 a.Spec.ExtraConfig = make(map[string]string, 0) 1000 1001 err = r.reconcileArgoConfigMap(a) 1002 assert.NoError(t, err) 1003 1004 err = r.Client.Get(context.TODO(), types.NamespacedName{ 1005 Name: common.ArgoCDConfigMapName, 1006 Namespace: testNamespace, 1007 }, cm) 1008 1009 assert.NoError(t, err) 1010 assert.Equal(t, cm.Data["admin.enabled"], "false") 1011 1012 } 1013 1014 func Test_reconcileRBAC(t *testing.T) { 1015 a := makeTestArgoCD() 1016 1017 resObjs := []client.Object{a} 1018 subresObjs := []client.Object{a} 1019 runtimeObjs := []runtime.Object{} 1020 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 1021 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 1022 r := makeTestReconciler(cl, sch) 1023 1024 err := r.reconcileRBAC(a) 1025 assert.NoError(t, err) 1026 1027 // Verify ArgoCD CR can be used to configure the RBAC policy matcher mode.\ 1028 matcherMode := "regex" 1029 a.Spec.RBAC.PolicyMatcherMode = &matcherMode 1030 1031 err = r.reconcileRBAC(a) 1032 assert.NoError(t, err) 1033 1034 cm := &corev1.ConfigMap{} 1035 err = r.Client.Get(context.TODO(), types.NamespacedName{ 1036 Name: common.ArgoCDRBACConfigMapName, 1037 Namespace: testNamespace, 1038 }, cm) 1039 1040 assert.NoError(t, err) 1041 assert.Equal(t, cm.Data["policy.matchMode"], matcherMode) 1042 }