github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/keycloak_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  	"fmt"
    20  	"testing"
    21  
    22  	oappsv1 "github.com/openshift/api/apps/v1"
    23  	routev1 "github.com/openshift/api/route/v1"
    24  	templatev1 "github.com/openshift/api/template/v1"
    25  	"github.com/stretchr/testify/assert"
    26  	corev1 "k8s.io/api/core/v1"
    27  	resourcev1 "k8s.io/apimachinery/pkg/api/resource"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
    33  	"github.com/argoproj-labs/argocd-operator/common"
    34  	"github.com/argoproj-labs/argocd-operator/controllers/argoutil"
    35  )
    36  
    37  var (
    38  	fakeNs             = "foo"
    39  	fakeReplicas int32 = 1
    40  	fakeVolumes        = []corev1.Volume{
    41  		{
    42  			Name: "sso-x509-https-volume",
    43  			VolumeSource: corev1.VolumeSource{
    44  				Secret: &corev1.SecretVolumeSource{
    45  					SecretName: servingCertSecretName,
    46  				},
    47  			},
    48  		},
    49  		{
    50  			Name: "service-ca",
    51  			VolumeSource: corev1.VolumeSource{
    52  				ConfigMap: &corev1.ConfigMapVolumeSource{
    53  					LocalObjectReference: corev1.LocalObjectReference{
    54  						Name: "${APPLICATION_NAME}-service-ca",
    55  					},
    56  				},
    57  			},
    58  		},
    59  		{
    60  			Name: "sso-probe-netrc-volume",
    61  			VolumeSource: corev1.VolumeSource{
    62  				EmptyDir: &corev1.EmptyDirVolumeSource{
    63  					Medium: "Memory",
    64  				},
    65  			},
    66  		},
    67  	}
    68  )
    69  
    70  func getFakeKeycloakResources() corev1.ResourceRequirements {
    71  	return corev1.ResourceRequirements{
    72  		Requests: corev1.ResourceList{
    73  			corev1.ResourceMemory: resourcev1.MustParse("1Mi"),
    74  			corev1.ResourceCPU:    resourcev1.MustParse("1m"),
    75  		},
    76  		Limits: corev1.ResourceList{
    77  			corev1.ResourceMemory: resourcev1.MustParse("128Mi"),
    78  			corev1.ResourceCPU:    resourcev1.MustParse("128m"),
    79  		},
    80  	}
    81  }
    82  
    83  func TestKeycloakContainerImage(t *testing.T) {
    84  
    85  	defer removeTemplateAPI()
    86  	tests := []struct {
    87  		name               string
    88  		setEnvVarFunc      func(*testing.T, string)
    89  		envVar             string
    90  		argoCD             *argoproj.ArgoCD
    91  		updateCrFunc       func(cr *argoproj.ArgoCD)
    92  		templateAPIFound   bool
    93  		wantContainerImage string
    94  	}{
    95  		{
    96  			name:          "no .spec.sso, no ArgoCDKeycloakImageEnvName env var set",
    97  			setEnvVarFunc: nil,
    98  			envVar:        "",
    99  			argoCD: makeArgoCD(func(cr *argoproj.ArgoCD) {
   100  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   101  					Provider: argoproj.SSOProviderTypeKeycloak,
   102  				}
   103  			}),
   104  			updateCrFunc:       nil,
   105  			templateAPIFound:   false,
   106  			wantContainerImage: "quay.io/keycloak/keycloak@sha256:64fb81886fde61dee55091e6033481fa5ccdac62ae30a4fd29b54eb5e97df6a9",
   107  		},
   108  		{
   109  			name:          "no .spec.sso, no ArgoCDKeycloakImageEnvName env var set - for OCP",
   110  			setEnvVarFunc: nil,
   111  			envVar:        "",
   112  			argoCD: makeArgoCD(func(cr *argoproj.ArgoCD) {
   113  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   114  					Provider: argoproj.SSOProviderTypeKeycloak,
   115  				}
   116  			}),
   117  			updateCrFunc:       nil,
   118  			templateAPIFound:   true,
   119  			wantContainerImage: "registry.redhat.io/rh-sso-7/sso76-openshift-rhel8@sha256:ec9f60018694dcc5d431ba47d5536b761b71cb3f66684978fe6bb74c157679ac",
   120  		},
   121  		{
   122  			name: "ArgoCDKeycloakImageEnvName env var set",
   123  			setEnvVarFunc: func(t *testing.T, s string) {
   124  				t.Setenv(common.ArgoCDKeycloakImageEnvName, s)
   125  			},
   126  			envVar: "envImage:latest",
   127  			argoCD: makeArgoCD(func(cr *argoproj.ArgoCD) {
   128  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   129  					Provider: argoproj.SSOProviderTypeKeycloak,
   130  				}
   131  			}),
   132  			updateCrFunc:       nil,
   133  			templateAPIFound:   true,
   134  			wantContainerImage: "envImage:latest",
   135  		},
   136  		{
   137  			name: "both cr.spec.sso.keycloak.Image and ArgoCDKeycloakImageEnvName are set",
   138  			setEnvVarFunc: func(t *testing.T, s string) {
   139  				t.Setenv(common.ArgoCDKeycloakImageEnvName, s)
   140  			},
   141  			envVar: "envImage:latest",
   142  			argoCD: makeArgoCD(func(cr *argoproj.ArgoCD) {
   143  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   144  					Provider: argoproj.SSOProviderTypeKeycloak,
   145  				}
   146  			}),
   147  			updateCrFunc: func(cr *argoproj.ArgoCD) {
   148  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   149  					Provider: argoproj.SSOProviderTypeKeycloak,
   150  					Keycloak: &argoproj.ArgoCDKeycloakSpec{
   151  						Image:   "crImage",
   152  						Version: "crVersion",
   153  					},
   154  				}
   155  			},
   156  			templateAPIFound:   true,
   157  			wantContainerImage: "crImage:crVersion",
   158  		},
   159  	}
   160  
   161  	for _, test := range tests {
   162  		t.Run(test.name, func(t *testing.T) {
   163  			templateAPIFound = test.templateAPIFound
   164  
   165  			if test.setEnvVarFunc != nil {
   166  				test.setEnvVarFunc(t, test.envVar)
   167  			}
   168  			if test.updateCrFunc != nil {
   169  				test.updateCrFunc(test.argoCD)
   170  			}
   171  
   172  			testImage := getKeycloakContainerImage(test.argoCD)
   173  			assert.Equal(t, test.wantContainerImage, testImage)
   174  
   175  		})
   176  	}
   177  }
   178  
   179  func TestNewKeycloakTemplateInstance(t *testing.T) {
   180  	// For OpenShift Container Platform.
   181  	templateAPIFound = true
   182  	defer removeTemplateAPI()
   183  
   184  	a := makeTestArgoCD()
   185  	a.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   186  		Provider: "keycloak",
   187  	}
   188  	tmplInstance, err := newKeycloakTemplateInstance(a)
   189  	assert.NoError(t, err)
   190  
   191  	assert.Equal(t, tmplInstance.Name, "rhsso")
   192  	assert.Equal(t, tmplInstance.Namespace, a.Namespace)
   193  }
   194  
   195  func TestNewKeycloakTemplate(t *testing.T) {
   196  	// For OpenShift Container Platform.
   197  	templateAPIFound = true
   198  	defer removeTemplateAPI()
   199  
   200  	a := makeTestArgoCD()
   201  	a.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   202  		Provider: "keycloak",
   203  	}
   204  	tmpl, err := newKeycloakTemplate(a)
   205  	assert.NoError(t, err)
   206  
   207  	assert.Equal(t, tmpl.Name, "rhsso")
   208  	assert.Equal(t, tmpl.Namespace, a.Namespace)
   209  }
   210  
   211  func TestNewKeycloakTemplate_testDeploymentConfig(t *testing.T) {
   212  	// For OpenShift Container Platform.
   213  	templateAPIFound = true
   214  	defer removeTemplateAPI()
   215  
   216  	a := makeTestArgoCD()
   217  	a.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   218  		Provider: "keycloak",
   219  	}
   220  	dc := getKeycloakDeploymentConfigTemplate(a)
   221  
   222  	assert.Equal(t, dc.Spec.Replicas, fakeReplicas)
   223  
   224  	strategy := oappsv1.DeploymentStrategy{
   225  		Type: "Recreate",
   226  		Resources: corev1.ResourceRequirements{
   227  			Requests: corev1.ResourceList{
   228  				corev1.ResourceMemory: resourcev1.MustParse("256Mi"),
   229  				corev1.ResourceCPU:    resourcev1.MustParse("250m"),
   230  			},
   231  			Limits: corev1.ResourceList{
   232  				corev1.ResourceMemory: resourcev1.MustParse("512Mi"),
   233  				corev1.ResourceCPU:    resourcev1.MustParse("500m"),
   234  			},
   235  		},
   236  	}
   237  	assert.Equal(t, dc.Spec.Strategy, strategy)
   238  
   239  	assert.Equal(t, dc.Spec.Template.ObjectMeta.Name, "${APPLICATION_NAME}")
   240  	assert.Equal(t, dc.Spec.Template.Spec.Volumes, fakeVolumes)
   241  }
   242  
   243  func TestNewKeycloakTemplate_testKeycloakContainer(t *testing.T) {
   244  	// For OpenShift Container Platform.
   245  	t.Setenv(common.ArgoCDKeycloakImageEnvName, "")
   246  	templateAPIFound = true
   247  	defer removeTemplateAPI()
   248  
   249  	a := makeTestArgoCD()
   250  	a.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   251  		Provider: "keycloak",
   252  	}
   253  	kc := getKeycloakContainer(a)
   254  	assert.Equal(t,
   255  		"registry.redhat.io/rh-sso-7/sso76-openshift-rhel8@sha256:ec9f60018694dcc5d431ba47d5536b761b71cb3f66684978fe6bb74c157679ac", kc.Image)
   256  	assert.Equal(t, corev1.PullAlways, kc.ImagePullPolicy)
   257  	assert.Equal(t, "${APPLICATION_NAME}", kc.Name)
   258  }
   259  
   260  func TestKeycloakResources(t *testing.T) {
   261  	defer removeTemplateAPI()
   262  	fR := getFakeKeycloakResources()
   263  
   264  	tests := []struct {
   265  		name          string
   266  		argoCD        *argoproj.ArgoCD
   267  		updateCrFunc  func(cr *argoproj.ArgoCD)
   268  		wantResources corev1.ResourceRequirements
   269  	}{
   270  		{
   271  			name: "default",
   272  			argoCD: makeTestArgoCD(func(cr *argoproj.ArgoCD) {
   273  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   274  					Provider: argoproj.SSOProviderTypeKeycloak,
   275  				}
   276  			}),
   277  			updateCrFunc:  nil,
   278  			wantResources: defaultKeycloakResources(),
   279  		},
   280  		{
   281  			name: "override with .spec.sso.keycloak",
   282  			argoCD: makeTestArgoCD(func(cr *argoproj.ArgoCD) {
   283  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   284  					Provider: argoproj.SSOProviderTypeKeycloak,
   285  				}
   286  			}),
   287  			updateCrFunc: func(cr *argoproj.ArgoCD) {
   288  				cr.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   289  					Keycloak: &argoproj.ArgoCDKeycloakSpec{
   290  						Resources: &fR,
   291  					},
   292  				}
   293  			},
   294  			wantResources: getFakeKeycloakResources(),
   295  		},
   296  	}
   297  
   298  	for _, test := range tests {
   299  		t.Run(test.name, func(t *testing.T) {
   300  			if test.updateCrFunc != nil {
   301  				test.updateCrFunc(test.argoCD)
   302  			}
   303  
   304  			testResources := getKeycloakContainer(test.argoCD).Resources
   305  			assert.Equal(t, test.wantResources, testResources)
   306  
   307  		})
   308  	}
   309  }
   310  
   311  func TestNewKeycloakTemplate_testConfigmap(t *testing.T) {
   312  	cm := getKeycloakConfigMapTemplate(fakeNs)
   313  	assert.Equal(t, cm.Name, "${APPLICATION_NAME}-service-ca")
   314  	assert.Equal(t, cm.Namespace, fakeNs)
   315  }
   316  
   317  func TestNewKeycloakTemplate_testService(t *testing.T) {
   318  	svc := getKeycloakServiceTemplate(fakeNs)
   319  	assert.Equal(t, svc.Name, "${APPLICATION_NAME}")
   320  	assert.Equal(t, svc.Namespace, fakeNs)
   321  	assert.Equal(t, svc.Spec.Selector, map[string]string{
   322  		"deploymentConfig": "${APPLICATION_NAME}"})
   323  }
   324  
   325  func TestNewKeycloakTemplate_testRoute(t *testing.T) {
   326  	route := getKeycloakRouteTemplate(fakeNs)
   327  	assert.Equal(t, route.Name, "${APPLICATION_NAME}")
   328  	assert.Equal(t, route.Namespace, fakeNs)
   329  	assert.Equal(t, route.Spec.To,
   330  		routev1.RouteTargetReference{Name: "${APPLICATION_NAME}"})
   331  	assert.Equal(t, route.Spec.TLS,
   332  		&routev1.TLSConfig{Termination: "reencrypt"})
   333  }
   334  
   335  func TestKeycloak_testRealmConfigCreation(t *testing.T) {
   336  	cfg := &keycloakConfig{
   337  		ArgoName:      "foo-argocd",
   338  		ArgoNamespace: "foo",
   339  		Username:      "test-user",
   340  		Password:      "test",
   341  		KeycloakURL:   "https://foo.keycloak.com",
   342  		ArgoCDURL:     "https://bar.argocd.com",
   343  	}
   344  
   345  	_, err := createRealmConfig(cfg)
   346  	assert.NoError(t, err)
   347  }
   348  
   349  func TestKeycloak_testServerCert(t *testing.T) {
   350  
   351  	a := makeTestArgoCDForKeycloak()
   352  
   353  	resObjs := []client.Object{a}
   354  	subresObjs := []client.Object{a}
   355  	runtimeObjs := []runtime.Object{}
   356  	sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install)
   357  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   358  	r := makeTestReconciler(cl, sch)
   359  
   360  	sslCertsSecret := &corev1.Secret{
   361  		ObjectMeta: metav1.ObjectMeta{
   362  			Name:      servingCertSecretName,
   363  			Namespace: a.Namespace,
   364  		},
   365  		Data: map[string][]byte{
   366  			"tls.crt": []byte("asdasfsff"),
   367  		},
   368  	}
   369  	r.Client.Create(context.TODO(), sslCertsSecret)
   370  
   371  	_, err := r.getKCServerCert(a)
   372  	assert.NoError(t, err)
   373  
   374  	sslCertsSecret.Data["tls.crt"] = nil
   375  	assert.NoError(t, r.Client.Update(context.TODO(), sslCertsSecret))
   376  
   377  	_, err = r.getKCServerCert(a)
   378  	assert.NoError(t, err)
   379  }
   380  
   381  func TestKeycloakConfigVerifyTLSForOpenShift(t *testing.T) {
   382  	tests := []struct {
   383  		name             string
   384  		argoCD           *argoproj.ArgoCD
   385  		desiredVerifyTLS bool
   386  	}{
   387  		{
   388  			name: ".spec.sso.keycloak.verifyTLS nil",
   389  			argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) {
   390  				ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   391  					Provider: argoproj.SSOProviderTypeKeycloak,
   392  				}
   393  			}),
   394  			desiredVerifyTLS: true,
   395  		},
   396  		{
   397  			name: ".spec.sso.keycloak.verifyTLS false",
   398  			argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) {
   399  				ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   400  					Provider: argoproj.SSOProviderTypeKeycloak,
   401  					Keycloak: &argoproj.ArgoCDKeycloakSpec{
   402  						VerifyTLS: boolPtr(false),
   403  					},
   404  				}
   405  			}),
   406  			desiredVerifyTLS: false,
   407  		},
   408  		{
   409  			name: ".spec.sso.keycloak.verifyTLS true",
   410  			argoCD: makeTestArgoCD(func(ac *argoproj.ArgoCD) {
   411  				ac.Spec.SSO = &argoproj.ArgoCDSSOSpec{
   412  					Provider: argoproj.SSOProviderTypeKeycloak,
   413  					Keycloak: &argoproj.ArgoCDKeycloakSpec{
   414  						VerifyTLS: boolPtr(true),
   415  					},
   416  				}
   417  			}),
   418  			desiredVerifyTLS: true,
   419  		},
   420  	}
   421  
   422  	for _, test := range tests {
   423  		t.Run(test.name, func(t *testing.T) {
   424  
   425  			resObjs := []client.Object{test.argoCD}
   426  			subresObjs := []client.Object{test.argoCD}
   427  			runtimeObjs := []runtime.Object{}
   428  			sch := makeTestReconcilerScheme(argoproj.AddToScheme, templatev1.Install, oappsv1.Install, routev1.Install)
   429  			cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   430  			r := makeTestReconciler(cl, sch)
   431  
   432  			keycloakRoute := &routev1.Route{
   433  				ObjectMeta: metav1.ObjectMeta{
   434  					Name:      defaultKeycloakIdentifier,
   435  					Namespace: test.argoCD.Namespace,
   436  				},
   437  				Spec: routev1.RouteSpec{
   438  					Host: "test-host",
   439  				},
   440  			}
   441  			r.Client.Create(context.TODO(), keycloakRoute)
   442  
   443  			argoCDRoute := &routev1.Route{
   444  				ObjectMeta: metav1.ObjectMeta{
   445  					Name:      fmt.Sprintf("%s-%s", test.argoCD.Name, "server"),
   446  					Namespace: test.argoCD.Namespace,
   447  				},
   448  				Spec: routev1.RouteSpec{
   449  					Host: "test-argocd-host",
   450  				},
   451  			}
   452  			r.Client.Create(context.TODO(), argoCDRoute)
   453  
   454  			keycloakSecret := &corev1.Secret{
   455  				ObjectMeta: metav1.ObjectMeta{
   456  					Name:      fmt.Sprintf("%s-%s", defaultKeycloakIdentifier, "secret"),
   457  					Namespace: test.argoCD.Namespace,
   458  				},
   459  				Data: map[string][]byte{"SSO_USERNAME": []byte("username"), "SSO_PASSWORD": []byte("password")},
   460  			}
   461  			r.Client.Create(context.TODO(), keycloakSecret)
   462  
   463  			sslCertsSecret := &corev1.Secret{
   464  				ObjectMeta: metav1.ObjectMeta{
   465  					Name:      servingCertSecretName,
   466  					Namespace: test.argoCD.Namespace,
   467  				},
   468  				Data: map[string][]byte{
   469  					"tls.crt": []byte("asdasfsff"),
   470  				},
   471  			}
   472  			r.Client.Create(context.TODO(), sslCertsSecret)
   473  
   474  			keyCloakConfig, err := r.prepareKeycloakConfig(test.argoCD)
   475  			assert.NoError(t, err)
   476  
   477  			assert.Equal(t, test.desiredVerifyTLS, keyCloakConfig.VerifyTLS)
   478  		})
   479  	}
   480  }
   481  
   482  func TestKeycloak_NodeLabelSelector(t *testing.T) {
   483  	a := makeTestArgoCDForKeycloak()
   484  	a.Spec.NodePlacement = &argoproj.ArgoCDNodePlacementSpec{
   485  		NodeSelector: deploymentDefaultNodeSelector(),
   486  		Tolerations:  deploymentDefaultTolerations(),
   487  	}
   488  
   489  	dc := getKeycloakDeploymentConfigTemplate(a)
   490  
   491  	nSelectors := deploymentDefaultNodeSelector()
   492  	nSelectors = argoutil.AppendStringMap(nSelectors, common.DefaultNodeSelector())
   493  	assert.Equal(t, dc.Spec.Template.Spec.NodeSelector, nSelectors)
   494  	assert.Equal(t, dc.Spec.Template.Spec.Tolerations, a.Spec.NodePlacement.Tolerations)
   495  }
   496  
   497  func removeTemplateAPI() {
   498  	templateAPIFound = false
   499  }