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  }