github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/notifications_test.go (about)

     1  package argocd
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
     9  	"github.com/google/go-cmp/cmp"
    10  	"github.com/stretchr/testify/assert"
    11  	appsv1 "k8s.io/api/apps/v1"
    12  	corev1 "k8s.io/api/core/v1"
    13  	v1 "k8s.io/api/core/v1"
    14  	rbacv1 "k8s.io/api/rbac/v1"
    15  	"k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/apimachinery/pkg/util/intstr"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    22  
    23  	"github.com/argoproj-labs/argocd-operator/api/v1alpha1"
    24  	argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
    25  	"github.com/argoproj-labs/argocd-operator/common"
    26  	"github.com/argoproj-labs/argocd-operator/controllers/argoutil"
    27  )
    28  
    29  func TestReconcileNotifications_CreateRoles(t *testing.T) {
    30  	logf.SetLogger(ZapLogger(true))
    31  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
    32  		a.Spec.Notifications.Enabled = true
    33  	})
    34  
    35  	resObjs := []client.Object{a}
    36  	subresObjs := []client.Object{a}
    37  	runtimeObjs := []runtime.Object{}
    38  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
    39  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
    40  	r := makeTestReconciler(cl, sch)
    41  
    42  	_, err := r.reconcileNotificationsRole(a)
    43  	assert.NoError(t, err)
    44  
    45  	testRole := &rbacv1.Role{}
    46  	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
    47  		Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
    48  		Namespace: a.Namespace,
    49  	}, testRole))
    50  
    51  	desiredPolicyRules := policyRuleForNotificationsController()
    52  
    53  	assert.Equal(t, desiredPolicyRules, testRole.Rules)
    54  
    55  	a.Spec.Notifications.Enabled = false
    56  	_, err = r.reconcileNotificationsRole(a)
    57  	assert.NoError(t, err)
    58  
    59  	err = r.Client.Get(context.TODO(), types.NamespacedName{
    60  		Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
    61  		Namespace: a.Namespace,
    62  	}, testRole)
    63  	assert.True(t, errors.IsNotFound(err))
    64  }
    65  
    66  func TestReconcileNotifications_CreateServiceAccount(t *testing.T) {
    67  	logf.SetLogger(ZapLogger(true))
    68  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
    69  		a.Spec.Notifications.Enabled = true
    70  	})
    71  
    72  	resObjs := []client.Object{a}
    73  	subresObjs := []client.Object{a}
    74  	runtimeObjs := []runtime.Object{}
    75  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
    76  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
    77  	r := makeTestReconciler(cl, sch)
    78  
    79  	desiredSa, err := r.reconcileNotificationsServiceAccount(a)
    80  	assert.NoError(t, err)
    81  
    82  	testSa := &corev1.ServiceAccount{}
    83  	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
    84  		Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
    85  		Namespace: a.Namespace,
    86  	}, testSa))
    87  
    88  	assert.Equal(t, testSa.Name, desiredSa.Name)
    89  
    90  	a.Spec.Notifications.Enabled = false
    91  	_, err = r.reconcileNotificationsServiceAccount(a)
    92  	assert.NoError(t, err)
    93  
    94  	err = r.Client.Get(context.TODO(), types.NamespacedName{
    95  		Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
    96  		Namespace: a.Namespace,
    97  	}, testSa)
    98  	assert.True(t, errors.IsNotFound(err))
    99  
   100  }
   101  
   102  func TestReconcileNotifications_CreateRoleBinding(t *testing.T) {
   103  	logf.SetLogger(ZapLogger(true))
   104  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   105  		a.Spec.Notifications.Enabled = true
   106  	})
   107  
   108  	resObjs := []client.Object{a}
   109  	subresObjs := []client.Object{a}
   110  	runtimeObjs := []runtime.Object{}
   111  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   112  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   113  	r := makeTestReconciler(cl, sch)
   114  
   115  	role := &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Name: "role-name"}}
   116  	sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "sa-name"}}
   117  
   118  	err := r.reconcileNotificationsRoleBinding(a, role, sa)
   119  	assert.NoError(t, err)
   120  
   121  	roleBinding := &rbacv1.RoleBinding{}
   122  	assert.NoError(t, r.Client.Get(
   123  		context.TODO(),
   124  		types.NamespacedName{
   125  			Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
   126  			Namespace: a.Namespace,
   127  		},
   128  		roleBinding))
   129  
   130  	assert.Equal(t, roleBinding.RoleRef.Name, role.Name)
   131  	assert.Equal(t, roleBinding.Subjects[0].Name, sa.Name)
   132  
   133  	a.Spec.Notifications.Enabled = false
   134  	err = r.reconcileNotificationsRoleBinding(a, role, sa)
   135  	assert.NoError(t, err)
   136  
   137  	err = r.Client.Get(context.TODO(), types.NamespacedName{
   138  		Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
   139  		Namespace: a.Namespace,
   140  	}, roleBinding)
   141  	assert.True(t, errors.IsNotFound(err))
   142  }
   143  
   144  func TestReconcileNotifications_CreateDeployments(t *testing.T) {
   145  	logf.SetLogger(ZapLogger(true))
   146  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   147  		a.Spec.Notifications.Enabled = true
   148  	})
   149  
   150  	resObjs := []client.Object{a}
   151  	subresObjs := []client.Object{a}
   152  	runtimeObjs := []runtime.Object{}
   153  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   154  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   155  	r := makeTestReconciler(cl, sch)
   156  	sa := corev1.ServiceAccount{}
   157  
   158  	assert.NoError(t, r.reconcileNotificationsDeployment(a, &sa))
   159  
   160  	deployment := &appsv1.Deployment{}
   161  	assert.NoError(t, r.Client.Get(
   162  		context.TODO(),
   163  		types.NamespacedName{
   164  			Name:      a.Name + "-notifications-controller",
   165  			Namespace: a.Namespace,
   166  		},
   167  		deployment))
   168  
   169  	// Ensure the created Deployment has the expected properties
   170  	assert.Equal(t, deployment.Spec.Template.Spec.ServiceAccountName, sa.ObjectMeta.Name)
   171  
   172  	want := []corev1.Container{{
   173  		Command:         []string{"argocd-notifications", "--loglevel", "info", "--argocd-repo-server", "argocd-repo-server.argocd.svc.cluster.local:8081"},
   174  		Image:           argoutil.CombineImageTag(common.ArgoCDDefaultArgoImage, common.ArgoCDDefaultArgoVersion),
   175  		ImagePullPolicy: corev1.PullAlways,
   176  		Name:            "argocd-notifications-controller",
   177  		SecurityContext: &corev1.SecurityContext{
   178  			AllowPrivilegeEscalation: boolPtr(false),
   179  			Capabilities: &corev1.Capabilities{
   180  				Drop: []corev1.Capability{
   181  					"ALL",
   182  				},
   183  			},
   184  		},
   185  		VolumeMounts: []corev1.VolumeMount{
   186  			{
   187  				Name:      "tls-certs",
   188  				MountPath: "/app/config/tls",
   189  			},
   190  			{
   191  				Name:      "argocd-repo-server-tls",
   192  				MountPath: "/app/config/reposerver/tls",
   193  			},
   194  		},
   195  		Resources:  corev1.ResourceRequirements{},
   196  		WorkingDir: "/app",
   197  		LivenessProbe: &corev1.Probe{
   198  			ProbeHandler: corev1.ProbeHandler{
   199  				TCPSocket: &corev1.TCPSocketAction{
   200  					Port: intstr.IntOrString{
   201  						IntVal: int32(9001),
   202  					},
   203  				},
   204  			},
   205  		},
   206  	}}
   207  
   208  	if diff := cmp.Diff(want, deployment.Spec.Template.Spec.Containers); diff != "" {
   209  		t.Fatalf("failed to reconcile notifications-controller deployment containers:\n%s", diff)
   210  	}
   211  
   212  	volumes := []corev1.Volume{
   213  		{
   214  			Name: "tls-certs",
   215  			VolumeSource: corev1.VolumeSource{
   216  				ConfigMap: &corev1.ConfigMapVolumeSource{
   217  					LocalObjectReference: corev1.LocalObjectReference{
   218  						Name: "argocd-tls-certs-cm",
   219  					},
   220  				},
   221  			},
   222  		},
   223  		{
   224  			Name: "argocd-repo-server-tls",
   225  			VolumeSource: corev1.VolumeSource{
   226  				Secret: &corev1.SecretVolumeSource{
   227  					SecretName: "argocd-repo-server-tls",
   228  					Optional:   boolPtr(true),
   229  				},
   230  			},
   231  		},
   232  	}
   233  
   234  	if diff := cmp.Diff(volumes, deployment.Spec.Template.Spec.Volumes); diff != "" {
   235  		t.Fatalf("failed to reconcile notifications-controller deployment volumes:\n%s", diff)
   236  	}
   237  
   238  	expectedSelector := &metav1.LabelSelector{
   239  		MatchLabels: map[string]string{
   240  			common.ArgoCDKeyName: deployment.Name,
   241  		},
   242  	}
   243  
   244  	if diff := cmp.Diff(expectedSelector, deployment.Spec.Selector); diff != "" {
   245  		t.Fatalf("failed to reconcile notifications-controller label selector:\n%s", diff)
   246  	}
   247  
   248  	a.Spec.Notifications.Enabled = false
   249  	err := r.reconcileNotificationsDeployment(a, &sa)
   250  	assert.NoError(t, err)
   251  
   252  	err = r.Client.Get(context.TODO(), types.NamespacedName{
   253  		Name:      generateResourceName(common.ArgoCDNotificationsControllerComponent, a),
   254  		Namespace: a.Namespace,
   255  	}, deployment)
   256  	assert.True(t, errors.IsNotFound(err))
   257  }
   258  
   259  func TestReconcileNotifications_CreateMetricsService(t *testing.T) {
   260  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   261  		a.Spec.Notifications.Enabled = true
   262  	})
   263  
   264  	resObjs := []client.Object{a}
   265  	subresObjs := []client.Object{a}
   266  	runtimeObjs := []runtime.Object{}
   267  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   268  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   269  	r := makeTestReconciler(cl, sch)
   270  
   271  	err := monitoringv1.AddToScheme(r.Scheme)
   272  	assert.NoError(t, err)
   273  
   274  	err = r.reconcileNotificationsMetricsService(a)
   275  	assert.NoError(t, err)
   276  
   277  	testService := &corev1.Service{}
   278  	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
   279  		Name:      fmt.Sprintf("%s-%s", a.Name, "notifications-controller-metrics"),
   280  		Namespace: a.Namespace,
   281  	}, testService))
   282  
   283  	assert.Equal(t, testService.ObjectMeta.Labels["app.kubernetes.io/name"],
   284  		fmt.Sprintf("%s-%s", a.Name, "notifications-controller-metrics"))
   285  
   286  	assert.Equal(t, testService.Spec.Selector["app.kubernetes.io/name"],
   287  		fmt.Sprintf("%s-%s", a.Name, "notifications-controller"))
   288  
   289  	assert.Equal(t, testService.Spec.Ports[0].Port, int32(9001))
   290  	assert.Equal(t, testService.Spec.Ports[0].TargetPort, intstr.IntOrString{
   291  		IntVal: int32(9001),
   292  	})
   293  	assert.Equal(t, testService.Spec.Ports[0].Protocol, v1.Protocol("TCP"))
   294  	assert.Equal(t, testService.Spec.Ports[0].Name, "metrics")
   295  }
   296  
   297  func TestReconcileNotifications_CreateServiceMonitor(t *testing.T) {
   298  
   299  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   300  		a.Spec.Notifications.Enabled = true
   301  	})
   302  
   303  	resObjs := []client.Object{a}
   304  	subresObjs := []client.Object{a}
   305  	runtimeObjs := []runtime.Object{}
   306  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   307  	monitoringv1.AddToScheme(sch)
   308  	v1alpha1.AddToScheme(sch)
   309  
   310  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   311  	r := makeTestReconciler(cl, sch)
   312  
   313  	// Notifications controller service monitor should not be created when Prometheus API is not found.
   314  	prometheusAPIFound = false
   315  	err := r.reconcileNotificationsController(a)
   316  	assert.NoError(t, err)
   317  
   318  	testServiceMonitor := &monitoringv1.ServiceMonitor{}
   319  	assert.Error(t, r.Client.Get(context.TODO(), types.NamespacedName{
   320  		Name:      fmt.Sprintf("%s-%s", a.Name, "notifications-controller-metrics"),
   321  		Namespace: a.Namespace,
   322  	}, testServiceMonitor))
   323  
   324  	// Prometheus API found, Verify notification controller service monitor exists.
   325  	prometheusAPIFound = true
   326  	err = r.reconcileNotificationsController(a)
   327  	assert.NoError(t, err)
   328  
   329  	testServiceMonitor = &monitoringv1.ServiceMonitor{}
   330  	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
   331  		Name:      fmt.Sprintf("%s-%s", a.Name, "notifications-controller-metrics"),
   332  		Namespace: a.Namespace,
   333  	}, testServiceMonitor))
   334  
   335  	assert.Equal(t, testServiceMonitor.ObjectMeta.Labels["release"], "prometheus-operator")
   336  
   337  	assert.Equal(t, testServiceMonitor.Spec.Endpoints[0].Port, "metrics")
   338  	assert.Equal(t, testServiceMonitor.Spec.Endpoints[0].Scheme, "http")
   339  	assert.Equal(t, testServiceMonitor.Spec.Endpoints[0].Interval, "30s")
   340  	assert.Equal(t, testServiceMonitor.Spec.Selector.MatchLabels["app.kubernetes.io/name"],
   341  		fmt.Sprintf("%s-%s", a.Name, "notifications-controller-metrics"))
   342  }
   343  
   344  func TestReconcileNotifications_CreateSecret(t *testing.T) {
   345  	logf.SetLogger(ZapLogger(true))
   346  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   347  		a.Spec.Notifications.Enabled = true
   348  	})
   349  
   350  	resObjs := []client.Object{a}
   351  	subresObjs := []client.Object{a}
   352  	runtimeObjs := []runtime.Object{}
   353  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   354  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   355  	r := makeTestReconciler(cl, sch)
   356  
   357  	err := r.reconcileNotificationsSecret(a)
   358  	assert.NoError(t, err)
   359  
   360  	testSecret := &corev1.Secret{}
   361  	assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{
   362  		Name:      "argocd-notifications-secret",
   363  		Namespace: a.Namespace,
   364  	}, testSecret))
   365  
   366  	a.Spec.Notifications.Enabled = false
   367  	err = r.reconcileNotificationsSecret(a)
   368  	assert.NoError(t, err)
   369  	secret := &corev1.Secret{}
   370  	err = r.Client.Get(context.TODO(), types.NamespacedName{Name: "argocd-notifications-secret", Namespace: a.Namespace}, secret)
   371  	assertNotFound(t, err)
   372  }
   373  
   374  func TestReconcileNotifications_testEnvVars(t *testing.T) {
   375  
   376  	envMap := []corev1.EnvVar{
   377  		{
   378  			Name:  "foo",
   379  			Value: "bar",
   380  		},
   381  	}
   382  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   383  		a.Spec.Notifications.Enabled = true
   384  		a.Spec.Notifications.Env = envMap
   385  	})
   386  
   387  	resObjs := []client.Object{a}
   388  	subresObjs := []client.Object{a}
   389  	runtimeObjs := []runtime.Object{}
   390  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   391  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   392  	r := makeTestReconciler(cl, sch)
   393  
   394  	sa := corev1.ServiceAccount{}
   395  	assert.NoError(t, r.reconcileNotificationsDeployment(a, &sa))
   396  
   397  	deployment := &appsv1.Deployment{}
   398  	assert.NoError(t, r.Client.Get(
   399  		context.TODO(),
   400  		types.NamespacedName{
   401  			Name:      a.Name + "-notifications-controller",
   402  			Namespace: a.Namespace,
   403  		},
   404  		deployment))
   405  
   406  	if diff := cmp.Diff(envMap, deployment.Spec.Template.Spec.Containers[0].Env); diff != "" {
   407  		t.Fatalf("failed to reconcile notifications-controller deployment env:\n%s", diff)
   408  	}
   409  
   410  	// Verify any manual updates to the env vars should be overridden by the operator.
   411  	unwantedEnv := []corev1.EnvVar{
   412  		{
   413  			Name:  "foo",
   414  			Value: "bar",
   415  		},
   416  		{
   417  			Name:  "ping",
   418  			Value: "pong",
   419  		},
   420  	}
   421  
   422  	deployment.Spec.Template.Spec.Containers[0].Env = unwantedEnv
   423  	assert.NoError(t, r.Client.Update(context.TODO(), deployment))
   424  
   425  	// Reconcile back
   426  	assert.NoError(t, r.reconcileNotificationsDeployment(a, &sa))
   427  
   428  	// Get the updated deployment
   429  	assert.NoError(t, r.Client.Get(
   430  		context.TODO(),
   431  		types.NamespacedName{
   432  			Name:      a.Name + "-notifications-controller",
   433  			Namespace: a.Namespace,
   434  		},
   435  		deployment))
   436  
   437  	if diff := cmp.Diff(envMap, deployment.Spec.Template.Spec.Containers[0].Env); diff != "" {
   438  		t.Fatalf("operator failed to override the manual changes to notification controller:\n%s", diff)
   439  	}
   440  }
   441  
   442  func TestReconcileNotifications_testLogLevel(t *testing.T) {
   443  
   444  	testLogLevel := "debug"
   445  	a := makeTestArgoCD(func(a *argoproj.ArgoCD) {
   446  		a.Spec.Notifications.Enabled = true
   447  		a.Spec.Notifications.LogLevel = testLogLevel
   448  	})
   449  
   450  	resObjs := []client.Object{a}
   451  	subresObjs := []client.Object{a}
   452  	runtimeObjs := []runtime.Object{}
   453  	sch := makeTestReconcilerScheme(argoproj.AddToScheme)
   454  	cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs)
   455  	r := makeTestReconciler(cl, sch)
   456  
   457  	sa := corev1.ServiceAccount{}
   458  	assert.NoError(t, r.reconcileNotificationsDeployment(a, &sa))
   459  
   460  	deployment := &appsv1.Deployment{}
   461  	assert.NoError(t, r.Client.Get(
   462  		context.TODO(),
   463  		types.NamespacedName{
   464  			Name:      a.Name + "-notifications-controller",
   465  			Namespace: a.Namespace,
   466  		},
   467  		deployment))
   468  
   469  	expectedCMD := []string{
   470  		"argocd-notifications",
   471  		"--loglevel",
   472  		"debug",
   473  		"--argocd-repo-server",
   474  		"argocd-repo-server.argocd.svc.cluster.local:8081",
   475  	}
   476  
   477  	if diff := cmp.Diff(expectedCMD, deployment.Spec.Template.Spec.Containers[0].Command); diff != "" {
   478  		t.Fatalf("failed to reconcile notifications-controller deployment logLevel:\n%s", diff)
   479  	}
   480  
   481  	// Verify any manual updates to the logLevel should be overridden by the operator.
   482  	unwantedCommand := []string{
   483  		"argocd-notifications",
   484  		"--logLevel",
   485  		"info",
   486  	}
   487  
   488  	deployment.Spec.Template.Spec.Containers[0].Command = unwantedCommand
   489  	assert.NoError(t, r.Client.Update(context.TODO(), deployment))
   490  
   491  	// Reconcile back
   492  	assert.NoError(t, r.reconcileNotificationsDeployment(a, &sa))
   493  
   494  	// Get the updated deployment
   495  	assert.NoError(t, r.Client.Get(
   496  		context.TODO(),
   497  		types.NamespacedName{
   498  			Name:      a.Name + "-notifications-controller",
   499  			Namespace: a.Namespace,
   500  		},
   501  		deployment))
   502  
   503  	if diff := cmp.Diff(expectedCMD, deployment.Spec.Template.Spec.Containers[0].Command); diff != "" {
   504  		t.Fatalf("operator failed to override the manual changes to notification controller:\n%s", diff)
   505  	}
   506  }