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 }