diff --git a/coding-standards-and-best-practices.md b/coding-standards-and-best-practices.md index 6111d4f56..595d13861 100644 --- a/coding-standards-and-best-practices.md +++ b/coding-standards-and-best-practices.md @@ -214,18 +214,18 @@ if rr.Instance.Spec.HA.Enabled { acr.Logger.Error(err, "reconcileManagedRoles: failed to retrieve role", "name", existingRole.Name, "namespace", existingRole.Namespace) ``` -- Use debug level (`Logger.V(1).Info`) when recording non-essential information. i.e, information on events that don't block happy path execution, but can provide hints if troubleshooting is needed e.g: +- Use debug level (`Logger.Debug`) when recording non-essential information. i.e, information on events that don't block happy path execution, but can provide hints if troubleshooting is needed e.g: ``` -acr.Logger.V(1).Info("reconcileManagedRoles: one or more mutations could not be applied") -acr.Logger.V(1).Info("reconcileManagedRoles: skip reconciliation in favor of custom role", "name", customRoleName) +acr.Logger.Debug("reconcileManagedRoles: one or more mutations could not be applied") +acr.Logger.Debug("reconcileManagedRoles: skip reconciliation in favor of custom role", "name", customRoleName) ``` -- Use Info level (`Logger.Info` or `Logger.V(0).Info`) for all other info-level logs. Any new action taken by the controller that is critical to normal functioning. +- Use Info level (`Logger.Info`) for all other info-level logs. Any new action taken by the controller that is critical to normal functioning. - - No need to mention function names when logging at `info` level. eg: ``` -acr.Logger.V(0).Info("role created", "name", desiredRole.Name, "namespace", desiredRole.Namespace) +acr.Logger.Info("role created", "name", desiredRole.Name, "namespace", desiredRole.Namespace) ``` - Only use log statements to log success/error if the function belongs to a controller package and is invoked by the controller. No need to log statements from utility/helper packages. e.g: diff --git a/common/TOBEREMOVED.go b/common/TOBEREMOVED.go index c6ce3ab01..410ac60a9 100644 --- a/common/TOBEREMOVED.go +++ b/common/TOBEREMOVED.go @@ -160,6 +160,15 @@ const ( // ArgoCDRedisHAImageEnvVar is the environment variable used to get the image // to used for the the Redis container in HA mode. ArgoCDRedisHAImageEnvVar = "ARGOCD_REDIS_HA_IMAGE" + + // ArgoCDDefaultRepoMetricsPort is the default listen port for the Argo CD repo server metrics. + ArgoCDDefaultRepoMetricsPort = 8084 + + // ArgoCDDefaultRepoServerPort is the default listen port for the Argo CD repo server. + ArgoCDDefaultRepoServerPort = 8081 + + // ArgoCDKeyRelease is the prometheus release key for labels. + ArgoCDKeyRelease = "release" ) // DefaultLabels returns the default set of labels for controllers. diff --git a/common/envVars.go b/common/envVars.go index 9b8593f28..2b84f20a2 100644 --- a/common/envVars.go +++ b/common/envVars.go @@ -14,10 +14,6 @@ const ( // to used for the Keycloak container. ArgoCDKeycloakImageEnvVar = "ARGOCD_KEYCLOAK_IMAGE" - // ArgoCDRepoImageEnvVar is the environment variable used to get the image - // to used for the Dex container. - ArgoCDRepoImageEnvVar = "ARGOCD_REPOSERVER_IMAGE" - // ArgoCDGrafanaImageEnvVar is the environment variable used to get the image // to used for the Grafana container. ArgoCDGrafanaImageEnvVar = "ARGOCD_GRAFANA_IMAGE" @@ -34,4 +30,6 @@ const ( // ArgoCDLabelSelectorEnvVar is an environment variable that contains the labels used for selective instance reconilliation. ArgoCDLabelSelectorEnvVar = "ARGOCD_LABEL_SELECTOR" + + ArgoCDExecTimeoutEnvVar = "ARGOCD_EXEC_TIMEOUT" ) diff --git a/common/keys.go b/common/keys.go index 70bef5360..d93ab55ee 100644 --- a/common/keys.go +++ b/common/keys.go @@ -78,9 +78,6 @@ const ( // ArgoCDKeyRBACScopes is the configuration key for the Argo CD RBAC scopes. ArgoCDKeyRBACScopes = "scopes" - // ArgoCDKeyRelease is the prometheus release key for labels. - ArgoCDKeyRelease = "release" - // ArgoCDKeyResourceExclusions is the configuration key for resource exclusions. ArgoCDKeyResourceExclusions = "resource.exclusions" diff --git a/common/names.go b/common/names.go index 8c619ead7..ca662016e 100644 --- a/common/names.go +++ b/common/names.go @@ -34,4 +34,6 @@ const ( const ( // ArgoCDCASuffix is the name suffix for ArgoCD CA resources. ArgoCDCASuffix = "ca" + + MetricsSuffix = "metrics" ) diff --git a/common/notifications.go b/common/notifications.go index dade5a663..8d544327b 100644 --- a/common/notifications.go +++ b/common/notifications.go @@ -2,6 +2,7 @@ package common // notifications const ( + NotificationsController = "notifications-controller" NotificationsControllerComponent = "argocd-notifications-controller" NotificationsSecretName = "argocd-notifications-secret" NotificationsConfigMapName = "argocd-notifications-cm" diff --git a/common/prometheus.go b/common/prometheus.go index d012a5538..1098598e7 100644 --- a/common/prometheus.go +++ b/common/prometheus.go @@ -4,6 +4,9 @@ package common const ( // ArgoCDKeyPrometheus is the resource prometheus key for labels. ArgoCDKeyPrometheus = "prometheus" + + // PrometheusReleaseKey is the prometheus release key for labels. + PrometheusReleaseKey = "release" ) // defaults diff --git a/common/redis.go b/common/redis.go index b687da112..a7375db0a 100644 --- a/common/redis.go +++ b/common/redis.go @@ -89,3 +89,11 @@ const ( const ( RedisTLSCertChangedKey = "redis.tls.cert.changed" ) + +// commands +const ( + RedisCmd = "--redis" + RedisUseTLSCmd = "--redis-use-tls" + RedisInsecureSkipTLSVerifyCmd = "--redis-insecure-skip-tls-verify" + RedisCACertificate = "--redis-ca-certificate" +) diff --git a/common/reposerver.go b/common/reposerver.go index 17575b899..6dcce63b1 100644 --- a/common/reposerver.go +++ b/common/reposerver.go @@ -2,10 +2,20 @@ package common // names const ( + RepoServerController = "repo-server-controller" + + // RepoServerComponent is the repo-server control plane component + RepoServerComponent = "repo-server" + + ArgoCDRepoServerName = "argocd-repo-server" + // ArgoCDRepoServerTLSSecretName is the name of the TLS secret for the repo-server ArgoCDRepoServerTLSSecretName = "argocd-repo-server-tls" +) - RepoServerSuffix = "-repo-server" +// suffixes +const ( + RepoServerSuffix = "repo-server" ) // values @@ -16,9 +26,26 @@ const ( // defaults const ( - // ArgoCDDefaultRepoMetricsPort is the default listen port for the Argo CD repo server metrics. - ArgoCDDefaultRepoMetricsPort = 8084 + // DefaultRepoServerMetricsPort is the default listen port for the Argo CD repo server metrics. + DefaultRepoServerMetricsPort = 8084 + + // DefaultRepoServerPort is the default listen port for the Argo CD repo server. + DefaultRepoServerPort = 8081 +) + +// env vars +const ( + // ArgoCDRepoImageEnvVar is the environment variable used to get the image to be used for + // the repo-server container + ArgoCDRepoImageEnvVar = "ARGOCD_REPOSERVER_IMAGE" +) - // ArgoCDDefaultRepoServerPort is the default listen port for the Argo CD repo server. - ArgoCDDefaultRepoServerPort = 8081 +// keys +const ( + RepoTLSCertChangedKey = "repo.tls.cert.changed" +) + +// commands +const ( + RepoServerCmd = "argocd-repo-server" ) diff --git a/common/values.go b/common/values.go index 5e3b002be..b33938d19 100644 --- a/common/values.go +++ b/common/values.go @@ -49,6 +49,8 @@ const ( ArgoCDStatusRunning = "Running" ArgoCDStatusAvailable = "Available" + + PrometheusOperator = "prometheus-operator" ) // general values @@ -88,5 +90,7 @@ const ( // Commnds const ( - LogLevel = "--loglevel" + LogLevelCmd = "--loglevel" + LogFormatCmd = "--logformat" + UidEntryPointSh = "uid_entrypoint.sh" ) diff --git a/controllers/argocd/TOBEREMOVED.go b/controllers/argocd/TOBEREMOVED.go index d789f2337..2b070888d 100644 --- a/controllers/argocd/TOBEREMOVED.go +++ b/controllers/argocd/TOBEREMOVED.go @@ -8,12 +8,12 @@ import ( "errors" "fmt" "os" - "reflect" "strings" "text/template" "time" argopass "github.com/argoproj/argo-cd/v2/util/password" + "github.com/prometheus/client_golang/prometheus" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" @@ -29,19 +29,18 @@ import ( "github.com/sethvargo/go-password/password" "golang.org/x/mod/semver" "gopkg.in/yaml.v2" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" + ctrlLog "sigs.k8s.io/controller-runtime/pkg/log" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // DeprecationEventEmissionStatus is meant to track which deprecation events have been emitted already. This is temporary and can be removed in v0.0.6 once we have provided enough @@ -458,1643 +457,6 @@ func getArgoContainerImage(cr *argoproj.ArgoCD) string { return argoutil.CombineImageTag(img, tag) } -// getNotificationsResources will return the ResourceRequirements for the Notifications container. -func getNotificationsResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements { - resources := corev1.ResourceRequirements{} - - // Allow override of resource requirements from CR - if cr.Spec.Notifications.Resources != nil { - resources = *cr.Spec.Notifications.Resources - } - - return resources -} - -func getNotificationsCommand(cr *argoproj.ArgoCD) []string { - - cmd := make([]string, 0) - cmd = append(cmd, "argocd-notifications") - - cmd = append(cmd, "--loglevel") - cmd = append(cmd, getLogLevel(cr.Spec.Notifications.LogLevel)) - - return cmd -} - -// reconcileNotificationsConfigMap only creates/deletes the argocd-notifications-cm based on whether notifications is enabled/disabled in the CR -// It does not reconcile/overwrite any fields or information in the configmap itself -func (r *ReconcileArgoCD) reconcileNotificationsConfigMap(cr *argoproj.ArgoCD) error { - - desiredConfigMap := newConfigMapWithName("argocd-notifications-cm", cr) - desiredConfigMap.Data = getDefaultNotificationsConfig() - - cmExists := true - existingConfigMap := &corev1.ConfigMap{} - if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredConfigMap.Name, existingConfigMap); err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get the configmap associated with %s : %s", desiredConfigMap.Name, err) - } - cmExists = false - } - - if cmExists { - // CM exists but shouldn't, so it should be deleted - if !cr.Spec.Notifications.Enabled { - log.Info(fmt.Sprintf("Deleting configmap %s as notifications is disabled", existingConfigMap.Name)) - return r.Client.Delete(context.TODO(), existingConfigMap) - } - - // CM exists and should, nothing to do here - return nil - } - - // CM doesn't exist and shouldn't, nothing to do here - if !cr.Spec.Notifications.Enabled { - return nil - } - - // CM doesn't exist but should, so it should be created - if err := controllerutil.SetControllerReference(cr, desiredConfigMap, r.Scheme); err != nil { - return err - } - - log.Info(fmt.Sprintf("Creating configmap %s", desiredConfigMap.Name)) - err := r.Client.Create(context.TODO(), desiredConfigMap) - if err != nil { - return err - } - - return nil -} - -// reconcileNotificationsSecret only creates/deletes the argocd-notifications-secret based on whether notifications is enabled/disabled in the CR -// It does not reconcile/overwrite any fields or information in the secret itself -func (r *ReconcileArgoCD) reconcileNotificationsSecret(cr *argoproj.ArgoCD) error { - - desiredSecret := argoutil.NewSecretWithName(cr, "argocd-notifications-secret") - - secretExists := true - existingSecret := &corev1.Secret{} - if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredSecret.Name, existingSecret); err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get the secret associated with %s : %s", desiredSecret.Name, err) - } - secretExists = false - } - - if secretExists { - // secret exists but shouldn't, so it should be deleted - if !cr.Spec.Notifications.Enabled { - log.Info(fmt.Sprintf("Deleting secret %s as notifications is disabled", existingSecret.Name)) - return r.Client.Delete(context.TODO(), existingSecret) - } - - // secret exists and should, nothing to do here - return nil - } - - // secret doesn't exist and shouldn't, nothing to do here - if !cr.Spec.Notifications.Enabled { - return nil - } - - // secret doesn't exist but should, so it should be created - if err := controllerutil.SetControllerReference(cr, desiredSecret, r.Scheme); err != nil { - return err - } - - log.Info(fmt.Sprintf("Creating secret %s", desiredSecret.Name)) - err := r.Client.Create(context.TODO(), desiredSecret) - if err != nil { - return err - } - - return nil -} - -func (r *ReconcileArgoCD) reconcileNotificationsController(cr *argoproj.ArgoCD) error { - - log.Info("reconciling notifications serviceaccount") - sa, err := r.reconcileNotificationsServiceAccount(cr) - if err != nil { - return err - } - - log.Info("reconciling notifications role") - role, err := r.reconcileNotificationsRole(cr) - if err != nil { - return err - } - - log.Info("reconciling notifications role binding") - if err := r.reconcileNotificationsRoleBinding(cr, role, sa); err != nil { - return err - } - - log.Info("reconciling notifications configmap") - if err := r.reconcileNotificationsConfigMap(cr); err != nil { - return err - } - - log.Info("reconciling notifications secret") - if err := r.reconcileNotificationsSecret(cr); err != nil { - return err - } - - log.Info("reconciling notifications deployment") - if err := r.reconcileNotificationsDeployment(cr, sa); err != nil { - return err - } - - return nil -} - -// The code to create/delete notifications resources is written within the reconciliation logic itself. However, these functions must be called -// in the right order depending on whether resources are getting created or deleted. During creation we must create the role and sa first. -// RoleBinding and deployment are dependent on these resouces. During deletion the order is reversed. -// Deployment and RoleBinding must be deleted before the role and sa. deleteNotificationsResources will only be called during -// delete events, so we don't need to worry about duplicate, recurring reconciliation calls -func (r *ReconcileArgoCD) deleteNotificationsResources(cr *argoproj.ArgoCD) error { - - sa := &corev1.ServiceAccount{} - role := &rbacv1.Role{} - - if err := argoutil.FetchObject(r.Client, cr.Namespace, fmt.Sprintf("%s-%s", cr.Name, common.ArgoCDNotificationsControllerComponent), sa); err != nil { - if !apierrors.IsNotFound(err) { - return err - } - } - if err := argoutil.FetchObject(r.Client, cr.Namespace, fmt.Sprintf("%s-%s", cr.Name, common.ArgoCDNotificationsControllerComponent), role); err != nil { - if !apierrors.IsNotFound(err) { - return err - } - } - - log.Info("reconciling notifications deployment") - if err := r.reconcileNotificationsDeployment(cr, sa); err != nil { - return err - } - - log.Info("reconciling notifications secret") - if err := r.reconcileNotificationsSecret(cr); err != nil { - return err - } - - log.Info("reconciling notifications configmap") - if err := r.reconcileNotificationsConfigMap(cr); err != nil { - return err - } - - log.Info("reconciling notifications role binding") - if err := r.reconcileNotificationsRoleBinding(cr, role, sa); err != nil { - return err - } - - log.Info("reconciling notifications role") - _, err := r.reconcileNotificationsRole(cr) - if err != nil { - return err - } - - log.Info("reconciling notifications serviceaccount") - _, err = r.reconcileNotificationsServiceAccount(cr) - if err != nil { - return err - } - - return nil -} - -func (r *ReconcileArgoCD) reconcileNotificationsServiceAccount(cr *argoproj.ArgoCD) (*corev1.ServiceAccount, error) { - - sa := newServiceAccountWithName(common.ArgoCDNotificationsControllerComponent, cr) - - if err := argoutil.FetchObject(r.Client, cr.Namespace, sa.Name, sa); err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("failed to get the serviceAccount associated with %s : %s", sa.Name, err) - } - - // SA doesn't exist and shouldn't, nothing to do here - if !cr.Spec.Notifications.Enabled { - return nil, nil - } - - // SA doesn't exist but should, so it should be created - if err := controllerutil.SetControllerReference(cr, sa, r.Scheme); err != nil { - return nil, err - } - - log.Info(fmt.Sprintf("Creating serviceaccount %s", sa.Name)) - err := r.Client.Create(context.TODO(), sa) - if err != nil { - return nil, err - } - } - - // SA exists but shouldn't, so it should be deleted - if !cr.Spec.Notifications.Enabled { - log.Info(fmt.Sprintf("Deleting serviceaccount %s as notifications is disabled", sa.Name)) - return nil, r.Client.Delete(context.TODO(), sa) - } - - return sa, nil -} - -func (r *ReconcileArgoCD) reconcileNotificationsRole(cr *argoproj.ArgoCD) (*rbacv1.Role, error) { - - policyRules := policyRuleForNotificationsController() - desiredRole := newRole(common.ArgoCDNotificationsControllerComponent, policyRules, cr) - - existingRole := &rbacv1.Role{} - if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredRole.Name, existingRole); err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("failed to get the role associated with %s : %s", desiredRole.Name, err) - } - - // role does not exist and shouldn't, nothing to do here - if !cr.Spec.Notifications.Enabled { - return nil, nil - } - - // role does not exist but should, so it should be created - if err := controllerutil.SetControllerReference(cr, desiredRole, r.Scheme); err != nil { - return nil, err - } - - log.Info(fmt.Sprintf("Creating role %s", desiredRole.Name)) - err := r.Client.Create(context.TODO(), desiredRole) - if err != nil { - return nil, err - } - return desiredRole, nil - } - - // role exists but shouldn't, so it should be deleted - if !cr.Spec.Notifications.Enabled { - log.Info(fmt.Sprintf("Deleting role %s as notifications is disabled", existingRole.Name)) - return nil, r.Client.Delete(context.TODO(), existingRole) - } - - // role exists and should. Reconcile role if changed - if !reflect.DeepEqual(existingRole.Rules, desiredRole.Rules) { - existingRole.Rules = desiredRole.Rules - if err := controllerutil.SetControllerReference(cr, existingRole, r.Scheme); err != nil { - return nil, err - } - return existingRole, r.Client.Update(context.TODO(), existingRole) - } - - return desiredRole, nil -} - -func (r *ReconcileArgoCD) reconcileNotificationsRoleBinding(cr *argoproj.ArgoCD, role *rbacv1.Role, sa *corev1.ServiceAccount) error { - - desiredRoleBinding := newRoleBindingWithname(common.ArgoCDNotificationsControllerComponent, cr) - desiredRoleBinding.RoleRef = rbacv1.RoleRef{ - APIGroup: rbacv1.GroupName, - Kind: "Role", - Name: role.Name, - } - - desiredRoleBinding.Subjects = []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: sa.Name, - Namespace: sa.Namespace, - }, - } - - // fetch existing rolebinding by name - existingRoleBinding := &rbacv1.RoleBinding{} - if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: desiredRoleBinding.Name, Namespace: cr.Namespace}, existingRoleBinding); err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get the rolebinding associated with %s : %s", desiredRoleBinding.Name, err) - } - - // roleBinding does not exist and shouldn't, nothing to do here - if !cr.Spec.Notifications.Enabled { - return nil - } - - // roleBinding does not exist but should, so it should be created - if err := controllerutil.SetControllerReference(cr, desiredRoleBinding, r.Scheme); err != nil { - return err - } - - log.Info(fmt.Sprintf("Creating roleBinding %s", desiredRoleBinding.Name)) - return r.Client.Create(context.TODO(), desiredRoleBinding) - } - - // roleBinding exists but shouldn't, so it should be deleted - if !cr.Spec.Notifications.Enabled { - log.Info(fmt.Sprintf("Deleting roleBinding %s as notifications is disabled", existingRoleBinding.Name)) - return r.Client.Delete(context.TODO(), existingRoleBinding) - } - - // roleBinding exists and should. Reconcile roleBinding if changed - if !reflect.DeepEqual(existingRoleBinding.RoleRef, desiredRoleBinding.RoleRef) { - // if the RoleRef changes, delete the existing role binding and create a new one - if err := r.Client.Delete(context.TODO(), existingRoleBinding); err != nil { - return err - } - } else if !reflect.DeepEqual(existingRoleBinding.Subjects, desiredRoleBinding.Subjects) { - existingRoleBinding.Subjects = desiredRoleBinding.Subjects - if err := controllerutil.SetControllerReference(cr, existingRoleBinding, r.Scheme); err != nil { - return err - } - return r.Client.Update(context.TODO(), existingRoleBinding) - } - - return nil -} - -func (r *ReconcileArgoCD) reconcileNotificationsDeployment(cr *argoproj.ArgoCD, sa *corev1.ServiceAccount) error { - - desiredDeployment := newDeploymentWithSuffix("notifications-controller", "controller", cr) - - desiredDeployment.Spec.Strategy = appsv1.DeploymentStrategy{ - Type: appsv1.RecreateDeploymentStrategyType, - } - - if replicas := getArgoCDNotificationsControllerReplicas(cr); replicas != nil { - desiredDeployment.Spec.Replicas = replicas - } - - notificationEnv := cr.Spec.Notifications.Env - // Let user specify their own environment first - notificationEnv = argoutil.EnvMerge(notificationEnv, proxyEnvVars(), false) - - podSpec := &desiredDeployment.Spec.Template.Spec - podSpec.SecurityContext = &corev1.PodSecurityContext{ - RunAsNonRoot: boolPtr(true), - } - AddSeccompProfileForOpenShift(r.Client, podSpec) - podSpec.ServiceAccountName = sa.ObjectMeta.Name - podSpec.Volumes = []corev1.Volume{ - { - Name: "tls-certs", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDTLSCertsConfigMapName, - }, - }, - }, - }, - { - Name: "argocd-repo-server-tls", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: common.ArgoCDRepoServerTLSSecretName, - Optional: boolPtr(true), - }, - }, - }, - } - - podSpec.Containers = []corev1.Container{{ - Command: getNotificationsCommand(cr), - Image: getArgoContainerImage(cr), - ImagePullPolicy: corev1.PullAlways, - Name: common.ArgoCDNotificationsControllerComponent, - Env: notificationEnv, - Resources: getNotificationsResources(cr), - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.IntOrString{ - IntVal: int32(9001), - }, - }, - }, - }, - SecurityContext: &corev1.SecurityContext{ - AllowPrivilegeEscalation: boolPtr(false), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{ - "ALL", - }, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "tls-certs", - MountPath: "/app/config/tls", - }, - { - Name: "argocd-repo-server-tls", - MountPath: "/app/config/reposerver/tls", - }, - }, - WorkingDir: "/app", - }} - - // fetch existing deployment by name - deploymentChanged := false - existingDeployment := &appsv1.Deployment{} - if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: desiredDeployment.Name, Namespace: cr.Namespace}, existingDeployment); err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get the deployment associated with %s : %s", existingDeployment.Name, err) - } - - // deployment does not exist and shouldn't, nothing to do here - if !cr.Spec.Notifications.Enabled { - return nil - } - - // deployment does not exist but should, so it should be created - if err := controllerutil.SetControllerReference(cr, desiredDeployment, r.Scheme); err != nil { - return err - } - - log.Info(fmt.Sprintf("Creating deployment %s", desiredDeployment.Name)) - return r.Client.Create(context.TODO(), desiredDeployment) - } - - // deployment exists but shouldn't, so it should be deleted - if !cr.Spec.Notifications.Enabled { - log.Info(fmt.Sprintf("Deleting deployment %s as notifications is disabled", existingDeployment.Name)) - return r.Client.Delete(context.TODO(), existingDeployment) - } - - // deployment exists and should. Reconcile deployment if changed - updateNodePlacement(existingDeployment, desiredDeployment, &deploymentChanged) - - if existingDeployment.Spec.Template.Spec.Containers[0].Image != desiredDeployment.Spec.Template.Spec.Containers[0].Image { - existingDeployment.Spec.Template.Spec.Containers[0].Image = desiredDeployment.Spec.Template.Spec.Containers[0].Image - existingDeployment.Spec.Template.ObjectMeta.Labels["image.upgraded"] = time.Now().UTC().Format("01022006-150406-MST") - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].Command, desiredDeployment.Spec.Template.Spec.Containers[0].Command) { - existingDeployment.Spec.Template.Spec.Containers[0].Command = desiredDeployment.Spec.Template.Spec.Containers[0].Command - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].Env, - desiredDeployment.Spec.Template.Spec.Containers[0].Env) { - existingDeployment.Spec.Template.Spec.Containers[0].Env = desiredDeployment.Spec.Template.Spec.Containers[0].Env - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Volumes, desiredDeployment.Spec.Template.Spec.Volumes) { - existingDeployment.Spec.Template.Spec.Volumes = desiredDeployment.Spec.Template.Spec.Volumes - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Replicas, desiredDeployment.Spec.Replicas) { - existingDeployment.Spec.Replicas = desiredDeployment.Spec.Replicas - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].VolumeMounts, desiredDeployment.Spec.Template.Spec.Containers[0].VolumeMounts) { - existingDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = desiredDeployment.Spec.Template.Spec.Containers[0].VolumeMounts - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].Resources, desiredDeployment.Spec.Template.Spec.Containers[0].Resources) { - existingDeployment.Spec.Template.Spec.Containers[0].Resources = desiredDeployment.Spec.Template.Spec.Containers[0].Resources - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.ServiceAccountName, desiredDeployment.Spec.Template.Spec.ServiceAccountName) { - existingDeployment.Spec.Template.Spec.ServiceAccountName = desiredDeployment.Spec.Template.Spec.ServiceAccountName - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Labels, desiredDeployment.Labels) { - existingDeployment.Labels = desiredDeployment.Labels - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Template.Labels, desiredDeployment.Spec.Template.Labels) { - existingDeployment.Spec.Template.Labels = desiredDeployment.Spec.Template.Labels - deploymentChanged = true - } - - if !reflect.DeepEqual(existingDeployment.Spec.Selector, desiredDeployment.Spec.Selector) { - existingDeployment.Spec.Selector = desiredDeployment.Spec.Selector - deploymentChanged = true - } - - if deploymentChanged { - return r.Client.Update(context.TODO(), existingDeployment) - } - - return nil - -} - -// getDefaultNotificationsConfig returns a map that contains default triggers and template configurations for argocd-notifications-cm -func getDefaultNotificationsConfig() map[string]string { - - notificationsConfig := make(map[string]string) - - // configure default notifications templates - - notificationsConfig["template.app-created"] = `email: - subject: Application {{.app.metadata.name}} has been created. -message: Application {{.app.metadata.name}} has been created. -teams: - title: Application {{.app.metadata.name}} has been created.` - - notificationsConfig["template.app-deleted"] = `email: - subject: Application {{.app.metadata.name}} has been deleted. -message: Application {{.app.metadata.name}} has been deleted. -teams: - title: Application {{.app.metadata.name}} has been deleted.` - - notificationsConfig["template.app-deployed"] = `email: - subject: New version of an application {{.app.metadata.name}} is up and running. -message: | - {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} is now running new version of deployments manifests. -slack: - attachments: | - [{ - "title": "{{ .app.metadata.name}}", - "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", - "color": "#18be52", - "fields": [ - { - "title": "Sync Status", - "value": "{{.app.status.sync.status}}", - "short": true - }, - { - "title": "Repository", - "value": "{{.app.spec.source.repoURL}}", - "short": true - }, - { - "title": "Revision", - "value": "{{.app.status.sync.revision}}", - "short": true - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "title": "{{$c.type}}", - "value": "{{$c.message}}", - "short": true - } - {{end}} - ] - }] - deliveryPolicy: Post - groupingKey: "" - notifyBroadcast: false -teams: - facts: | - [{ - "name": "Sync Status", - "value": "{{.app.status.sync.status}}" - }, - { - "name": "Repository", - "value": "{{.app.spec.source.repoURL}}" - }, - { - "name": "Revision", - "value": "{{.app.status.sync.revision}}" - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "name": "{{$c.type}}", - "value": "{{$c.message}}" - } - {{end}} - ] - potentialAction: |- - [{ - "@type":"OpenUri", - "name":"Operation Application", - "targets":[{ - "os":"default", - "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}" - }] - }, - { - "@type":"OpenUri", - "name":"Open Repository", - "targets":[{ - "os":"default", - "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" - }] - }] - themeColor: '#000080' - title: New version of an application {{.app.metadata.name}} is up and running.` - - notificationsConfig["template.app-health-degraded"] = `email: - subject: Application {{.app.metadata.name}} has degraded. -message: | - {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} has degraded. - Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}. -slack: - attachments: | - [{ - "title": "{{ .app.metadata.name}}", - "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", - "color": "#f4c030", - "fields": [ - { - "title": "Health Status", - "value": "{{.app.status.health.status}}", - "short": true - }, - { - "title": "Repository", - "value": "{{.app.spec.source.repoURL}}", - "short": true - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "title": "{{$c.type}}", - "value": "{{$c.message}}", - "short": true - } - {{end}} - ] - }] - deliveryPolicy: Post - groupingKey: "" - notifyBroadcast: false -teams: - facts: | - [{ - "name": "Health Status", - "value": "{{.app.status.health.status}}" - }, - { - "name": "Repository", - "value": "{{.app.spec.source.repoURL}}" - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "name": "{{$c.type}}", - "value": "{{$c.message}}" - } - {{end}} - ] - potentialAction: | - [{ - "@type":"OpenUri", - "name":"Open Application", - "targets":[{ - "os":"default", - "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}" - }] - }, - { - "@type":"OpenUri", - "name":"Open Repository", - "targets":[{ - "os":"default", - "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" - }] - }] - themeColor: '#FF0000' - title: Application {{.app.metadata.name}} has degraded.` - - notificationsConfig["template.app-sync-failed"] = `email: - subject: Failed to sync application {{.app.metadata.name}}. -message: | - {{if eq .serviceType "slack"}}:exclamation:{{end}} The sync operation of application {{.app.metadata.name}} has failed at {{.app.status.operationState.finishedAt}} with the following error: {{.app.status.operationState.message}} - Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true . -slack: - attachments: | - [{ - "title": "{{ .app.metadata.name}}", - "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", - "color": "#E96D76", - "fields": [ - { - "title": "Sync Status", - "value": "{{.app.status.sync.status}}", - "short": true - }, - { - "title": "Repository", - "value": "{{.app.spec.source.repoURL}}", - "short": true - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "title": "{{$c.type}}", - "value": "{{$c.message}}", - "short": true - } - {{end}} - ] - }] - deliveryPolicy: Post - groupingKey: "" - notifyBroadcast: false -teams: - facts: | - [{ - "name": "Sync Status", - "value": "{{.app.status.sync.status}}" - }, - { - "name": "Failed at", - "value": "{{.app.status.operationState.finishedAt}}" - }, - { - "name": "Repository", - "value": "{{.app.spec.source.repoURL}}" - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "name": "{{$c.type}}", - "value": "{{$c.message}}" - } - {{end}} - ] - potentialAction: |- - [{ - "@type":"OpenUri", - "name":"Open Operation", - "targets":[{ - "os":"default", - "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" - }] - }, - { - "@type":"OpenUri", - "name":"Open Repository", - "targets":[{ - "os":"default", - "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" - }] - }] - themeColor: '#FF0000' - title: Failed to sync application {{.app.metadata.name}}.` - - notificationsConfig["template.app-sync-running"] = `email: - subject: Start syncing application {{.app.metadata.name}}. -message: | - The sync operation of application {{.app.metadata.name}} has started at {{.app.status.operationState.startedAt}}. - Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true . -slack: - attachments: | - [{ - "title": "{{ .app.metadata.name}}", - "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", - "color": "#0DADEA", - "fields": [ - { - "title": "Sync Status", - "value": "{{.app.status.sync.status}}", - "short": true - }, - { - "title": "Repository", - "value": "{{.app.spec.source.repoURL}}", - "short": true - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "title": "{{$c.type}}", - "value": "{{$c.message}}", - "short": true - } - {{end}} - ] - }] - deliveryPolicy: Post - groupingKey: "" - notifyBroadcast: false -teams: - facts: | - [{ - "name": "Sync Status", - "value": "{{.app.status.sync.status}}" - }, - { - "name": "Started at", - "value": "{{.app.status.operationState.startedAt}}" - }, - { - "name": "Repository", - "value": "{{.app.spec.source.repoURL}}" - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "name": "{{$c.type}}", - "value": "{{$c.message}}" - } - {{end}} - ] - potentialAction: |- - [{ - "@type":"OpenUri", - "name":"Open Operation", - "targets":[{ - "os":"default", - "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" - }] - }, - { - "@type":"OpenUri", - "name":"Open Repository", - "targets":[{ - "os":"default", - "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" - }] - }] - title: Start syncing application {{.app.metadata.name}}.` - - notificationsConfig["template.app-sync-status-unknown"] = `email: - subject: Application {{.app.metadata.name}} sync status is 'Unknown' -message: | - {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} sync is 'Unknown'. - Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}. - {{if ne .serviceType "slack"}} - {{range $c := .app.status.conditions}} - * {{$c.message}} - {{end}} - {{end}} -slack: - attachments: | - [{ - "title": "{{ .app.metadata.name}}", - "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", - "color": "#E96D76", - "fields": [ - { - "title": "Sync Status", - "value": "{{.app.status.sync.status}}", - "short": true - }, - { - "title": "Repository", - "value": "{{.app.spec.source.repoURL}}", - "short": true - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "title": "{{$c.type}}", - "value": "{{$c.message}}", - "short": true - } - {{end}} - ] - }] - deliveryPolicy: Post - groupingKey: "" - notifyBroadcast: false -teams: - facts: | - [{ - "name": "Sync Status", - "value": "{{.app.status.sync.status}}" - }, - { - "name": "Repository", - "value": "{{.app.spec.source.repoURL}}" - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "name": "{{$c.type}}", - "value": "{{$c.message}}" - } - {{end}} - ] - potentialAction: |- - [{ - "@type":"OpenUri", - "name":"Open Application", - "targets":[{ - "os":"default", - "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}" - }] - }, - { - "@type":"OpenUri", - "name":"Open Repository", - "targets":[{ - "os":"default", - "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" - }] - }] - title: Application {{.app.metadata.name}} sync status is 'Unknown'` - - notificationsConfig["template.app-sync-succeeded"] = `email: - subject: Application {{.app.metadata.name}} has been successfully synced. -message: | - {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} has been successfully synced at {{.app.status.operationState.finishedAt}}. - Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true . -slack: - attachments: | - [{ - "title": "{{ .app.metadata.name}}", - "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", - "color": "#18be52", - "fields": [ - { - "title": "Sync Status", - "value": "{{.app.status.sync.status}}", - "short": true - }, - { - "title": "Repository", - "value": "{{.app.spec.source.repoURL}}", - "short": true - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "title": "{{$c.type}}", - "value": "{{$c.message}}", - "short": true - } - {{end}} - ] - }] - deliveryPolicy: Post - groupingKey: "" - notifyBroadcast: false -teams: - facts: | - [{ - "name": "Sync Status", - "value": "{{.app.status.sync.status}}" - }, - { - "name": "Synced at", - "value": "{{.app.status.operationState.finishedAt}}" - }, - { - "name": "Repository", - "value": "{{.app.spec.source.repoURL}}" - } - {{range $index, $c := .app.status.conditions}} - {{if not $index}},{{end}} - {{if $index}},{{end}} - { - "name": "{{$c.type}}", - "value": "{{$c.message}}" - } - {{end}} - ] - potentialAction: |- - [{ - "@type":"OpenUri", - "name":"Operation Details", - "targets":[{ - "os":"default", - "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" - }] - }, - { - "@type":"OpenUri", - "name":"Open Repository", - "targets":[{ - "os":"default", - "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" - }] - }] - themeColor: '#000080' - title: Application {{.app.metadata.name}} has been successfully synced` - - // configure default notifications triggers - - notificationsConfig["trigger.on-created"] = `- description: Application is created. - oncePer: app.metadata.name - send: - - app-created - when: "true"` - - notificationsConfig["trigger.on-deleted"] = `- description: Application is deleted. - oncePer: app.metadata.name - send: - - app-deleted - when: app.metadata.deletionTimestamp != nil` - - notificationsConfig["trigger.on-deployed"] = `- description: Application is synced and healthy. Triggered once per commit. - oncePer: app.status.operationState.syncResult.revision - send: - - app-deployed - when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status - == 'Healthy'` - - notificationsConfig["trigger.on-health-degraded"] = `- description: Application has degraded - send: - - app-health-degraded - when: app.status.health.status == 'Degraded'` - - notificationsConfig["trigger.on-sync-failed"] = `- description: Application syncing has failed - send: - - app-sync-failed - when: app.status.operationState.phase in ['Error', 'Failed']` - - notificationsConfig["trigger.on-sync-running"] = `- description: Application is being synced - send: - - app-sync-running - when: app.status.operationState.phase in ['Running']` - - notificationsConfig["trigger.on-sync-status-unknown"] = `- description: Application status is 'Unknown' - send: - - app-sync-status-unknown - when: app.status.sync.status == 'Unknown'` - - notificationsConfig["trigger.on-sync-succeeded"] = `- description: Application syncing has succeeded - send: - - app-sync-succeeded - when: app.status.operationState.phase in ['Succeeded']` - - return notificationsConfig -} - -// getArgoCDNotificationsControllerReplicas will return the size value for the argocd-notifications-controller replica count if it -// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or -// replicas value is < 0. -func getArgoCDNotificationsControllerReplicas(cr *argoproj.ArgoCD) *int32 { - if cr.Spec.Notifications.Replicas != nil && *cr.Spec.Notifications.Replicas >= 0 { - return cr.Spec.Notifications.Replicas - } - - return nil -} - -const ( - ApplicationSetGitlabSCMTlsCertPath = "/app/tls/scm/cert" -) - -// getArgoApplicationSetCommand will return the command for the ArgoCD ApplicationSet component. -func getArgoApplicationSetCommand(cr *argoproj.ArgoCD) []string { - cmd := make([]string, 0) - - cmd = append(cmd, "entrypoint.sh") - cmd = append(cmd, "argocd-applicationset-controller") - - if cr.Spec.Repo.IsEnabled() { - cmd = append(cmd, "--argocd-repo-server", getRepoServerAddress(cr)) - } else { - log.Info("Repo Server is disabled. This would affect the functioning of ApplicationSet Controller.") - } - - cmd = append(cmd, "--loglevel") - cmd = append(cmd, getLogLevel(cr.Spec.ApplicationSet.LogLevel)) - - if cr.Spec.ApplicationSet.SCMRootCAConfigMap != "" { - cmd = append(cmd, "--scm-root-ca-path") - cmd = append(cmd, ApplicationSetGitlabSCMTlsCertPath) - } - - // ApplicationSet command arguments provided by the user - extraArgs := cr.Spec.ApplicationSet.ExtraCommandArgs - err := isMergable(extraArgs, cmd) - if err != nil { - return cmd - } - - cmd = append(cmd, extraArgs...) - - return cmd -} - -func (r *ReconcileArgoCD) reconcileApplicationSetController(cr *argoproj.ArgoCD) error { - - log.Info("reconciling applicationset serviceaccounts") - sa, err := r.reconcileApplicationSetServiceAccount(cr) - if err != nil { - return err - } - - log.Info("reconciling applicationset roles") - role, err := r.reconcileApplicationSetRole(cr) - if err != nil { - return err - } - - log.Info("reconciling applicationset role bindings") - if err := r.reconcileApplicationSetRoleBinding(cr, role, sa); err != nil { - return err - } - - log.Info("reconciling applicationset deployments") - if err := r.reconcileApplicationSetDeployment(cr, sa); err != nil { - return err - } - - log.Info("reconciling applicationset service") - if err := r.reconcileApplicationSetService(cr); err != nil { - return err - } - - return nil -} - -// reconcileApplicationControllerDeployment will ensure the Deployment resource is present for the ArgoCD Application Controller component. -func (r *ReconcileArgoCD) reconcileApplicationSetDeployment(cr *argoproj.ArgoCD, sa *corev1.ServiceAccount) error { - deploy := newDeploymentWithSuffix("applicationset-controller", "controller", cr) - - setAppSetLabels(&deploy.ObjectMeta) - - podSpec := &deploy.Spec.Template.Spec - - // sa would be nil when spec.applicationset.enabled = false - if sa != nil { - podSpec.ServiceAccountName = sa.ObjectMeta.Name - } - podSpec.Volumes = []corev1.Volume{ - { - Name: "ssh-known-hosts", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDKnownHostsConfigMapName, - }, - }, - }, - }, - { - Name: "tls-certs", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDTLSCertsConfigMapName, - }, - }, - }, - }, - { - Name: "gpg-keys", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDGPGKeysConfigMapName, - }, - }, - }, - }, - { - Name: "gpg-keyring", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: "tmp", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - } - addSCMGitlabVolumeMount := false - if scmRootCAConfigMapName := getSCMRootCAConfigMapName(cr); scmRootCAConfigMapName != "" { - cm := newConfigMapWithName(scmRootCAConfigMapName, cr) - if argoutil.IsObjectFound(r.Client, cr.Namespace, cr.Spec.ApplicationSet.SCMRootCAConfigMap, cm) { - addSCMGitlabVolumeMount = true - podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ - Name: "appset-gitlab-scm-tls-cert", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName, - }, - }, - }, - }) - } - } - - podSpec.Containers = []corev1.Container{ - applicationSetContainer(cr, addSCMGitlabVolumeMount), - } - AddSeccompProfileForOpenShift(r.Client, podSpec) - - if existing := newDeploymentWithSuffix("applicationset-controller", "controller", cr); argoutil.IsObjectFound(r.Client, cr.Namespace, existing.Name, existing) { - - if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - err := r.Client.Delete(context.TODO(), existing) - return err - } - - existingSpec := existing.Spec.Template.Spec - - deploymentsDifferent := !reflect.DeepEqual(existingSpec.Containers[0], podSpec.Containers) || - !reflect.DeepEqual(existingSpec.Volumes, podSpec.Volumes) || - existingSpec.ServiceAccountName != podSpec.ServiceAccountName || - !reflect.DeepEqual(existing.Labels, deploy.Labels) || - !reflect.DeepEqual(existing.Spec.Template.Labels, deploy.Spec.Template.Labels) || - !reflect.DeepEqual(existing.Spec.Selector, deploy.Spec.Selector) || - !reflect.DeepEqual(existing.Spec.Template.Spec.NodeSelector, deploy.Spec.Template.Spec.NodeSelector) || - !reflect.DeepEqual(existing.Spec.Template.Spec.Tolerations, deploy.Spec.Template.Spec.Tolerations) - - // If the Deployment already exists, make sure the values we care about are up-to-date - if deploymentsDifferent { - existing.Spec.Template.Spec.Containers = podSpec.Containers - existing.Spec.Template.Spec.Volumes = podSpec.Volumes - existing.Spec.Template.Spec.ServiceAccountName = podSpec.ServiceAccountName - existing.Labels = deploy.Labels - existing.Spec.Template.Labels = deploy.Spec.Template.Labels - existing.Spec.Selector = deploy.Spec.Selector - existing.Spec.Template.Spec.NodeSelector = deploy.Spec.Template.Spec.NodeSelector - existing.Spec.Template.Spec.Tolerations = deploy.Spec.Template.Spec.Tolerations - return r.Client.Update(context.TODO(), existing) - } - return nil // Deployment found with nothing to do, move along... - } - - if !cr.Spec.ApplicationSet.IsEnabled() { - return nil - } - - if err := controllerutil.SetControllerReference(cr, deploy, r.Scheme); err != nil { - return err - } - return r.Client.Create(context.TODO(), deploy) - -} - -func applicationSetContainer(cr *argoproj.ArgoCD, addSCMGitlabVolumeMount bool) corev1.Container { - // Global proxy env vars go first - appSetEnv := []corev1.EnvVar{{ - Name: "NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "metadata.namespace", - }, - }, - }} - - // Merge ApplicationSet env vars provided by the user - // User should be able to override the default NAMESPACE environmental variable - appSetEnv = argoutil.EnvMerge(cr.Spec.ApplicationSet.Env, appSetEnv, true) - // Environment specified in the CR take precedence over everything else - appSetEnv = argoutil.EnvMerge(appSetEnv, proxyEnvVars(), false) - - container := corev1.Container{ - Command: getArgoApplicationSetCommand(cr), - Env: appSetEnv, - Image: getApplicationSetContainerImage(cr), - ImagePullPolicy: corev1.PullAlways, - Name: "argocd-applicationset-controller", - Resources: getApplicationSetResources(cr), - VolumeMounts: []corev1.VolumeMount{ - { - Name: "ssh-known-hosts", - MountPath: "/app/config/ssh", - }, - { - Name: "tls-certs", - MountPath: "/app/config/tls", - }, - { - Name: "gpg-keys", - MountPath: "/app/config/gpg/source", - }, - { - Name: "gpg-keyring", - MountPath: "/app/config/gpg/keys", - }, - { - Name: "tmp", - MountPath: "/tmp", - }, - }, - Ports: []corev1.ContainerPort{ - { - ContainerPort: 7000, - Name: "webhook", - }, - { - ContainerPort: 8080, - Name: "metrics", - }, - }, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{ - "ALL", - }, - }, - AllowPrivilegeEscalation: boolPtr(false), - ReadOnlyRootFilesystem: boolPtr(true), - RunAsNonRoot: boolPtr(true), - }, - } - if addSCMGitlabVolumeMount { - container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ - Name: "appset-gitlab-scm-tls-cert", - MountPath: ApplicationSetGitlabSCMTlsCertPath, - }) - } - return container -} - -func (r *ReconcileArgoCD) reconcileApplicationSetServiceAccount(cr *argoproj.ArgoCD) (*corev1.ServiceAccount, error) { - - sa := newServiceAccountWithName("applicationset-controller", cr) - setAppSetLabels(&sa.ObjectMeta) - - exists := true - if err := argoutil.FetchObject(r.Client, cr.Namespace, sa.Name, sa); err != nil { - if !apierrors.IsNotFound(err) { - return nil, err - } - exists = false - } - - if exists { - if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - err := r.Client.Delete(context.TODO(), sa) - return nil, err - } - return sa, nil - } - - if err := controllerutil.SetControllerReference(cr, sa, r.Scheme); err != nil { - return nil, err - } - - if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - return nil, nil - } - - err := r.Client.Create(context.TODO(), sa) - if err != nil { - return nil, err - } - - return sa, err -} - -func (r *ReconcileArgoCD) reconcileApplicationSetRole(cr *argoproj.ArgoCD) (*rbacv1.Role, error) { - - policyRules := []rbacv1.PolicyRule{ - - // ApplicationSet - { - APIGroups: []string{"argoproj.io"}, - Resources: []string{ - "applications", - "applicationsets", - "appprojects", - "applicationsets/finalizers", - }, - Verbs: []string{ - "create", - "delete", - "get", - "list", - "patch", - "update", - "watch", - }, - }, - // ApplicationSet Status - { - APIGroups: []string{"argoproj.io"}, - Resources: []string{ - "applicationsets/status", - }, - Verbs: []string{ - "get", - "patch", - "update", - }, - }, - - // Events - { - APIGroups: []string{""}, - Resources: []string{ - "events", - }, - Verbs: []string{ - "create", - "delete", - "get", - "list", - "patch", - "update", - "watch", - }, - }, - - // Read Secrets/ConfigMaps - { - APIGroups: []string{""}, - Resources: []string{ - "secrets", - "configmaps", - }, - Verbs: []string{ - "get", - "list", - "watch", - }, - }, - - // Read Deployments - { - APIGroups: []string{"apps", "extensions"}, - Resources: []string{ - "deployments", - }, - Verbs: []string{ - "get", - "list", - "watch", - }, - }, - } - - role := newRole("applicationset-controller", policyRules, cr) - setAppSetLabels(&role.ObjectMeta) - - err := r.Client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: cr.Namespace}, role) - if err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("failed to reconcile the role for the service account associated with %s : %s", role.Name, err) - } - if apierrors.IsNotFound(err) && cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - return nil, nil - } - if err = controllerutil.SetControllerReference(cr, role, r.Scheme); err != nil { - return nil, err - } - return role, r.Client.Create(context.TODO(), role) - } - if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - return nil, r.Client.Delete(context.TODO(), role) - } - - role.Rules = policyRules - if err = controllerutil.SetControllerReference(cr, role, r.Scheme); err != nil { - return nil, err - } - return role, r.Client.Update(context.TODO(), role) -} - -func (r *ReconcileArgoCD) reconcileApplicationSetRoleBinding(cr *argoproj.ArgoCD, role *rbacv1.Role, sa *corev1.ServiceAccount) error { - - name := "applicationset-controller" - - // get expected name - roleBinding := newRoleBindingWithname(name, cr) - - // fetch existing rolebinding by name - roleBindingExists := true - if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: cr.Namespace}, roleBinding); err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get the rolebinding associated with %s : %s", name, err) - } - if apierrors.IsNotFound(err) && cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - return nil - } - roleBindingExists = false - } - - if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { - return r.Client.Delete(context.TODO(), roleBinding) - } - - setAppSetLabels(&roleBinding.ObjectMeta) - - roleBinding.RoleRef = rbacv1.RoleRef{ - APIGroup: rbacv1.GroupName, - Kind: "Role", - Name: role.Name, - } - - roleBinding.Subjects = []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: sa.Name, - Namespace: sa.Namespace, - }, - } - - if err := controllerutil.SetControllerReference(cr, roleBinding, r.Scheme); err != nil { - return err - } - - if roleBindingExists { - return r.Client.Update(context.TODO(), roleBinding) - } - - return r.Client.Create(context.TODO(), roleBinding) -} - -func getApplicationSetContainerImage(cr *argoproj.ArgoCD) string { - defaultImg, defaultTag := false, false - - img := "" - tag := "" - - // First pull from spec, if it exists - if cr.Spec.ApplicationSet != nil { - img = cr.Spec.ApplicationSet.Image - tag = cr.Spec.ApplicationSet.Version - } - - // If spec is empty, use the defaults - if img == "" { - img = common.ArgoCDDefaultArgoImage - defaultImg = true - } - if tag == "" { - tag = common.ArgoCDDefaultArgoVersion - defaultTag = true - } - - // If an env var is specified then use that, but don't override the spec values (if they are present) - if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) { - return e - } - return argoutil.CombineImageTag(img, tag) -} - -// getApplicationSetResources will return the ResourceRequirements for the Application Sets container. -func getApplicationSetResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements { - resources := corev1.ResourceRequirements{} - - // Allow override of resource requirements from CR - if cr.Spec.ApplicationSet.Resources != nil { - resources = *cr.Spec.ApplicationSet.Resources - } - - return resources -} - -func setAppSetLabels(obj *metav1.ObjectMeta) { - obj.Labels["app.kubernetes.io/name"] = "argocd-applicationset-controller" - obj.Labels["app.kubernetes.io/part-of"] = "argocd-applicationset" - obj.Labels["app.kubernetes.io/component"] = "controller" -} - -// reconcileApplicationSetService will ensure that the Service is present for the ApplicationSet webhook and metrics component. -func (r *ReconcileArgoCD) reconcileApplicationSetService(cr *argoproj.ArgoCD) error { - log.Info("reconciling applicationset service") - - svc := newServiceWithSuffix(common.ApplicationSetServiceNameSuffix, common.ApplicationSetServiceNameSuffix, cr) - if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() { - - if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { - err := argoutil.FetchObject(r.Client, cr.Namespace, svc.Name, svc) - if err != nil { - return err - } - log.Info(fmt.Sprintf("Deleting applicationset controller service %s as applicationset is disabled", svc.Name)) - err = r.Delete(context.TODO(), svc) - if err != nil { - return err - } - } - return nil - } else { - if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { - return nil // Service found, do nothing - } - } - svc.Spec.Ports = []corev1.ServicePort{ - { - Name: "webhook", - Port: 7000, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(7000), - }, { - Name: "metrics", - Port: 8080, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(8080), - }, - } - - svc.Spec.Selector = map[string]string{ - common.ArgoCDKeyName: nameWithSuffix(common.ApplicationSetServiceNameSuffix, cr), - } - - if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { - return err - } - return r.Client.Create(context.TODO(), svc) -} - // isMergable returns error if any of the extraArgs is already part of the default command Arguments. func isMergable(extraArgs []string, cmd []string) error { if len(extraArgs) > 0 { @@ -2214,3 +576,134 @@ func newRouteWithName(name string, cr *argoproj.ArgoCD) *routev1.Route { func newRouteWithSuffix(suffix string, cr *argoproj.ArgoCD) *routev1.Route { return newRouteWithName(fmt.Sprintf("%s-%s", cr.Name, suffix), cr) } + +// old reconcile function - leave as is +func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + + reconcileStartTS := time.Now() + defer func() { + ReconcileTime.WithLabelValues(request.Namespace).Observe(time.Since(reconcileStartTS).Seconds()) + }() + + reqLogger := ctrlLog.FromContext(ctx, "namespace", request.Namespace, "name", request.Name) + reqLogger.Info("Reconciling ArgoCD") + + argocd := &argoproj.ArgoCD{} + err := r.Client.Get(ctx, request.NamespacedName, argocd) + if err != nil { + if apierrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // Fetch labelSelector from r.LabelSelector (command-line option) + labelSelector, err := labels.Parse(r.LabelSelector) + if err != nil { + reqLogger.Info(fmt.Sprintf("error parsing the labelSelector '%s'.", labelSelector)) + return reconcile.Result{}, err + } + // Match the value of labelSelector from ReconcileArgoCD to labels from the argocd instance + if !labelSelector.Matches(labels.Set(argocd.Labels)) { + reqLogger.Info(fmt.Sprintf("the ArgoCD instance '%s' does not match the label selector '%s' and skipping for reconciliation", request.NamespacedName, r.LabelSelector)) + return reconcile.Result{}, fmt.Errorf("Error: failed to reconcile ArgoCD instance: '%s'", request.NamespacedName) + } + + newPhase := argocd.Status.Phase + // If we discover a new Argo CD instance in a previously un-seen namespace + // we add it to the map and increment active instance count by phase + // as well as total active instance count + if _, ok := ActiveInstanceMap[request.Namespace]; !ok { + if newPhase != "" { + ActiveInstanceMap[request.Namespace] = newPhase + ActiveInstancesByPhase.WithLabelValues(newPhase).Inc() + ActiveInstancesTotal.Inc() + } + } else { + // If we discover an existing instance's phase has changed since we last saw it + // increment instance count with new phase and decrement instance count with old phase + // update the phase in corresponding map entry + // total instance count remains the same + if oldPhase := ActiveInstanceMap[argocd.Namespace]; oldPhase != newPhase { + ActiveInstanceMap[argocd.Namespace] = newPhase + ActiveInstancesByPhase.WithLabelValues(newPhase).Inc() + ActiveInstancesByPhase.WithLabelValues(oldPhase).Dec() + } + } + + ActiveInstanceReconciliationCount.WithLabelValues(argocd.Namespace).Inc() + + if argocd.GetDeletionTimestamp() != nil { + + // Argo CD instance marked for deletion; remove entry from activeInstances map and decrement active instance count + // by phase as well as total + delete(ActiveInstanceMap, argocd.Namespace) + ActiveInstancesByPhase.WithLabelValues(newPhase).Dec() + ActiveInstancesTotal.Dec() + ActiveInstanceReconciliationCount.DeleteLabelValues(argocd.Namespace) + ReconcileTime.DeletePartialMatch(prometheus.Labels{"namespace": argocd.Namespace}) + + if argocd.IsDeletionFinalizerPresent() { + if err := r.deleteClusterResources(argocd); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to delete ClusterResources: %w", err) + } + + if isRemoveManagedByLabelOnArgoCDDeletion() { + if err := r.removeManagedByLabelFromNamespaces(argocd.Namespace); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to remove label from namespace[%v], error: %w", argocd.Namespace, err) + } + } + + if err := r.removeUnmanagedSourceNamespaceResources(argocd); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to remove resources from sourceNamespaces, error: %w", err) + } + + if err := r.removeDeletionFinalizer(argocd); err != nil { + return reconcile.Result{}, err + } + + // remove namespace of deleted Argo CD instance from deprecationEventEmissionTracker (if exists) so that if another instance + // is created in the same namespace in the future, that instance is appropriately tracked + delete(DeprecationEventEmissionTracker, argocd.Namespace) + } + return reconcile.Result{}, nil + } + + if !argocd.IsDeletionFinalizerPresent() { + if err := r.addDeletionFinalizer(argocd); err != nil { + return reconcile.Result{}, err + } + } + + // get the latest version of argocd instance before reconciling + if err = r.Client.Get(ctx, request.NamespacedName, argocd); err != nil { + return reconcile.Result{}, err + } + + if err = r.setManagedNamespaces(argocd); err != nil { + return reconcile.Result{}, err + } + + if err = r.setManagedSourceNamespaces(argocd); err != nil { + return reconcile.Result{}, err + } + + if err := r.reconcileResources(argocd); err != nil { + // Error reconciling ArgoCD sub-resources - requeue the request. + return reconcile.Result{}, err + } + + // Return and don't requeue + return reconcile.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ReconcileArgoCD) SetupWithManager(mgr ctrl.Manager) error { + bldr := ctrl.NewControllerManagedBy(mgr) + r.setResourceWatches(bldr, r.clusterResourceMapper, r.tlsSecretMapper, r.namespaceResourceMapper, r.clusterSecretResourceMapper, r.applicationSetSCMTLSConfigMapMapper) + return bldr.Complete(r) +} diff --git a/controllers/argocd/TOBEREMOVED_test.go b/controllers/argocd/TOBEREMOVED_test.go index 0dc7a3c62..57053452f 100644 --- a/controllers/argocd/TOBEREMOVED_test.go +++ b/controllers/argocd/TOBEREMOVED_test.go @@ -15,7 +15,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" logf "sigs.k8s.io/controller-runtime/pkg/log" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" @@ -1200,3 +1202,56 @@ func TestArgoCDApplicationSetEnv(t *testing.T) { assert.Equal(t, defaultEnv, deployment.Spec.Template.Spec.Containers[0].Env) } + +type SchemeOpt func(*runtime.Scheme) error + +func makeTestReconcilerClient(sch *runtime.Scheme, resObjs, subresObjs []client.Object, runtimeObj []runtime.Object) client.Client { + client := fake.NewClientBuilder().WithScheme(sch) + if len(resObjs) > 0 { + client = client.WithObjects(resObjs...) + } + if len(subresObjs) > 0 { + client = client.WithStatusSubresource(subresObjs...) + } + if len(runtimeObj) > 0 { + client = client.WithRuntimeObjects(runtimeObj...) + } + return client.Build() +} + +func makeTestReconcilerScheme(sOpts ...SchemeOpt) *runtime.Scheme { + s := scheme.Scheme + for _, opt := range sOpts { + _ = opt(s) + } + + return s +} + +type namespaceOpt func(*corev1.Namespace) + +func makeTestNs(opts ...namespaceOpt) *corev1.Namespace { + a := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ns", + Labels: make(map[string]string), + }, + } + for _, o := range opts { + o(a) + } + return a +} + +func makeTestArgoCD(opts ...argoCDOpt) *argoproj.ArgoCD { + a := &argoproj.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: testArgoCDName, + Namespace: testNamespace, + }, + } + for _, o := range opts { + o(a) + } + return a +} diff --git a/controllers/argocd/appcontroller/appcontroller.go b/controllers/argocd/appcontroller/appcontroller.go index ccbd27ce9..4c98ebeca 100644 --- a/controllers/argocd/appcontroller/appcontroller.go +++ b/controllers/argocd/appcontroller/appcontroller.go @@ -23,3 +23,8 @@ func (acr *AppControllerReconciler) Reconcile() error { // controller logic goes here return nil } + +// TO DO: finish this +func (acr *AppControllerReconciler) TriggerRollout(key string) error { + return acr.TriggerStatefulSetRollout("", "", key) +} diff --git a/controllers/argocd/appcontroller/statefulset.go b/controllers/argocd/appcontroller/statefulset.go new file mode 100644 index 000000000..d56ad4e68 --- /dev/null +++ b/controllers/argocd/appcontroller/statefulset.go @@ -0,0 +1,7 @@ +package appcontroller + +import "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + +func (acr *AppControllerReconciler) TriggerStatefulSetRollout(name, namespace, key string) error { + return argocdcommon.TriggerStatefulSetRollout(name, namespace, key, acr.Client) +} diff --git a/controllers/argocd/appcontroller/status.go b/controllers/argocd/appcontroller/status.go new file mode 100644 index 000000000..205f4606b --- /dev/null +++ b/controllers/argocd/appcontroller/status.go @@ -0,0 +1,32 @@ +package appcontroller + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// reconcileStatus will ensure that the app-controller status is updated for the given ArgoCD instance +func (acr *AppControllerReconciler) ReconcileStatus() error { + + // TO DO + + return acr.updateInstanceStatus() +} + +func (acr *AppControllerReconciler) updateInstanceStatus() error { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := acr.Client.Status().Update(context.TODO(), acr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/appcontroller_TOBEREMOVED.go b/controllers/argocd/appcontroller_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/appcontroller_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/applicationset/applicationset.go b/controllers/argocd/applicationset/applicationset.go index f7e3a7a6f..015c3d102 100644 --- a/controllers/argocd/applicationset/applicationset.go +++ b/controllers/argocd/applicationset/applicationset.go @@ -1,23 +1,23 @@ package applicationset import ( - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/argoutil" "github.com/argoproj-labs/argocd-operator/pkg/openshift" + "github.com/argoproj-labs/argocd-operator/pkg/util" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" ) type ApplicationSetReconciler struct { - Client client.Client - Scheme *runtime.Scheme - Instance *argoproj.ArgoCD - Logger logr.Logger + Client client.Client + Scheme *runtime.Scheme + Instance *argoproj.ArgoCD + Logger *util.Logger + RepoServer RepoServerController } var ( @@ -26,9 +26,6 @@ var ( ) func (asr *ApplicationSetReconciler) Reconcile() error { - - asr.Logger = ctrl.Log.WithName(AppSetControllerComponent).WithValues("instance", asr.Instance.Name, "instance-namespace", asr.Instance.Namespace) - resourceName = argoutil.GenerateUniqueResourceName(asr.Instance.Name, asr.Instance.Namespace, AppSetControllerComponent) resourceLabels = common.DefaultResourceLabels(resourceName, asr.Instance.Name, AppSetControllerComponent) diff --git a/controllers/argocd/applicationset/applicationset_test.go b/controllers/argocd/applicationset/applicationset_test.go index a504e6084..8495887ba 100644 --- a/controllers/argocd/applicationset/applicationset_test.go +++ b/controllers/argocd/applicationset/applicationset_test.go @@ -7,15 +7,14 @@ import ( "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -var testExpectedLabels = common.DefaultResourceLabels(argocdcommon.TestArgoCDName, argocdcommon.TestNamespace, AppSetControllerComponent) +var testExpectedLabels = common.DefaultResourceLabels(test.TestArgoCDName, test.TestNamespace, AppSetControllerComponent) func makeTestApplicationSetReconciler(t *testing.T, webhookServerRouteEnabled bool, objs ...runtime.Object) *ApplicationSetReconciler { s := scheme.Scheme @@ -24,12 +23,11 @@ func makeTestApplicationSetReconciler(t *testing.T, webhookServerRouteEnabled bo assert.NoError(t, argoproj.AddToScheme(s)) cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build() - logger := ctrl.Log.WithName(AppSetControllerComponent) return &ApplicationSetReconciler{ Client: cl, Scheme: s, - Instance: argocdcommon.MakeTestArgoCD(func(a *argoproj.ArgoCD) { + Instance: test.MakeTestArgoCD(nil, func(a *argoproj.ArgoCD) { a.Spec.ApplicationSet = &argoproj.ArgoCDApplicationSet{ WebhookServer: argoproj.WebhookServerSpec{ Route: argoproj.ArgoCDRouteSpec{ @@ -38,6 +36,5 @@ func makeTestApplicationSetReconciler(t *testing.T, webhookServerRouteEnabled bo }, } }), - Logger: logger, } } diff --git a/controllers/argocd/applicationset/deployment.go b/controllers/argocd/applicationset/deployment.go index 073480aec..343711c06 100644 --- a/controllers/argocd/applicationset/deployment.go +++ b/controllers/argocd/applicationset/deployment.go @@ -5,7 +5,6 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/reposerver" "github.com/argoproj-labs/argocd-operator/pkg/argoutil" "github.com/argoproj-labs/argocd-operator/pkg/cluster" "github.com/argoproj-labs/argocd-operator/pkg/mutation" @@ -29,7 +28,7 @@ func (asr *ApplicationSetReconciler) reconcileDeployment() error { desiredDeployment, err := workloads.RequestDeployment(deploymentRequest) if err != nil { asr.Logger.Error(err, "reconcileDeployment: failed to request deployment", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) - asr.Logger.V(1).Info("reconcileDeployment: one or more mutations could not be applied") + asr.Logger.Debug("reconcileDeployment: one or more mutations could not be applied") return err } @@ -60,36 +59,32 @@ func (asr *ApplicationSetReconciler) reconcileDeployment() error { asr.Logger.Error(err, "reconcileDeployment: failed to create deployment", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) return err } - asr.Logger.V(0).Info("reconcileDeployment: deployment created", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) + asr.Logger.Info("deployment created", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) return nil } deploymentChanged := false - fieldsToCompare := []struct { - existing, desired interface{} - extraAction func() - }{ - {&existingDeployment.Spec.Template.Spec.Containers[0].Image, &desiredDeployment.Spec.Template.Spec.Containers[0].Image, - func() { + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Image, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Image, + ExtraAction: func() { existingDeployment.Spec.Template.ObjectMeta.Labels[common.ImageUpgradedKey] = time.Now().UTC().Format(common.TimeFormatMST) }, }, - {&existingDeployment.Spec.Template.Spec.Containers[0].Command, &desiredDeployment.Spec.Template.Spec.Containers[0].Command, nil}, - {&existingDeployment.Spec.Template.Spec.Containers[0].Env, &desiredDeployment.Spec.Template.Spec.Containers[0].Env, nil}, - {&existingDeployment.Spec.Template.Spec.Containers[0].Resources, &desiredDeployment.Spec.Template.Spec.Containers[0].Resources, nil}, - {&existingDeployment.Spec.Template.Spec.Volumes, &desiredDeployment.Spec.Template.Spec.Volumes, nil}, - {&existingDeployment.Spec.Template.Spec.NodeSelector, &desiredDeployment.Spec.Template.Spec.NodeSelector, nil}, - {&existingDeployment.Spec.Template.Spec.Tolerations, &desiredDeployment.Spec.Template.Spec.Tolerations, nil}, - {&existingDeployment.Spec.Template.Spec.ServiceAccountName, &desiredDeployment.Spec.Template.Spec.ServiceAccountName, nil}, - {&existingDeployment.Spec.Template.Labels, &desiredDeployment.Spec.Template.Labels, nil}, - {&existingDeployment.Spec.Replicas, &desiredDeployment.Spec.Replicas, nil}, - {&existingDeployment.Spec.Selector, &desiredDeployment.Spec.Selector, nil}, - {&existingDeployment.Labels, &desiredDeployment.Labels, nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Command, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Command, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Env, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Env, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Resources, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Resources, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Volumes, Desired: &desiredDeployment.Spec.Template.Spec.Volumes, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.NodeSelector, Desired: &desiredDeployment.Spec.Template.Spec.NodeSelector, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Tolerations, Desired: &desiredDeployment.Spec.Template.Spec.Tolerations, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.ServiceAccountName, Desired: &desiredDeployment.Spec.Template.Spec.ServiceAccountName, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Labels, Desired: &desiredDeployment.Spec.Template.Labels, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Replicas, Desired: &desiredDeployment.Spec.Replicas, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Selector, Desired: &desiredDeployment.Spec.Selector, ExtraAction: nil}, + {Existing: &existingDeployment.Labels, Desired: &desiredDeployment.Labels, ExtraAction: nil}, + {Existing: &existingDeployment.Annotations, Desired: &desiredDeployment.Annotations, ExtraAction: nil}, } - for _, field := range fieldsToCompare { - argocdcommon.UpdateIfChanged(field.existing, field.desired, field.extraAction, &deploymentChanged) - } + argocdcommon.UpdateIfChanged(fieldsToCompare, &deploymentChanged) if deploymentChanged { @@ -99,7 +94,7 @@ func (asr *ApplicationSetReconciler) reconcileDeployment() error { } } - asr.Logger.V(0).Info("reconcileDeployment: deployment updated", "name", existingDeployment.Name, "namespace", existingDeployment.Namespace) + asr.Logger.Info("deployment updated", "name", existingDeployment.Name, "namespace", existingDeployment.Namespace) return nil } @@ -111,7 +106,7 @@ func (asr *ApplicationSetReconciler) deleteDeployment(name, namespace string) er asr.Logger.Error(err, "DeleteDeployment: failed to delete deployment", "name", name, "namespace", namespace) return err } - asr.Logger.V(0).Info("DeleteDeployment: deployment deleted", "name", name, "namespace", namespace) + asr.Logger.Info("deployment deleted", "name", name, "namespace", namespace) return nil } @@ -184,9 +179,9 @@ func (asr *ApplicationSetReconciler) getArgoApplicationSetCommand() []string { cmd = append(cmd, AppSetController) cmd = append(cmd, ArgoCDRepoServer) - cmd = append(cmd, reposerver.GetRepoServerAddress(resourceName, asr.Instance.Namespace)) + cmd = append(cmd, asr.RepoServer.GetServerAddress()) - cmd = append(cmd, common.LogLevel) + cmd = append(cmd, common.LogLevelCmd) cmd = append(cmd, argoutil.GetLogLevel(asr.Instance.Spec.ApplicationSet.LogLevel)) // ApplicationSet command arguments provided by the user diff --git a/controllers/argocd/applicationset/deployment_test.go b/controllers/argocd/applicationset/deployment_test.go index b4f21d653..288a736fe 100644 --- a/controllers/argocd/applicationset/deployment_test.go +++ b/controllers/argocd/applicationset/deployment_test.go @@ -4,18 +4,17 @@ import ( "context" "testing" + "github.com/argoproj-labs/argocd-operator/tests/test" "github.com/stretchr/testify/assert" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" - appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/types" ) func TestApplicationSetReconciler_reconcileDeployment(t *testing.T) { - resourceName = argocdcommon.TestArgoCDName + resourceName = test.TestArgoCDName resourceLabels = testExpectedLabels - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) asr := makeTestApplicationSetReconciler(t, false, ns) existingDeployment := asr.getDesiredDeployment() @@ -36,7 +35,7 @@ func TestApplicationSetReconciler_reconcileDeployment(t *testing.T) { name: "update a deployment", setupClient: func() *ApplicationSetReconciler { outdatedDeployment := existingDeployment - outdatedDeployment.ObjectMeta.Labels = argocdcommon.TestKVP + outdatedDeployment.ObjectMeta.Labels = test.TestKVP return makeTestApplicationSetReconciler(t, false, outdatedDeployment, ns) }, wantErr: false, @@ -55,7 +54,7 @@ func TestApplicationSetReconciler_reconcileDeployment(t *testing.T) { } updatedDeployment := &appsv1.Deployment{} - err = asr.Client.Get(context.TODO(), types.NamespacedName{Name: resourceName, Namespace: argocdcommon.TestNamespace}, updatedDeployment) + err = asr.Client.Get(context.TODO(), types.NamespacedName{Name: resourceName, Namespace: test.TestNamespace}, updatedDeployment) if err != nil { t.Fatalf("Could not get updated Deployment: %v", err) } @@ -65,8 +64,8 @@ func TestApplicationSetReconciler_reconcileDeployment(t *testing.T) { } func TestApplicationSetReconciler_DeleteDeployment(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *ApplicationSetReconciler diff --git a/controllers/argocd/applicationset/interfaces.go b/controllers/argocd/applicationset/interfaces.go new file mode 100644 index 000000000..cb6ccded2 --- /dev/null +++ b/controllers/argocd/applicationset/interfaces.go @@ -0,0 +1,5 @@ +package applicationset + +type RepoServerController interface { + GetServerAddress() string +} diff --git a/controllers/argocd/applicationset/role.go b/controllers/argocd/applicationset/role.go index c6e8f468f..9d98d1f7d 100644 --- a/controllers/argocd/applicationset/role.go +++ b/controllers/argocd/applicationset/role.go @@ -33,7 +33,7 @@ func (asr *ApplicationSetReconciler) reconcileRole() error { desiredRole, err := permissions.RequestRole(roleRequest) if err != nil { asr.Logger.Error(err, "reconcileRole: failed to request role", "name", desiredRole.Name, "namespace", desiredRole.Namespace) - asr.Logger.V(1).Info("reconcileRole: one or more mutations could not be applied") + asr.Logger.Debug("reconcileRole: one or more mutations could not be applied") return err } @@ -64,7 +64,7 @@ func (asr *ApplicationSetReconciler) reconcileRole() error { asr.Logger.Error(err, "reconcileRole: failed to create role", "name", desiredRole.Name, "namespace", desiredRole.Namespace) return err } - asr.Logger.V(0).Info("reconcileRole: role created", "name", desiredRole.Name, "namespace", desiredRole.Namespace) + asr.Logger.Info("role created", "name", desiredRole.Name, "namespace", desiredRole.Namespace) return nil } @@ -75,7 +75,7 @@ func (asr *ApplicationSetReconciler) reconcileRole() error { return err } } - asr.Logger.V(0).Info("reconcileRole: role updated", "name", existingRole.Name, "namespace", existingRole.Namespace) + asr.Logger.Info("role updated", "name", existingRole.Name, "namespace", existingRole.Namespace) return nil } @@ -87,7 +87,7 @@ func (asr *ApplicationSetReconciler) deleteRole(name, namespace string) error { asr.Logger.Error(err, "DeleteRole: failed to delete role", "name", name, "namespace", namespace) return err } - asr.Logger.V(0).Info("DeleteRole: role deleted", "name", name, "namespace", namespace) + asr.Logger.Info("role deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/applicationset/role_test.go b/controllers/argocd/applicationset/role_test.go index a9c421603..122f6f507 100644 --- a/controllers/argocd/applicationset/role_test.go +++ b/controllers/argocd/applicationset/role_test.go @@ -10,20 +10,20 @@ import ( "k8s.io/apimachinery/pkg/types" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" ) func TestApplicationSetReconciler_reconcileRole(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName existingRole := &rbacv1.Role{ TypeMeta: metav1.TypeMeta{ Kind: common.RoleKind, APIVersion: common.APIGroupVersionRbacV1, }, ObjectMeta: metav1.ObjectMeta{ - Name: argocdcommon.TestArgoCDName, - Namespace: argocdcommon.TestNamespace, + Name: test.TestArgoCDName, + Namespace: test.TestNamespace, }, Rules: getPolicyRules(), } @@ -63,7 +63,7 @@ func TestApplicationSetReconciler_reconcileRole(t *testing.T) { } updatedRole := &rbacv1.Role{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, updatedRole) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, updatedRole) if err != nil { t.Fatalf("Could not get updated Role: %v", err) } @@ -73,8 +73,8 @@ func TestApplicationSetReconciler_reconcileRole(t *testing.T) { } func TestApplicationSetReconciler_DeleteRole(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *ApplicationSetReconciler diff --git a/controllers/argocd/applicationset/rolebinding.go b/controllers/argocd/applicationset/rolebinding.go index 852e94d42..6cc81c4e4 100644 --- a/controllers/argocd/applicationset/rolebinding.go +++ b/controllers/argocd/applicationset/rolebinding.go @@ -1,13 +1,15 @@ package applicationset import ( + "reflect" + "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" "github.com/argoproj-labs/argocd-operator/pkg/cluster" "github.com/argoproj-labs/argocd-operator/pkg/permissions" + "github.com/pkg/errors" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -61,7 +63,7 @@ func (asr *ApplicationSetReconciler) reconcileRoleBinding() error { existingRoleBinding, err := permissions.GetRoleBinding(desiredRoleBinding.Name, desiredRoleBinding.Namespace, asr.Client) if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { asr.Logger.Error(err, "reconcileRoleBinding: failed to retrieve roleBinding", "name", desiredRoleBinding.Name, "namespace", desiredRoleBinding.Namespace) return err } @@ -74,36 +76,36 @@ func (asr *ApplicationSetReconciler) reconcileRoleBinding() error { asr.Logger.Error(err, "reconcileRoleBinding: failed to create roleBinding", "name", desiredRoleBinding.Name, "namespace", desiredRoleBinding.Namespace) return err } - asr.Logger.V(0).Info("reconcileRoleBinding: roleBinding created", "name", desiredRoleBinding.Name, "namespace", desiredRoleBinding.Namespace) + asr.Logger.Info("roleBinding created", "name", desiredRoleBinding.Name, "namespace", desiredRoleBinding.Namespace) return nil } - roleBindingChanged := false - fieldsToCompare := []struct { - existing, desired interface{} - }{ - { - &existingRoleBinding.RoleRef, - &desiredRoleBinding.RoleRef, - }, - { - &existingRoleBinding.Subjects, - &desiredRoleBinding.Subjects, - }, + // if roleRef differs, we must delete the rolebinding as kubernetes does not allow updation of roleRef + if !reflect.DeepEqual(existingRoleBinding.RoleRef, desiredRoleBinding.RoleRef) { + asr.Logger.Info("detected drift in roleRef for rolebinding", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) + if err := asr.deleteRoleBinding(resourceName, asr.Instance.Namespace); err != nil { + return errors.Wrapf(err, "reconcileRoleBinding: unable to delete obsolete rolebinding %s", existingRoleBinding.Name) + } + return nil + } + + rbChanged := false + + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existingRoleBinding.Subjects, Desired: &desiredRoleBinding.Subjects, ExtraAction: nil}, } - for _, field := range fieldsToCompare { - argocdcommon.UpdateIfChanged(field.existing, field.desired, nil, &roleBindingChanged) + argocdcommon.UpdateIfChanged(fieldsToCompare, &rbChanged) + + if !rbChanged { + return nil } - if roleBindingChanged { - if err = permissions.UpdateRoleBinding(existingRoleBinding, asr.Client); err != nil { - asr.Logger.Error(err, "reconcileRoleBinding: failed to update roleBinding", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) - return err - } + if err = permissions.UpdateRoleBinding(existingRoleBinding, asr.Client); err != nil { + return errors.Wrapf(err, "reconcileRoleBinding: failed to update role %s", existingRoleBinding.Name) } - asr.Logger.V(0).Info("reconcileRoleBinding: roleBinding updated", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) + asr.Logger.Info("rolebinding updated", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) return nil } @@ -116,6 +118,6 @@ func (asr *ApplicationSetReconciler) deleteRoleBinding(name, namespace string) e asr.Logger.Error(err, "DeleteRole: failed to delete roleBinding", "name", name, "namespace", namespace) return err } - asr.Logger.V(0).Info("DeleteRoleBinding: roleBinding deleted", "name", name, "namespace", namespace) + asr.Logger.Info("roleBinding deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/applicationset/rolebinding_test.go b/controllers/argocd/applicationset/rolebinding_test.go index 67723e656..cdaa16768 100644 --- a/controllers/argocd/applicationset/rolebinding_test.go +++ b/controllers/argocd/applicationset/rolebinding_test.go @@ -5,18 +5,21 @@ import ( "testing" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" ) func TestApplicationSetReconciler_reconcileRoleBinding(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - sa := argocdcommon.MakeTestServiceAccount() - resourceName = argocdcommon.TestArgoCDName + resourceName = test.TestArgoCDName + ns := test.MakeTestNamespace(nil) + sa := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = resourceName + }) tests := []struct { name string @@ -39,8 +42,8 @@ func TestApplicationSetReconciler_reconcileRoleBinding(t *testing.T) { APIVersion: common.APIGroupVersionRbacV1, }, ObjectMeta: metav1.ObjectMeta{ - Name: argocdcommon.TestArgoCDName, - Namespace: argocdcommon.TestNamespace, + Name: test.TestArgoCDName, + Namespace: test.TestNamespace, }, RoleRef: rbacv1.RoleRef{}, Subjects: []rbacv1.Subject{}, @@ -62,20 +65,20 @@ func TestApplicationSetReconciler_reconcileRoleBinding(t *testing.T) { } } updatedRoleBinding := &rbacv1.RoleBinding{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, updatedRoleBinding) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, updatedRoleBinding) if err != nil { t.Fatalf("Could not get updated RoleBinding: %v", err) } - assert.Equal(t, argocdcommon.TestRoleRef, updatedRoleBinding.RoleRef) - assert.Equal(t, argocdcommon.TestSubjects, updatedRoleBinding.Subjects) + assert.Equal(t, test.MakeTestRoleRef(resourceName), updatedRoleBinding.RoleRef) + assert.Equal(t, test.MakeTestSubjects(types.NamespacedName{Name: resourceName, Namespace: test.TestNamespace}), updatedRoleBinding.Subjects) }) } } func TestApplicationSetReconciler_DeleteRoleBinding(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - sa := argocdcommon.MakeTestServiceAccount() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + sa := test.MakeTestServiceAccount() + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *ApplicationSetReconciler diff --git a/controllers/argocd/applicationset/service.go b/controllers/argocd/applicationset/service.go index e4bcf55d9..931194088 100644 --- a/controllers/argocd/applicationset/service.go +++ b/controllers/argocd/applicationset/service.go @@ -33,7 +33,7 @@ func (asr *ApplicationSetReconciler) reconcileService() error { desiredService, err := networking.RequestService(serviceRequest) if err != nil { asr.Logger.Error(err, "reconcileService: failed to request service", "name", desiredService.Name, "namespace", desiredService.Namespace) - asr.Logger.V(1).Info("reconcileService: one or more mutations could not be applied") + asr.Logger.Debug("reconcileService: one or more mutations could not be applied") return err } @@ -64,7 +64,7 @@ func (asr *ApplicationSetReconciler) reconcileService() error { asr.Logger.Error(err, "reconcileService: failed to create service", "name", desiredService.Name, "namespace", desiredService.Namespace) return err } - asr.Logger.V(0).Info("reconcileService: service created", "name", desiredService.Name, "namespace", desiredService.Namespace) + asr.Logger.Info("service created", "name", desiredService.Name, "namespace", desiredService.Namespace) return nil } @@ -79,7 +79,7 @@ func (asr *ApplicationSetReconciler) deleteService(name, namespace string) error asr.Logger.Error(err, "DeleteService: failed to delete service", "name", name, "namespace", namespace) return err } - asr.Logger.V(0).Info("DeleteService: service deleted", "name", name, "namespace", namespace) + asr.Logger.Info("service deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/applicationset/service_test.go b/controllers/argocd/applicationset/service_test.go index f90d8e3fd..03782e9ed 100644 --- a/controllers/argocd/applicationset/service_test.go +++ b/controllers/argocd/applicationset/service_test.go @@ -4,17 +4,16 @@ import ( "context" "testing" + "github.com/argoproj-labs/argocd-operator/tests/test" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" ) func TestApplicationSetReconciler_reconcileService(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - sa := argocdcommon.MakeTestServiceAccount() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + sa := test.MakeTestServiceAccount() + resourceName = test.TestArgoCDName tests := []struct { name string @@ -41,7 +40,7 @@ func TestApplicationSetReconciler_reconcileService(t *testing.T) { } } currentService := &corev1.Service{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, currentService) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, currentService) if err != nil { t.Fatalf("Could not get current Service: %v", err) } @@ -51,9 +50,9 @@ func TestApplicationSetReconciler_reconcileService(t *testing.T) { } func TestApplicationSetReconciler_DeleteService(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - sa := argocdcommon.MakeTestServiceAccount() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + sa := test.MakeTestServiceAccount() + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *ApplicationSetReconciler diff --git a/controllers/argocd/applicationset/serviceaccount.go b/controllers/argocd/applicationset/serviceaccount.go index b91d93e3a..2546e8428 100644 --- a/controllers/argocd/applicationset/serviceaccount.go +++ b/controllers/argocd/applicationset/serviceaccount.go @@ -52,7 +52,7 @@ func (asr *ApplicationSetReconciler) reconcileServiceAccount() error { asr.Logger.Error(err, "reconcileServiceAccount: failed to create serviceAccount", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) return err } - asr.Logger.V(0).Info("reconcileServiceAccount: serviceAccount created", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) + asr.Logger.Info("serviceAccount created", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) return nil } @@ -67,6 +67,6 @@ func (nr *ApplicationSetReconciler) deleteServiceAccount(name, namespace string) nr.Logger.Error(err, "DeleteServiceAccount: failed to delete serviceAccount", "name", name, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteServiceAccount: serviceAccount deleted", "name", name, "namespace", namespace) + nr.Logger.Info("erviceAccount deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/applicationset/serviceaccount_test.go b/controllers/argocd/applicationset/serviceaccount_test.go index 32d532f2b..010964a4f 100644 --- a/controllers/argocd/applicationset/serviceaccount_test.go +++ b/controllers/argocd/applicationset/serviceaccount_test.go @@ -4,16 +4,15 @@ import ( "context" "testing" + "github.com/argoproj-labs/argocd-operator/tests/test" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" ) func TestApplicationSetReconciler_reconcileServiceAccount(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName resourceLabels = testExpectedLabels tests := []struct { @@ -42,7 +41,7 @@ func TestApplicationSetReconciler_reconcileServiceAccount(t *testing.T) { } currentServiceAccount := &corev1.ServiceAccount{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, currentServiceAccount) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, currentServiceAccount) if err != nil { t.Fatalf("Could not get current ServiceAccount: %v", err) } @@ -52,8 +51,8 @@ func TestApplicationSetReconciler_reconcileServiceAccount(t *testing.T) { } func TestApplicationSetReconciler_DeleteServiceAccount(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *ApplicationSetReconciler diff --git a/controllers/argocd/applicationset/status.go b/controllers/argocd/applicationset/status.go new file mode 100644 index 000000000..c5b0aef56 --- /dev/null +++ b/controllers/argocd/applicationset/status.go @@ -0,0 +1,32 @@ +package applicationset + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// reconcileStatus will ensure that the appset controller status is updated for the given ArgoCD instance +func (asr *ApplicationSetReconciler) ReconcileStatus() error { + + // TO DO + + return asr.updateInstanceStatus() +} + +func (asr *ApplicationSetReconciler) updateInstanceStatus() error { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := asr.Client.Status().Update(context.TODO(), asr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/applicationset/webhookroute.go b/controllers/argocd/applicationset/webhookroute.go index 8e41ca612..440d096db 100644 --- a/controllers/argocd/applicationset/webhookroute.go +++ b/controllers/argocd/applicationset/webhookroute.go @@ -56,28 +56,23 @@ func (asr *ApplicationSetReconciler) reconcileWebhookRoute() error { asr.Logger.Error(err, "reconcileRoute: failed to create route", "name", desiredWebhookRoute.Name, "namespace", desiredWebhookRoute.Namespace) return err } - asr.Logger.V(0).Info("reconcileRoute: route created", "name", desiredWebhookRoute.Name, "namespace", desiredWebhookRoute.Namespace) + asr.Logger.Info("route created", "name", desiredWebhookRoute.Name, "namespace", desiredWebhookRoute.Namespace) return nil } webhookRouteChanged := false - fieldsToCompare := []struct { - existing, desired interface{} - extraAction func() - }{ - {&existingRoute.Annotations, &desiredWebhookRoute.Annotations, nil}, - {&existingRoute.Labels, &desiredWebhookRoute.Labels, nil}, - {&existingRoute.Spec.WildcardPolicy, &desiredWebhookRoute.Spec.WildcardPolicy, nil}, - {&existingRoute.Spec.Host, &desiredWebhookRoute.Spec.Host, nil}, - {&existingRoute.Spec.Port, &desiredWebhookRoute.Spec.Port, nil}, - {&existingRoute.Spec.TLS, &desiredWebhookRoute.Spec.TLS, nil}, - {&existingRoute.Spec.To, &desiredWebhookRoute.Spec.To, nil}, + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existingRoute.Annotations, Desired: &desiredWebhookRoute.Annotations, ExtraAction: nil}, + {Existing: &existingRoute.Labels, Desired: &desiredWebhookRoute.Labels, ExtraAction: nil}, + {Existing: &existingRoute.Spec.WildcardPolicy, Desired: &desiredWebhookRoute.Spec.WildcardPolicy, ExtraAction: nil}, + {Existing: &existingRoute.Spec.Host, Desired: &desiredWebhookRoute.Spec.Host, ExtraAction: nil}, + {Existing: &existingRoute.Spec.Port, Desired: &desiredWebhookRoute.Spec.Port, ExtraAction: nil}, + {Existing: &existingRoute.Spec.TLS, Desired: &desiredWebhookRoute.Spec.TLS, ExtraAction: nil}, + {Existing: &existingRoute.Spec.To, Desired: &desiredWebhookRoute.Spec.To, ExtraAction: nil}, } - for _, field := range fieldsToCompare { - argocdcommon.UpdateIfChanged(field.existing, field.desired, field.extraAction, &webhookRouteChanged) - } + argocdcommon.UpdateIfChanged(fieldsToCompare, &webhookRouteChanged) if webhookRouteChanged { if err = openshift.UpdateRoute(existingRoute, asr.Client); err != nil { @@ -86,7 +81,7 @@ func (asr *ApplicationSetReconciler) reconcileWebhookRoute() error { } } - asr.Logger.V(0).Info("reconcileRoute: webhook route updated", "name", desiredWebhookRoute.Name, "namespace", desiredWebhookRoute.Namespace) + asr.Logger.Info("webhook route updated", "name", desiredWebhookRoute.Name, "namespace", desiredWebhookRoute.Namespace) return nil } @@ -99,7 +94,7 @@ func (asr *ApplicationSetReconciler) deleteWebhookRoute(name, namespace string) asr.Logger.Error(err, "DeleteRoute: failed to delete route", "name", name, "namespace", namespace) return err } - asr.Logger.V(0).Info("DeleteRoute: route deleted", "name", name, "namespace", namespace) + asr.Logger.Info("route deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/applicationset/webhookroute_test.go b/controllers/argocd/applicationset/webhookroute_test.go index 846136a79..54cd4329d 100644 --- a/controllers/argocd/applicationset/webhookroute_test.go +++ b/controllers/argocd/applicationset/webhookroute_test.go @@ -4,10 +4,9 @@ import ( "context" "testing" + "github.com/argoproj-labs/argocd-operator/tests/test" "github.com/stretchr/testify/assert" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" - routev1 "github.com/openshift/api/route/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -15,7 +14,7 @@ import ( func TestApplicationSetReconciler_reconcileWebhookRoute(t *testing.T) { resourceLabels = testExpectedLabels - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) asr := makeTestApplicationSetReconciler(t, true, ns) existingWebhookRoute := asr.getDesiredWebhookRoute() @@ -39,7 +38,7 @@ func TestApplicationSetReconciler_reconcileWebhookRoute(t *testing.T) { webhookServerRouteEnabled: true, setupClient: func(webhookServerRouteEnabled bool) *ApplicationSetReconciler { outdatedWebhookRoute := existingWebhookRoute - outdatedWebhookRoute.ObjectMeta.Labels = argocdcommon.TestKVP + outdatedWebhookRoute.ObjectMeta.Labels = test.TestKVP return makeTestApplicationSetReconciler(t, webhookServerRouteEnabled, outdatedWebhookRoute, ns) }, wantErr: false, @@ -58,7 +57,7 @@ func TestApplicationSetReconciler_reconcileWebhookRoute(t *testing.T) { } updatedWebhookRoute := &routev1.Route{} - err = asr.Client.Get(context.TODO(), types.NamespacedName{Name: AppSetWebhookRouteName, Namespace: argocdcommon.TestNamespace}, updatedWebhookRoute) + err = asr.Client.Get(context.TODO(), types.NamespacedName{Name: AppSetWebhookRouteName, Namespace: test.TestNamespace}, updatedWebhookRoute) if err != nil { t.Fatalf("Could not get updated WebhookRoute: %v", err) } @@ -68,7 +67,7 @@ func TestApplicationSetReconciler_reconcileWebhookRoute(t *testing.T) { } func TestApplicationSetReconciler_reconcileWebhookRoute_WebhookServerRouteDisabled(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) tests := []struct { name string @@ -98,7 +97,7 @@ func TestApplicationSetReconciler_reconcileWebhookRoute_WebhookServerRouteDisabl } webhookRoute := &routev1.Route{} - err = asr.Client.Get(context.TODO(), types.NamespacedName{Name: AppSetWebhookRouteName, Namespace: argocdcommon.TestNamespace}, webhookRoute) + err = asr.Client.Get(context.TODO(), types.NamespacedName{Name: AppSetWebhookRouteName, Namespace: test.TestNamespace}, webhookRoute) if err != nil { assert.Equal(t, errors.IsNotFound(err), true) } @@ -107,7 +106,7 @@ func TestApplicationSetReconciler_reconcileWebhookRoute_WebhookServerRouteDisabl } func TestApplicationSetReconciler_deleteWebhookRoute(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) tests := []struct { name string webhookServerRouteEnabled bool diff --git a/controllers/argocd/appset_TOBEREMOVED.go b/controllers/argocd/appset_TOBEREMOVED.go new file mode 100644 index 000000000..c80192954 --- /dev/null +++ b/controllers/argocd/appset_TOBEREMOVED.go @@ -0,0 +1,577 @@ +package argocd + +import ( + "context" + "fmt" + "os" + "reflect" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +const ( + ApplicationSetGitlabSCMTlsCertPath = "/app/tls/scm/cert" +) + +// getArgoApplicationSetCommand will return the command for the ArgoCD ApplicationSet component. +func getArgoApplicationSetCommand(cr *argoproj.ArgoCD) []string { + cmd := make([]string, 0) + + cmd = append(cmd, "entrypoint.sh") + cmd = append(cmd, "argocd-applicationset-controller") + + if cr.Spec.Repo.IsEnabled() { + cmd = append(cmd, "--argocd-repo-server", getRepoServerAddress(cr)) + } else { + log.Info("Repo Server is disabled. This would affect the functioning of ApplicationSet Controller.") + } + + cmd = append(cmd, "--loglevel") + cmd = append(cmd, getLogLevel(cr.Spec.ApplicationSet.LogLevel)) + + if cr.Spec.ApplicationSet.SCMRootCAConfigMap != "" { + cmd = append(cmd, "--scm-root-ca-path") + cmd = append(cmd, ApplicationSetGitlabSCMTlsCertPath) + } + + // ApplicationSet command arguments provided by the user + extraArgs := cr.Spec.ApplicationSet.ExtraCommandArgs + err := isMergable(extraArgs, cmd) + if err != nil { + return cmd + } + + cmd = append(cmd, extraArgs...) + + return cmd +} + +func (r *ReconcileArgoCD) reconcileApplicationSetController(cr *argoproj.ArgoCD) error { + + log.Info("reconciling applicationset serviceaccounts") + sa, err := r.reconcileApplicationSetServiceAccount(cr) + if err != nil { + return err + } + + log.Info("reconciling applicationset roles") + role, err := r.reconcileApplicationSetRole(cr) + if err != nil { + return err + } + + log.Info("reconciling applicationset role bindings") + if err := r.reconcileApplicationSetRoleBinding(cr, role, sa); err != nil { + return err + } + + log.Info("reconciling applicationset deployments") + if err := r.reconcileApplicationSetDeployment(cr, sa); err != nil { + return err + } + + log.Info("reconciling applicationset service") + if err := r.reconcileApplicationSetService(cr); err != nil { + return err + } + + return nil +} + +// reconcileApplicationControllerDeployment will ensure the Deployment resource is present for the ArgoCD Application Controller component. +func (r *ReconcileArgoCD) reconcileApplicationSetDeployment(cr *argoproj.ArgoCD, sa *corev1.ServiceAccount) error { + deploy := newDeploymentWithSuffix("applicationset-controller", "controller", cr) + + setAppSetLabels(&deploy.ObjectMeta) + + podSpec := &deploy.Spec.Template.Spec + + // sa would be nil when spec.applicationset.enabled = false + if sa != nil { + podSpec.ServiceAccountName = sa.ObjectMeta.Name + } + podSpec.Volumes = []corev1.Volume{ + { + Name: "ssh-known-hosts", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDKnownHostsConfigMapName, + }, + }, + }, + }, + { + Name: "tls-certs", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDTLSCertsConfigMapName, + }, + }, + }, + }, + { + Name: "gpg-keys", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDGPGKeysConfigMapName, + }, + }, + }, + }, + { + Name: "gpg-keyring", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "tmp", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + addSCMGitlabVolumeMount := false + if scmRootCAConfigMapName := getSCMRootCAConfigMapName(cr); scmRootCAConfigMapName != "" { + cm := newConfigMapWithName(scmRootCAConfigMapName, cr) + if argoutil.IsObjectFound(r.Client, cr.Namespace, cr.Spec.ApplicationSet.SCMRootCAConfigMap, cm) { + addSCMGitlabVolumeMount = true + podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ + Name: "appset-gitlab-scm-tls-cert", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName, + }, + }, + }, + }) + } + } + + podSpec.Containers = []corev1.Container{ + applicationSetContainer(cr, addSCMGitlabVolumeMount), + } + AddSeccompProfileForOpenShift(r.Client, podSpec) + + if existing := newDeploymentWithSuffix("applicationset-controller", "controller", cr); argoutil.IsObjectFound(r.Client, cr.Namespace, existing.Name, existing) { + + if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + err := r.Client.Delete(context.TODO(), existing) + return err + } + + existingSpec := existing.Spec.Template.Spec + + deploymentsDifferent := !reflect.DeepEqual(existingSpec.Containers[0], podSpec.Containers) || + !reflect.DeepEqual(existingSpec.Volumes, podSpec.Volumes) || + existingSpec.ServiceAccountName != podSpec.ServiceAccountName || + !reflect.DeepEqual(existing.Labels, deploy.Labels) || + !reflect.DeepEqual(existing.Spec.Template.Labels, deploy.Spec.Template.Labels) || + !reflect.DeepEqual(existing.Spec.Selector, deploy.Spec.Selector) || + !reflect.DeepEqual(existing.Spec.Template.Spec.NodeSelector, deploy.Spec.Template.Spec.NodeSelector) || + !reflect.DeepEqual(existing.Spec.Template.Spec.Tolerations, deploy.Spec.Template.Spec.Tolerations) + + // If the Deployment already exists, make sure the values we care about are up-to-date + if deploymentsDifferent { + existing.Spec.Template.Spec.Containers = podSpec.Containers + existing.Spec.Template.Spec.Volumes = podSpec.Volumes + existing.Spec.Template.Spec.ServiceAccountName = podSpec.ServiceAccountName + existing.Labels = deploy.Labels + existing.Spec.Template.Labels = deploy.Spec.Template.Labels + existing.Spec.Selector = deploy.Spec.Selector + existing.Spec.Template.Spec.NodeSelector = deploy.Spec.Template.Spec.NodeSelector + existing.Spec.Template.Spec.Tolerations = deploy.Spec.Template.Spec.Tolerations + return r.Client.Update(context.TODO(), existing) + } + return nil // Deployment found with nothing to do, move along... + } + + if !cr.Spec.ApplicationSet.IsEnabled() { + return nil + } + + if err := controllerutil.SetControllerReference(cr, deploy, r.Scheme); err != nil { + return err + } + return r.Client.Create(context.TODO(), deploy) + +} + +func applicationSetContainer(cr *argoproj.ArgoCD, addSCMGitlabVolumeMount bool) corev1.Container { + // Global proxy env vars go first + appSetEnv := []corev1.EnvVar{{ + Name: "NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }} + + // Merge ApplicationSet env vars provided by the user + // User should be able to override the default NAMESPACE environmental variable + appSetEnv = argoutil.EnvMerge(cr.Spec.ApplicationSet.Env, appSetEnv, true) + // Environment specified in the CR take precedence over everything else + appSetEnv = argoutil.EnvMerge(appSetEnv, proxyEnvVars(), false) + + container := corev1.Container{ + Command: getArgoApplicationSetCommand(cr), + Env: appSetEnv, + Image: getApplicationSetContainerImage(cr), + ImagePullPolicy: corev1.PullAlways, + Name: "argocd-applicationset-controller", + Resources: getApplicationSetResources(cr), + VolumeMounts: []corev1.VolumeMount{ + { + Name: "ssh-known-hosts", + MountPath: "/app/config/ssh", + }, + { + Name: "tls-certs", + MountPath: "/app/config/tls", + }, + { + Name: "gpg-keys", + MountPath: "/app/config/gpg/source", + }, + { + Name: "gpg-keyring", + MountPath: "/app/config/gpg/keys", + }, + { + Name: "tmp", + MountPath: "/tmp", + }, + }, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 7000, + Name: "webhook", + }, + { + ContainerPort: 8080, + Name: "metrics", + }, + }, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + AllowPrivilegeEscalation: boolPtr(false), + ReadOnlyRootFilesystem: boolPtr(true), + RunAsNonRoot: boolPtr(true), + }, + } + if addSCMGitlabVolumeMount { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: "appset-gitlab-scm-tls-cert", + MountPath: ApplicationSetGitlabSCMTlsCertPath, + }) + } + return container +} + +func (r *ReconcileArgoCD) reconcileApplicationSetServiceAccount(cr *argoproj.ArgoCD) (*corev1.ServiceAccount, error) { + + sa := newServiceAccountWithName("applicationset-controller", cr) + setAppSetLabels(&sa.ObjectMeta) + + exists := true + if err := argoutil.FetchObject(r.Client, cr.Namespace, sa.Name, sa); err != nil { + if !apierrors.IsNotFound(err) { + return nil, err + } + exists = false + } + + if exists { + if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + err := r.Client.Delete(context.TODO(), sa) + return nil, err + } + return sa, nil + } + + if err := controllerutil.SetControllerReference(cr, sa, r.Scheme); err != nil { + return nil, err + } + + if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + return nil, nil + } + + err := r.Client.Create(context.TODO(), sa) + if err != nil { + return nil, err + } + + return sa, err +} + +func (r *ReconcileArgoCD) reconcileApplicationSetRole(cr *argoproj.ArgoCD) (*rbacv1.Role, error) { + + policyRules := []rbacv1.PolicyRule{ + + // ApplicationSet + { + APIGroups: []string{"argoproj.io"}, + Resources: []string{ + "applications", + "applicationsets", + "appprojects", + "applicationsets/finalizers", + }, + Verbs: []string{ + "create", + "delete", + "get", + "list", + "patch", + "update", + "watch", + }, + }, + // ApplicationSet Status + { + APIGroups: []string{"argoproj.io"}, + Resources: []string{ + "applicationsets/status", + }, + Verbs: []string{ + "get", + "patch", + "update", + }, + }, + + // Events + { + APIGroups: []string{""}, + Resources: []string{ + "events", + }, + Verbs: []string{ + "create", + "delete", + "get", + "list", + "patch", + "update", + "watch", + }, + }, + + // Read Secrets/ConfigMaps + { + APIGroups: []string{""}, + Resources: []string{ + "secrets", + "configmaps", + }, + Verbs: []string{ + "get", + "list", + "watch", + }, + }, + + // Read Deployments + { + APIGroups: []string{"apps", "extensions"}, + Resources: []string{ + "deployments", + }, + Verbs: []string{ + "get", + "list", + "watch", + }, + }, + } + + role := newRole("applicationset-controller", policyRules, cr) + setAppSetLabels(&role.ObjectMeta) + + err := r.Client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: cr.Namespace}, role) + if err != nil { + if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to reconcile the role for the service account associated with %s : %s", role.Name, err) + } + if apierrors.IsNotFound(err) && cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + return nil, nil + } + if err = controllerutil.SetControllerReference(cr, role, r.Scheme); err != nil { + return nil, err + } + return role, r.Client.Create(context.TODO(), role) + } + if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + return nil, r.Client.Delete(context.TODO(), role) + } + + role.Rules = policyRules + if err = controllerutil.SetControllerReference(cr, role, r.Scheme); err != nil { + return nil, err + } + return role, r.Client.Update(context.TODO(), role) +} + +func (r *ReconcileArgoCD) reconcileApplicationSetRoleBinding(cr *argoproj.ArgoCD, role *rbacv1.Role, sa *corev1.ServiceAccount) error { + + name := "applicationset-controller" + + // get expected name + roleBinding := newRoleBindingWithname(name, cr) + + // fetch existing rolebinding by name + roleBindingExists := true + if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: cr.Namespace}, roleBinding); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get the rolebinding associated with %s : %s", name, err) + } + if apierrors.IsNotFound(err) && cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + return nil + } + roleBindingExists = false + } + + if cr.Spec.ApplicationSet != nil && !cr.Spec.ApplicationSet.IsEnabled() { + return r.Client.Delete(context.TODO(), roleBinding) + } + + setAppSetLabels(&roleBinding.ObjectMeta) + + roleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: role.Name, + } + + roleBinding.Subjects = []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: sa.Name, + Namespace: sa.Namespace, + }, + } + + if err := controllerutil.SetControllerReference(cr, roleBinding, r.Scheme); err != nil { + return err + } + + if roleBindingExists { + return r.Client.Update(context.TODO(), roleBinding) + } + + return r.Client.Create(context.TODO(), roleBinding) +} + +func getApplicationSetContainerImage(cr *argoproj.ArgoCD) string { + defaultImg, defaultTag := false, false + + img := "" + tag := "" + + // First pull from spec, if it exists + if cr.Spec.ApplicationSet != nil { + img = cr.Spec.ApplicationSet.Image + tag = cr.Spec.ApplicationSet.Version + } + + // If spec is empty, use the defaults + if img == "" { + img = common.ArgoCDDefaultArgoImage + defaultImg = true + } + if tag == "" { + tag = common.ArgoCDDefaultArgoVersion + defaultTag = true + } + + // If an env var is specified then use that, but don't override the spec values (if they are present) + if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) { + return e + } + return argoutil.CombineImageTag(img, tag) +} + +// getApplicationSetResources will return the ResourceRequirements for the Application Sets container. +func getApplicationSetResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements { + resources := corev1.ResourceRequirements{} + + // Allow override of resource requirements from CR + if cr.Spec.ApplicationSet.Resources != nil { + resources = *cr.Spec.ApplicationSet.Resources + } + + return resources +} + +func setAppSetLabels(obj *metav1.ObjectMeta) { + obj.Labels["app.kubernetes.io/name"] = "argocd-applicationset-controller" + obj.Labels["app.kubernetes.io/part-of"] = "argocd-applicationset" + obj.Labels["app.kubernetes.io/component"] = "controller" +} + +// reconcileApplicationSetService will ensure that the Service is present for the ApplicationSet webhook and metrics component. +func (r *ReconcileArgoCD) reconcileApplicationSetService(cr *argoproj.ArgoCD) error { + log.Info("reconciling applicationset service") + + svc := newServiceWithSuffix(common.ApplicationSetServiceNameSuffix, common.ApplicationSetServiceNameSuffix, cr) + if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() { + + if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { + err := argoutil.FetchObject(r.Client, cr.Namespace, svc.Name, svc) + if err != nil { + return err + } + log.Info(fmt.Sprintf("Deleting applicationset controller service %s as applicationset is disabled", svc.Name)) + err = r.Delete(context.TODO(), svc) + if err != nil { + return err + } + } + return nil + } else { + if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { + return nil // Service found, do nothing + } + } + svc.Spec.Ports = []corev1.ServicePort{ + { + Name: "webhook", + Port: 7000, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(7000), + }, { + Name: "metrics", + Port: 8080, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(8080), + }, + } + + svc.Spec.Selector = map[string]string{ + common.ArgoCDKeyName: nameWithSuffix(common.ApplicationSetServiceNameSuffix, cr), + } + + if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { + return err + } + return r.Client.Create(context.TODO(), svc) +} diff --git a/controllers/argocd/argocd_controller.go b/controllers/argocd/argocd_controller.go index 476555542..82b0d928c 100644 --- a/controllers/argocd/argocd_controller.go +++ b/controllers/argocd/argocd_controller.go @@ -24,7 +24,6 @@ import ( monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" oappsv1 "github.com/openshift/api/apps/v1" routev1 "github.com/openshift/api/route/v1" - "github.com/prometheus/client_golang/prometheus" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" "github.com/argoproj-labs/argocd-operator/common" @@ -40,25 +39,27 @@ import ( "github.com/argoproj-labs/argocd-operator/pkg/cluster" "github.com/argoproj-labs/argocd-operator/pkg/monitoring" "github.com/argoproj-labs/argocd-operator/pkg/openshift" + "github.com/argoproj-labs/argocd-operator/pkg/util" - "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" v1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" - ctrlLog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +const ( + ArgoCDController = "argocd-controller" +) + // blank assignment to verify that ArgoCDReconciler implements reconcile.Reconciler var _ reconcile.Reconciler = &ArgoCDReconciler{} @@ -80,7 +81,7 @@ type ArgoCDReconciler struct { Scheme *runtime.Scheme Instance *argoproj.ArgoCD ClusterScoped bool - Logger logr.Logger + Logger *util.Logger ResourceManagedNamespaces map[string]string AppManagedNamespaces map[string]string @@ -134,8 +135,6 @@ var ActiveInstanceMap = make(map[string]string) // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile func (r *ArgoCDReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - argocdControllerLog := ctrl.Log.WithName("argocd-controller") - reconcileStartTS := time.Now() defer func() { ReconcileTime.WithLabelValues(request.Namespace).Observe(time.Since(reconcileStartTS).Seconds()) @@ -180,7 +179,7 @@ func (r *ArgoCDReconciler) Reconcile(ctx context.Context, request ctrl.Request) r.Instance = argocd r.ClusterScoped = IsClusterConfigNs(r.Instance.Namespace) - r.Logger = argocdControllerLog.WithValues("instance", r.Instance.Name, "instance-namespace", r.Instance.Namespace) + r.Logger = util.NewLogger(ArgoCDController, "instance", r.Instance.Name, "instance-namespace", r.Instance.Namespace) // if r.Instance.GetDeletionTimestamp() != nil { @@ -239,130 +238,6 @@ func (r *ArgoCDReconciler) Reconcile(ctx context.Context, request ctrl.Request) return reconcile.Result{}, nil } -// old reconcile function - leave as is -func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - - reconcileStartTS := time.Now() - defer func() { - ReconcileTime.WithLabelValues(request.Namespace).Observe(time.Since(reconcileStartTS).Seconds()) - }() - - reqLogger := ctrlLog.FromContext(ctx, "namespace", request.Namespace, "name", request.Name) - reqLogger.Info("Reconciling ArgoCD") - - argocd := &argoproj.ArgoCD{} - err := r.Client.Get(ctx, request.NamespacedName, argocd) - if err != nil { - if errors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - - // Fetch labelSelector from r.LabelSelector (command-line option) - labelSelector, err := labels.Parse(r.LabelSelector) - if err != nil { - reqLogger.Info(fmt.Sprintf("error parsing the labelSelector '%s'.", labelSelector)) - return reconcile.Result{}, err - } - // Match the value of labelSelector from ReconcileArgoCD to labels from the argocd instance - if !labelSelector.Matches(labels.Set(argocd.Labels)) { - reqLogger.Info(fmt.Sprintf("the ArgoCD instance '%s' does not match the label selector '%s' and skipping for reconciliation", request.NamespacedName, r.LabelSelector)) - return reconcile.Result{}, fmt.Errorf("Error: failed to reconcile ArgoCD instance: '%s'", request.NamespacedName) - } - - newPhase := argocd.Status.Phase - // If we discover a new Argo CD instance in a previously un-seen namespace - // we add it to the map and increment active instance count by phase - // as well as total active instance count - if _, ok := ActiveInstanceMap[request.Namespace]; !ok { - if newPhase != "" { - ActiveInstanceMap[request.Namespace] = newPhase - ActiveInstancesByPhase.WithLabelValues(newPhase).Inc() - ActiveInstancesTotal.Inc() - } - } else { - // If we discover an existing instance's phase has changed since we last saw it - // increment instance count with new phase and decrement instance count with old phase - // update the phase in corresponding map entry - // total instance count remains the same - if oldPhase := ActiveInstanceMap[argocd.Namespace]; oldPhase != newPhase { - ActiveInstanceMap[argocd.Namespace] = newPhase - ActiveInstancesByPhase.WithLabelValues(newPhase).Inc() - ActiveInstancesByPhase.WithLabelValues(oldPhase).Dec() - } - } - - ActiveInstanceReconciliationCount.WithLabelValues(argocd.Namespace).Inc() - - if argocd.GetDeletionTimestamp() != nil { - - // Argo CD instance marked for deletion; remove entry from activeInstances map and decrement active instance count - // by phase as well as total - delete(ActiveInstanceMap, argocd.Namespace) - ActiveInstancesByPhase.WithLabelValues(newPhase).Dec() - ActiveInstancesTotal.Dec() - ActiveInstanceReconciliationCount.DeleteLabelValues(argocd.Namespace) - ReconcileTime.DeletePartialMatch(prometheus.Labels{"namespace": argocd.Namespace}) - - if argocd.IsDeletionFinalizerPresent() { - if err := r.deleteClusterResources(argocd); err != nil { - return reconcile.Result{}, fmt.Errorf("failed to delete ClusterResources: %w", err) - } - - if isRemoveManagedByLabelOnArgoCDDeletion() { - if err := r.removeManagedByLabelFromNamespaces(argocd.Namespace); err != nil { - return reconcile.Result{}, fmt.Errorf("failed to remove label from namespace[%v], error: %w", argocd.Namespace, err) - } - } - - if err := r.removeUnmanagedSourceNamespaceResources(argocd); err != nil { - return reconcile.Result{}, fmt.Errorf("failed to remove resources from sourceNamespaces, error: %w", err) - } - - if err := r.removeDeletionFinalizer(argocd); err != nil { - return reconcile.Result{}, err - } - - // remove namespace of deleted Argo CD instance from deprecationEventEmissionTracker (if exists) so that if another instance - // is created in the same namespace in the future, that instance is appropriately tracked - delete(DeprecationEventEmissionTracker, argocd.Namespace) - } - return reconcile.Result{}, nil - } - - if !argocd.IsDeletionFinalizerPresent() { - if err := r.addDeletionFinalizer(argocd); err != nil { - return reconcile.Result{}, err - } - } - - // get the latest version of argocd instance before reconciling - if err = r.Client.Get(ctx, request.NamespacedName, argocd); err != nil { - return reconcile.Result{}, err - } - - if err = r.setManagedNamespaces(argocd); err != nil { - return reconcile.Result{}, err - } - - if err = r.setManagedSourceNamespaces(argocd); err != nil { - return reconcile.Result{}, err - } - - if err := r.reconcileResources(argocd); err != nil { - // Error reconciling ArgoCD sub-resources - requeue the request. - return reconcile.Result{}, err - } - - // Return and don't requeue - return reconcile.Result{}, nil -} - // setResourceManagedNamespaces finds all namespaces carrying the managed-by label, adds the control plane namespace and stores that list in ArgoCDReconciler to be accessed later func (r *ArgoCDReconciler) setResourceManagedNamespaces() error { r.ResourceManagedNamespaces = make(map[string]string) @@ -404,7 +279,7 @@ func (r *ArgoCDReconciler) setAppManagedNamespaces() error { allowedSourceNamespaces := make(map[string]string) if !r.ClusterScoped { - r.Logger.V(1).Info("setSourceNamespaces: instance is not cluster scoped, skip processing namespaces for application management") + r.Logger.Debug("setSourceNamespaces: instance is not cluster scoped, skip processing namespaces for application management") return nil } @@ -449,7 +324,7 @@ func (r *ArgoCDReconciler) setAppManagedNamespaces() error { // check if desired namespace is already being managed by a different cluster scoped Argo CD instance. If yes, skip it // If not, add ArgoCDArgoprojKeyManagedByClusterArgoCD to it and add it to allowedSourceNamespaces if val, ok := ns.Labels[common.ArgoCDArgoprojKeyManagedByClusterArgoCD]; ok && val != r.Instance.Namespace { - r.Logger.V(1).Info("setSourceNamespaces: skipping namespace as it is already managed by a different instance", "namespace", ns.Name, "managing-instance-namespace", val) + r.Logger.Debug("setSourceNamespaces: skipping namespace as it is already managed by a different instance", "namespace", ns.Name, "managing-instance-namespace", val) continue } else { ns.Labels[common.ArgoCDArgoprojKeyManagedByClusterArgoCD] = r.Instance.Namespace @@ -460,7 +335,7 @@ func (r *ArgoCDReconciler) setAppManagedNamespaces() error { r.Logger.Error(err, "setSourceNamespaces: failed to update namespace", "namespace", ns.Name) continue } - r.Logger.V(1).Info("setSourceNamespaces: labeled namespace", "namespace", ns.Name) + r.Logger.Debug("setSourceNamespaces: labeled namespace", "namespace", ns.Name) continue } allowedSourceNamespaces[desiredNs] = "" @@ -481,7 +356,7 @@ func (r *ArgoCDReconciler) setAppManagedNamespaces() error { r.Logger.Error(err, "setSourceNamespaces: failed to update namespace", "namespace", ns.Name) continue } - r.Logger.V(1).Info("setSourceNamespaces: unlabeled namespace", "namespace", ns.Name) + r.Logger.Debug("setSourceNamespaces: unlabeled namespace", "namespace", ns.Name) continue } } @@ -518,12 +393,18 @@ func (r *ArgoCDReconciler) reconcileControllers() error { return err } - if err := r.ReposerverController.Reconcile(); err != nil { - r.Logger.Error(err, "failed to reconcile reposerver controller") - return err + if r.Instance.Spec.Repo.IsEnabled() { + if err := r.ReposerverController.Reconcile(); err != nil { + r.Logger.Error(err, "failed to reconcile repo-server controller") + return err + } + } else { + r.Logger.Info("repo-server disabled; deleting resources") + if err := r.ReposerverController.DeleteResources(); err != nil { + r.Logger.Error(err, "failed to delete repo-server resources") + } } - // non-core components, don't return reconciliation errors if r.Instance.Spec.ApplicationSet != nil { if err := r.AppsetController.Reconcile(); err != nil { r.Logger.Error(err, "failed to reconcile applicationset controller") @@ -548,6 +429,10 @@ func (r *ArgoCDReconciler) reconcileControllers() error { r.Logger.Error(err, "failed to reconcile SSO controller") } + if err := r.reconcileStatus(); err != nil { + return err + } + return nil } @@ -571,12 +456,14 @@ func (r *ArgoCDReconciler) InitializeControllerReconcilers() { Client: r.Client, Scheme: r.Scheme, Instance: r.Instance, + Logger: util.NewLogger(common.RedisController, "instance", r.Instance.Name, "instance-namespace", r.Instance.Namespace), } reposerverController := &reposerver.RepoServerReconciler{ Client: r.Client, Scheme: r.Scheme, Instance: r.Instance, + Logger: util.NewLogger(common.RepoServerController, "instance", r.Instance.Name, "instance-namespace", r.Instance.Namespace), } serverController := &server.ServerReconciler{ @@ -592,6 +479,7 @@ func (r *ArgoCDReconciler) InitializeControllerReconcilers() { Client: r.Client, Scheme: r.Scheme, Instance: r.Instance, + Logger: util.NewLogger(common.NotificationsController, "instance", r.Instance.Name, "instance-namespace", r.Instance.Namespace), } appController := &appcontroller.AppControllerReconciler{ @@ -607,10 +495,12 @@ func (r *ArgoCDReconciler) InitializeControllerReconcilers() { Client: r.Client, Scheme: r.Scheme, Instance: r.Instance, + // TO DO: update this later + Logger: util.NewLogger(applicationset.AppSetControllerComponent, "instance", r.Instance.Name, "instance-namespace", r.Instance.Namespace), } ssoController := &sso.SSOReconciler{ - Client: &r.Client, + Client: r.Client, Scheme: r.Scheme, Instance: r.Instance, } @@ -620,8 +510,12 @@ func (r *ArgoCDReconciler) InitializeControllerReconcilers() { r.ServerController = serverController r.ReposerverController = reposerverController + r.ReposerverController.Appcontroller = appController + r.ReposerverController.Server = serverController + r.ReposerverController.Redis = redisController r.AppsetController = appsetController + r.AppsetController.RepoServer = reposerverController r.RedisController = redisController @@ -635,13 +529,6 @@ func (r *ArgoCDReconciler) InitializeControllerReconcilers() { } -// SetupWithManager sets up the controller with the Manager. -func (r *ReconcileArgoCD) SetupWithManager(mgr ctrl.Manager) error { - bldr := ctrl.NewControllerManagedBy(mgr) - r.setResourceWatches(bldr, r.clusterResourceMapper, r.tlsSecretMapper, r.namespaceResourceMapper, r.clusterSecretResourceMapper, r.applicationSetSCMTLSConfigMapMapper) - return bldr.Complete(r) -} - // SetupWithManager sets up the controller with the Manager. func (r *ArgoCDReconciler) SetupWithManager(mgr ctrl.Manager) error { bldr := ctrl.NewControllerManagedBy(mgr) diff --git a/controllers/argocd/argocd_controller_test.go b/controllers/argocd/argocd_controller_test.go index 39efcfb7d..9ddf9fbbf 100644 --- a/controllers/argocd/argocd_controller_test.go +++ b/controllers/argocd/argocd_controller_test.go @@ -42,6 +42,13 @@ import ( var _ reconcile.Reconciler = &ArgoCDReconciler{} +func makeTestArgoCDReconciler(client client.Client, sch *runtime.Scheme) *ArgoCDReconciler { + return &ArgoCDReconciler{ + Client: client, + Scheme: sch, + } +} + func addFinalizer(finalizer string) argoCDOpt { return func(a *argoproj.ArgoCD) { a.Finalizers = append(a.Finalizers, finalizer) @@ -427,7 +434,7 @@ func TestSetResourceManagedNamespaces(t *testing.T) { sch := makeTestReconcilerScheme(argoproj.AddToScheme) cl := makeTestReconcilerClient(sch, resources, subresObjs, runtimeObjs) - r := makeNewTestReconciler(cl, sch) + r := makeTestArgoCDReconciler(cl, sch) instanceOne := makeTestArgoCD(func(ac *argoproj.ArgoCD) { ac.Namespace = "instance-1" @@ -492,7 +499,7 @@ func TestSetAppManagedNamespaces(t *testing.T) { sch := makeTestReconcilerScheme(argoproj.AddToScheme) cl := makeTestReconcilerClient(sch, resources, subresObjs, runtimeObjs) - r := makeNewTestReconciler(cl, sch) + r := makeTestArgoCDReconciler(cl, sch) instance := makeTestArgoCD(func(ac *argoproj.ArgoCD) { ac.Namespace = "instance-1" diff --git a/controllers/argocd/argocdcommon/commands.go b/controllers/argocd/argocdcommon/commands.go new file mode 100644 index 000000000..dc42c3817 --- /dev/null +++ b/controllers/argocd/argocdcommon/commands.go @@ -0,0 +1,16 @@ +package argocdcommon + +const ( + ArgoCDBinPath = "/usr/local/bin/argocd" + CmpServerBinPath = "/var/run/argocd/argocd-cmp-server" +) + +// getArgoCmpServerInitCommand will return the command for the ArgoCD CMP Server init container +func GetArgoCmpServerInitCommand() []string { + cmd := make([]string, 0) + cmd = append(cmd, "cp") + cmd = append(cmd, "-n") + cmd = append(cmd, ArgoCDBinPath) + cmd = append(cmd, CmpServerBinPath) + return cmd +} diff --git a/controllers/argocd/argocdcommon/helper.go b/controllers/argocd/argocdcommon/helper.go index 3eaf1d514..6a998b3af 100644 --- a/controllers/argocd/argocdcommon/helper.go +++ b/controllers/argocd/argocdcommon/helper.go @@ -7,14 +7,32 @@ import ( "github.com/argoproj-labs/argocd-operator/pkg/util" ) -func UpdateIfChanged(existingVal, desiredVal interface{}, extraAction func(), changed *bool) { - if util.IsPtr(existingVal) && util.IsPtr(desiredVal) { - if !reflect.DeepEqual(existingVal, desiredVal) { - reflect.ValueOf(existingVal).Elem().Set(reflect.ValueOf(desiredVal).Elem()) - if extraAction != nil { - extraAction() +type FieldToCompare struct { + Existing interface{} + Desired interface{} + ExtraAction func() +} + +// UpdateIfChanged accepts a slice of fields to be compared, along with a bool ptr. It compares all the provided fields, updating any fields and setting the bool ptr to true if a drift is detected +func UpdateIfChanged(ftc []FieldToCompare, changed *bool) { + for _, field := range ftc { + if util.IsPtr(field.Existing) && util.IsPtr(field.Desired) { + if !reflect.DeepEqual(field.Existing, field.Desired) { + reflect.ValueOf(field.Existing).Elem().Set(reflect.ValueOf(field.Desired).Elem()) + if field.ExtraAction != nil { + field.ExtraAction() + } + *changed = true } - *changed = true + } + } +} + +// PartialMatch accepts a slice of fields to be compared, along with a bool ptr. It compares all the provided fields and sets the bool to false if a drift is detected +func PartialMatch(ftc []FieldToCompare, match *bool) { + for _, field := range ftc { + if !reflect.DeepEqual(field.Existing, field.Desired) { + *match = false } } } diff --git a/controllers/argocd/argocdcommon/secret.go b/controllers/argocd/argocdcommon/secret.go index e6b9f82ec..6fc9a8d53 100644 --- a/controllers/argocd/argocdcommon/secret.go +++ b/controllers/argocd/argocdcommon/secret.go @@ -9,6 +9,7 @@ import ( "github.com/argoproj-labs/argocd-operator/pkg/networking" "github.com/argoproj-labs/argocd-operator/pkg/workloads" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" @@ -20,6 +21,9 @@ func TLSSecretChecksum(secretRef types.NamespacedName, client cntrlClient.Client tlsSecret, err := workloads.GetSecret(secretRef.Name, secretRef.Namespace, client) if err != nil { + if apierrors.IsNotFound(err) { + return "", nil + } return "", err } diff --git a/controllers/argocd/argocdcommon/testing.go b/controllers/argocd/argocdcommon/testing.go deleted file mode 100644 index 11b5b0f50..000000000 --- a/controllers/argocd/argocdcommon/testing.go +++ /dev/null @@ -1,69 +0,0 @@ -package argocdcommon - -import ( - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" - "github.com/argoproj-labs/argocd-operator/common" -) - -const ( - TestNamespace = "argocd" - TestArgoCDName = "argocd" -) - -var ( - TestKey = "test" - TestVal = "test" - TestRoleRef = rbacv1.RoleRef{ - Kind: common.RoleKind, - Name: TestArgoCDName, - APIGroup: rbacv1.GroupName, - } - - TestSubjects = []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: TestArgoCDName, - Namespace: TestNamespace, - }, - } - - TestKVP = map[string]string{ - TestKey: TestVal, - } -) - -func MakeTestNamespace() *corev1.Namespace { - return &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: TestNamespace, - }, - } -} -func MakeTestServiceAccount() *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: TestArgoCDName, - Namespace: TestNamespace, - }, - Secrets: []corev1.ObjectReference{}, - } -} - -type argoCDOpt func(*argoproj.ArgoCD) - -func MakeTestArgoCD(opts ...argoCDOpt) *argoproj.ArgoCD { - a := &argoproj.ArgoCD{ - ObjectMeta: metav1.ObjectMeta{ - Name: TestArgoCDName, - Namespace: TestNamespace, - }, - } - for _, o := range opts { - o(a) - } - return a -} diff --git a/controllers/argocd/argocdcommon/workloads.go b/controllers/argocd/argocdcommon/workloads.go index 46d7585e2..197780f18 100644 --- a/controllers/argocd/argocdcommon/workloads.go +++ b/controllers/argocd/argocdcommon/workloads.go @@ -13,6 +13,10 @@ func TriggerDeploymentRollout(name, namespace, key string, client cntrlClient.Cl return err } + if deployment.Spec.Template.ObjectMeta.Labels == nil { + deployment.Spec.Template.ObjectMeta.Labels = make(map[string]string) + } + deployment.Spec.Template.ObjectMeta.Labels[key] = util.NowNano() return workloads.UpdateDeployment(deployment, client) } @@ -24,6 +28,10 @@ func TriggerStatefulSetRollout(name, namespace, key string, client cntrlClient.C return err } + if statefulset.Spec.Template.ObjectMeta.Labels == nil { + statefulset.Spec.Template.ObjectMeta.Labels = make(map[string]string) + } + statefulset.Spec.Template.ObjectMeta.Labels[key] = util.NowNano() return workloads.UpdateStatefulSet(statefulset, client) } diff --git a/controllers/argocd/cm_TOBEREMOVED.go b/controllers/argocd/cm_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/cm_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/deployment.go b/controllers/argocd/deployment.go index 9e805408c..8a2c54d3f 100644 --- a/controllers/argocd/deployment.go +++ b/controllers/argocd/deployment.go @@ -35,17 +35,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -// getArgoCDRepoServerReplicas will return the size value for the argocd-repo-server replica count if it -// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or -// replicas value is < 0. -func getArgoCDRepoServerReplicas(cr *argoproj.ArgoCD) *int32 { - if cr.Spec.Repo.Replicas != nil && *cr.Spec.Repo.Replicas >= 0 { - return cr.Spec.Repo.Replicas - } - - return nil -} - // getArgoCDServerReplicas will return the size value for the argocd-server replica count if it // has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or // replicas value is < 0. If Autoscale is enabled, the value for replicas in the argocd CR will be ignored. @@ -224,55 +213,6 @@ func getArgoRedisArgs(useTLS bool) []string { return args } -// getArgoRepoCommand will return the command for the ArgoCD Repo component. -func getArgoRepoCommand(cr *argoproj.ArgoCD, useTLSForRedis bool) []string { - cmd := make([]string, 0) - - cmd = append(cmd, "uid_entrypoint.sh") - cmd = append(cmd, "argocd-repo-server") - - if cr.Spec.Redis.IsEnabled() { - cmd = append(cmd, "--redis", getRedisServerAddress(cr)) - } else { - log.Info("Redis is Disabled. Skipping adding Redis configuration to Repo Server.") - } - if useTLSForRedis { - cmd = append(cmd, "--redis-use-tls") - if isRedisTLSVerificationDisabled(cr) { - cmd = append(cmd, "--redis-insecure-skip-tls-verify") - } else { - cmd = append(cmd, "--redis-ca-certificate", "/app/config/reposerver/tls/redis/tls.crt") - } - } - - cmd = append(cmd, "--loglevel") - cmd = append(cmd, getLogLevel(cr.Spec.Repo.LogLevel)) - - cmd = append(cmd, "--logformat") - cmd = append(cmd, getLogFormat(cr.Spec.Repo.LogFormat)) - - // *** NOTE *** - // Do Not add any new default command line arguments below this. - extraArgs := cr.Spec.Repo.ExtraRepoCommandArgs - err := isMergable(extraArgs, cmd) - if err != nil { - return cmd - } - - cmd = append(cmd, extraArgs...) - return cmd -} - -// getArgoCmpServerInitCommand will return the command for the ArgoCD CMP Server init container -func getArgoCmpServerInitCommand() []string { - cmd := make([]string, 0) - cmd = append(cmd, "cp") - cmd = append(cmd, "-n") - cmd = append(cmd, "/usr/local/bin/argocd") - cmd = append(cmd, "/var/run/argocd/argocd-cmp-server") - return cmd -} - // getArgoServerCommand will return the command for the ArgoCD server component. func getArgoServerCommand(cr *argoproj.ArgoCD, useTLSForRedis bool) []string { cmd := make([]string, 0) @@ -337,14 +277,6 @@ func getDexServerAddress(cr *argoproj.ArgoCD) string { return fmt.Sprintf("https://%s", fqdnServiceRef("dex-server", common.ArgoCDDefaultDexHTTPPort, cr)) } -// getRepoServerAddress will return the Argo CD repo server address. -func getRepoServerAddress(cr *argoproj.ArgoCD) string { - if cr.Spec.Repo.Remote != nil && *cr.Spec.Repo.Remote != "" { - return *cr.Spec.Repo.Remote - } - return fqdnServiceRef("repo-server", common.ArgoCDDefaultRepoServerPort, cr) -} - // newDeployment returns a new Deployment instance for the given ArgoCD. func newDeployment(cr *argoproj.ArgoCD) *appsv1.Deployment { return &appsv1.Deployment{ @@ -879,320 +811,6 @@ func (r *ReconcileArgoCD) reconcileRedisHAProxyDeployment(cr *argoproj.ArgoCD) e return r.Client.Create(context.TODO(), deploy) } -// reconcileRepoDeployment will ensure the Deployment resource is present for the ArgoCD Repo component. -func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoproj.ArgoCD, useTLSForRedis bool) error { - deploy := newDeploymentWithSuffix("repo-server", "repo-server", cr) - automountToken := false - if cr.Spec.Repo.MountSAToken { - automountToken = cr.Spec.Repo.MountSAToken - } - - deploy.Spec.Template.Spec.AutomountServiceAccountToken = &automountToken - - if cr.Spec.Repo.ServiceAccount != "" { - deploy.Spec.Template.Spec.ServiceAccountName = cr.Spec.Repo.ServiceAccount - } - - // Global proxy env vars go first - repoEnv := cr.Spec.Repo.Env - // Environment specified in the CR take precedence over everything else - repoEnv = argoutil.EnvMerge(repoEnv, proxyEnvVars(), false) - if cr.Spec.Repo.ExecTimeout != nil { - repoEnv = argoutil.EnvMerge(repoEnv, []corev1.EnvVar{{Name: "ARGOCD_EXEC_TIMEOUT", Value: fmt.Sprintf("%ds", *cr.Spec.Repo.ExecTimeout)}}, true) - } - - AddSeccompProfileForOpenShift(r.Client, &deploy.Spec.Template.Spec) - - deploy.Spec.Template.Spec.InitContainers = []corev1.Container{{ - Name: "copyutil", - Image: getArgoContainerImage(cr), - Command: getArgoCmpServerInitCommand(), - ImagePullPolicy: corev1.PullAlways, - Resources: getArgoRepoResources(cr), - Env: proxyEnvVars(), - SecurityContext: &corev1.SecurityContext{ - AllowPrivilegeEscalation: boolPtr(false), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{ - "ALL", - }, - }, - RunAsNonRoot: boolPtr(true), - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "var-files", - MountPath: "/var/run/argocd", - }, - }, - }} - - if cr.Spec.Repo.InitContainers != nil { - deploy.Spec.Template.Spec.InitContainers = append(deploy.Spec.Template.Spec.InitContainers, cr.Spec.Repo.InitContainers...) - } - - repoServerVolumeMounts := []corev1.VolumeMount{ - { - Name: "ssh-known-hosts", - MountPath: "/app/config/ssh", - }, - { - Name: "tls-certs", - MountPath: "/app/config/tls", - }, - { - Name: "gpg-keys", - MountPath: "/app/config/gpg/source", - }, - { - Name: "gpg-keyring", - MountPath: "/app/config/gpg/keys", - }, - { - Name: "tmp", - MountPath: "/tmp", - }, - { - Name: "argocd-repo-server-tls", - MountPath: "/app/config/reposerver/tls", - }, - { - Name: common.ArgoCDRedisServerTLSSecretName, - MountPath: "/app/config/reposerver/tls/redis", - }, - { - Name: "plugins", - MountPath: "/home/argocd/cmp-server/plugins", - }, - } - - if cr.Spec.Repo.VolumeMounts != nil { - repoServerVolumeMounts = append(repoServerVolumeMounts, cr.Spec.Repo.VolumeMounts...) - } - - deploy.Spec.Template.Spec.Containers = []corev1.Container{{ - Command: getArgoRepoCommand(cr, useTLSForRedis), - Image: getRepoServerContainerImage(cr), - ImagePullPolicy: corev1.PullAlways, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), - }, - }, - InitialDelaySeconds: 5, - PeriodSeconds: 10, - }, - Env: repoEnv, - Name: "argocd-repo-server", - Ports: []corev1.ContainerPort{ - { - ContainerPort: common.ArgoCDDefaultRepoServerPort, - Name: "server", - }, { - ContainerPort: common.ArgoCDDefaultRepoMetricsPort, - Name: "metrics", - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), - }, - }, - InitialDelaySeconds: 5, - PeriodSeconds: 10, - }, - Resources: getArgoRepoResources(cr), - SecurityContext: &corev1.SecurityContext{ - AllowPrivilegeEscalation: boolPtr(false), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{ - "ALL", - }, - }, - RunAsNonRoot: boolPtr(true), - }, - VolumeMounts: repoServerVolumeMounts, - }} - - if cr.Spec.Repo.SidecarContainers != nil { - deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, cr.Spec.Repo.SidecarContainers...) - } - - repoServerVolumes := []corev1.Volume{ - { - Name: "ssh-known-hosts", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDKnownHostsConfigMapName, - }, - }, - }, - }, - { - Name: "tls-certs", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDTLSCertsConfigMapName, - }, - }, - }, - }, - { - Name: "gpg-keys", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.ArgoCDGPGKeysConfigMapName, - }, - }, - }, - }, - { - Name: "gpg-keyring", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: "tmp", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: "argocd-repo-server-tls", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: common.ArgoCDRepoServerTLSSecretName, - Optional: boolPtr(true), - }, - }, - }, - { - Name: common.ArgoCDRedisServerTLSSecretName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: common.ArgoCDRedisServerTLSSecretName, - Optional: boolPtr(true), - }, - }, - }, - { - Name: "var-files", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: "plugins", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - } - - if cr.Spec.Repo.Volumes != nil { - repoServerVolumes = append(repoServerVolumes, cr.Spec.Repo.Volumes...) - } - - deploy.Spec.Template.Spec.Volumes = repoServerVolumes - - if replicas := getArgoCDRepoServerReplicas(cr); replicas != nil { - deploy.Spec.Replicas = replicas - } - - existing := newDeploymentWithSuffix("repo-server", "repo-server", cr) - if argoutil.IsObjectFound(r.Client, cr.Namespace, existing.Name, existing) { - - if !cr.Spec.Repo.IsEnabled() { - log.Info("Existing ArgoCD Repo Server found but should be disabled. Deleting Repo Server") - // Delete existing deployment for ArgoCD Repo Server, if any .. - return r.Client.Delete(context.TODO(), existing) - } - - changed := false - actualImage := existing.Spec.Template.Spec.Containers[0].Image - desiredImage := getRepoServerContainerImage(cr) - if actualImage != desiredImage { - existing.Spec.Template.Spec.Containers[0].Image = desiredImage - if existing.Spec.Template.ObjectMeta.Labels == nil { - existing.Spec.Template.ObjectMeta.Labels = map[string]string{ - "image.upgraded": time.Now().UTC().Format("01022006-150406-MST"), - } - } - existing.Spec.Template.ObjectMeta.Labels["image.upgraded"] = time.Now().UTC().Format("01022006-150406-MST") - changed = true - } - updateNodePlacement(existing, deploy, &changed) - if !reflect.DeepEqual(deploy.Spec.Template.Spec.Volumes, existing.Spec.Template.Spec.Volumes) { - existing.Spec.Template.Spec.Volumes = deploy.Spec.Template.Spec.Volumes - changed = true - } - if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].VolumeMounts, - existing.Spec.Template.Spec.Containers[0].VolumeMounts) { - existing.Spec.Template.Spec.Containers[0].VolumeMounts = deploy.Spec.Template.Spec.Containers[0].VolumeMounts - changed = true - } - if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Env, - existing.Spec.Template.Spec.Containers[0].Env) { - existing.Spec.Template.Spec.Containers[0].Env = deploy.Spec.Template.Spec.Containers[0].Env - changed = true - } - if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Resources, existing.Spec.Template.Spec.Containers[0].Resources) { - existing.Spec.Template.Spec.Containers[0].Resources = deploy.Spec.Template.Spec.Containers[0].Resources - changed = true - } - if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Command, existing.Spec.Template.Spec.Containers[0].Command) { - existing.Spec.Template.Spec.Containers[0].Command = deploy.Spec.Template.Spec.Containers[0].Command - changed = true - } - if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[1:], - existing.Spec.Template.Spec.Containers[1:]) { - existing.Spec.Template.Spec.Containers = append(existing.Spec.Template.Spec.Containers[0:1], - deploy.Spec.Template.Spec.Containers[1:]...) - changed = true - } - if !reflect.DeepEqual(deploy.Spec.Template.Spec.InitContainers, existing.Spec.Template.Spec.InitContainers) { - existing.Spec.Template.Spec.InitContainers = deploy.Spec.Template.Spec.InitContainers - changed = true - } - - if !reflect.DeepEqual(deploy.Spec.Replicas, existing.Spec.Replicas) { - existing.Spec.Replicas = deploy.Spec.Replicas - changed = true - } - - if deploy.Spec.Template.Spec.AutomountServiceAccountToken != existing.Spec.Template.Spec.AutomountServiceAccountToken { - existing.Spec.Template.Spec.AutomountServiceAccountToken = deploy.Spec.Template.Spec.AutomountServiceAccountToken - changed = true - } - - if deploy.Spec.Template.Spec.ServiceAccountName != existing.Spec.Template.Spec.ServiceAccountName { - existing.Spec.Template.Spec.ServiceAccountName = deploy.Spec.Template.Spec.ServiceAccountName - changed = true - } - - if changed { - return r.Client.Update(context.TODO(), existing) - } - return nil // Deployment found with nothing to do, move along... - } - - if !cr.Spec.Repo.IsEnabled() { - log.Info("ArgoCD Repo Server disabled. Skipping starting ArgoCD Repo Server.") - return nil - } - - if err := controllerutil.SetControllerReference(cr, deploy, r.Scheme); err != nil { - return err - } - return r.Client.Create(context.TODO(), deploy) -} - // reconcileServerDeployment will ensure the Deployment resource is present for the ArgoCD Server component. func (r *ReconcileArgoCD) reconcileServerDeployment(cr *argoproj.ArgoCD, useTLSForRedis bool) error { deploy := newDeploymentWithSuffix("server", "server", cr) diff --git a/controllers/argocd/dex_TOBEREMOVED.go b/controllers/argocd/dex_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/dex_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/keycloak_TOBEREMOVED.go b/controllers/argocd/keycloak_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/keycloak_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/notifications/configmap.go b/controllers/argocd/notifications/configmap.go index c5b635e2b..5392ade2a 100644 --- a/controllers/argocd/notifications/configmap.go +++ b/controllers/argocd/notifications/configmap.go @@ -28,7 +28,7 @@ func (nr *NotificationsReconciler) reconcileConfigMap() error { if err != nil { nr.Logger.Error(err, "reconcileConfigMap: failed to request configMap", "name", desiredConfigMap.Name, "namespace", desiredConfigMap.Namespace) - nr.Logger.V(1).Info("reconcileConfigMap: one or more mutations could not be applied") + nr.Logger.Debug("reconcileConfigMap: one or more mutations could not be applied") return err } @@ -59,7 +59,7 @@ func (nr *NotificationsReconciler) reconcileConfigMap() error { nr.Logger.Error(err, "reconcileConfigMap: failed to create configMap", "name", desiredConfigMap.Name, "namespace", desiredConfigMap.Namespace) return err } - nr.Logger.V(0).Info("reconcileConfigMap: configMap created", "name", desiredConfigMap.Name, "namespace", desiredConfigMap.Namespace) + nr.Logger.Info("configMap created", "name", desiredConfigMap.Name, "namespace", desiredConfigMap.Namespace) return nil } @@ -74,6 +74,6 @@ func (nr *NotificationsReconciler) deleteConfigMap(namespace string) error { nr.Logger.Error(err, "DeleteConfigMap: failed to delete configMap", "name", common.NotificationsConfigMapName, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteConfigMap: configMap deleted", "name", common.NotificationsConfigMapName, "namespace", namespace) + nr.Logger.Info("configMap deleted", "name", common.NotificationsConfigMapName, "namespace", namespace) return nil } diff --git a/controllers/argocd/notifications/configmap_test.go b/controllers/argocd/notifications/configmap_test.go index ce148e920..074a8042a 100644 --- a/controllers/argocd/notifications/configmap_test.go +++ b/controllers/argocd/notifications/configmap_test.go @@ -7,14 +7,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" ) func TestNotificationsReconciler_reconcileConfigMap(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) tests := []struct { name string @@ -42,7 +42,7 @@ func TestNotificationsReconciler_reconcileConfigMap(t *testing.T) { } currentConfigMap := &corev1.ConfigMap{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: common.NotificationsConfigMapName, Namespace: argocdcommon.TestNamespace}, currentConfigMap) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: common.NotificationsConfigMapName, Namespace: test.TestNamespace}, currentConfigMap) if err != nil { t.Fatalf("Could not get current ConfigMap: %v", err) } @@ -52,7 +52,7 @@ func TestNotificationsReconciler_reconcileConfigMap(t *testing.T) { } func TestNotificationsReconciler_DeleteConfigMap(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) tests := []struct { name string setupClient func() *NotificationsReconciler diff --git a/controllers/argocd/notifications/deployment.go b/controllers/argocd/notifications/deployment.go index 8d5f75ee6..e4dc15917 100644 --- a/controllers/argocd/notifications/deployment.go +++ b/controllers/argocd/notifications/deployment.go @@ -12,7 +12,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -29,7 +29,7 @@ func (nr *NotificationsReconciler) reconcileDeployment() error { desiredDeployment, err := workloads.RequestDeployment(deploymentRequest) if err != nil { nr.Logger.Error(err, "reconcileDeployment: failed to request deployment", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) - nr.Logger.V(1).Info("reconcileDeployment: one or more mutations could not be applied") + nr.Logger.Debug("reconcileDeployment: one or more mutations could not be applied") return err } @@ -47,7 +47,7 @@ func (nr *NotificationsReconciler) reconcileDeployment() error { existingDeployment, err := workloads.GetDeployment(desiredDeployment.Name, desiredDeployment.Namespace, nr.Client) if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { nr.Logger.Error(err, "reconcileDeployment: failed to retrieve deployment", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) return err } @@ -60,36 +60,31 @@ func (nr *NotificationsReconciler) reconcileDeployment() error { nr.Logger.Error(err, "reconcileDeployment: failed to create deployment", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) return err } - nr.Logger.V(0).Info("reconcileDeployment: deployment created", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) + nr.Logger.Info("deployment created", "name", desiredDeployment.Name, "namespace", desiredDeployment.Namespace) return nil } deploymentChanged := false - fieldsToCompare := []struct { - existing, desired interface{} - extraAction func() - }{ - {&existingDeployment.Spec.Template.Spec.Containers[0].Image, &desiredDeployment.Spec.Template.Spec.Containers[0].Image, - func() { + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Image, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Image, + ExtraAction: func() { existingDeployment.Spec.Template.ObjectMeta.Labels[common.ImageUpgradedKey] = time.Now().UTC().Format(common.TimeFormatMST) }, }, - {&existingDeployment.Spec.Template.Spec.Containers[0].Command, &desiredDeployment.Spec.Template.Spec.Containers[0].Command, nil}, - {&existingDeployment.Spec.Template.Spec.Containers[0].Env, &desiredDeployment.Spec.Template.Spec.Containers[0].Env, nil}, - {&existingDeployment.Spec.Template.Spec.Containers[0].Resources, &desiredDeployment.Spec.Template.Spec.Containers[0].Resources, nil}, - {&existingDeployment.Spec.Template.Spec.Volumes, &desiredDeployment.Spec.Template.Spec.Volumes, nil}, - {&existingDeployment.Spec.Template.Spec.NodeSelector, &desiredDeployment.Spec.Template.Spec.NodeSelector, nil}, - {&existingDeployment.Spec.Template.Spec.Tolerations, &desiredDeployment.Spec.Template.Spec.Tolerations, nil}, - {&existingDeployment.Spec.Template.Spec.ServiceAccountName, &desiredDeployment.Spec.Template.Spec.ServiceAccountName, nil}, - {&existingDeployment.Spec.Template.Labels, &desiredDeployment.Spec.Template.Labels, nil}, - {&existingDeployment.Spec.Replicas, &desiredDeployment.Spec.Replicas, nil}, - {&existingDeployment.Spec.Selector, &desiredDeployment.Spec.Selector, nil}, - {&existingDeployment.Labels, &desiredDeployment.Labels, nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Command, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Command, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Env, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Env, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Containers[0].Resources, Desired: &desiredDeployment.Spec.Template.Spec.Containers[0].Resources, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Volumes, Desired: &desiredDeployment.Spec.Template.Spec.Volumes, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.NodeSelector, Desired: &desiredDeployment.Spec.Template.Spec.NodeSelector, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.Tolerations, Desired: &desiredDeployment.Spec.Template.Spec.Tolerations, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Spec.ServiceAccountName, Desired: &desiredDeployment.Spec.Template.Spec.ServiceAccountName, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Template.Labels, Desired: &desiredDeployment.Spec.Template.Labels, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Replicas, Desired: &desiredDeployment.Spec.Replicas, ExtraAction: nil}, + {Existing: &existingDeployment.Spec.Selector, Desired: &desiredDeployment.Spec.Selector, ExtraAction: nil}, + {Existing: &existingDeployment.Labels, Desired: &desiredDeployment.Labels, ExtraAction: nil}, } - for _, field := range fieldsToCompare { - argocdcommon.UpdateIfChanged(field.existing, field.desired, field.extraAction, &deploymentChanged) - } + argocdcommon.UpdateIfChanged(fieldsToCompare, &deploymentChanged) if deploymentChanged { @@ -99,7 +94,7 @@ func (nr *NotificationsReconciler) reconcileDeployment() error { } } - nr.Logger.V(0).Info("reconcileDeployment: deployment updated", "name", existingDeployment.Name, "namespace", existingDeployment.Namespace) + nr.Logger.Info("deployment updated", "name", existingDeployment.Name, "namespace", existingDeployment.Namespace) return nil } @@ -111,7 +106,7 @@ func (nr *NotificationsReconciler) deleteDeployment(name, namespace string) erro nr.Logger.Error(err, "DeleteDeployment: failed to delete deployment", "name", name, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteDeployment: deployment deleted", "name", name, "namespace", namespace) + nr.Logger.Info("deployment deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/notifications/deployment_test.go b/controllers/argocd/notifications/deployment_test.go index 2604003d6..3ccc4b9da 100644 --- a/controllers/argocd/notifications/deployment_test.go +++ b/controllers/argocd/notifications/deployment_test.go @@ -6,16 +6,16 @@ import ( "github.com/stretchr/testify/assert" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/types" ) func TestNotificationsReconciler_reconcileDeployment(t *testing.T) { - resourceName = argocdcommon.TestArgoCDName + resourceName = test.TestArgoCDName resourceLabels = testExpectedLabels - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) nr := makeTestNotificationsReconciler(t, ns) existingDeployment := nr.getDesiredDeployment() @@ -36,7 +36,7 @@ func TestNotificationsReconciler_reconcileDeployment(t *testing.T) { name: "update a deployment", setupClient: func() *NotificationsReconciler { outdatedDeployment := existingDeployment - outdatedDeployment.ObjectMeta.Labels = argocdcommon.TestKVP + outdatedDeployment.ObjectMeta.Labels = test.TestKVP return makeTestNotificationsReconciler(t, outdatedDeployment, ns) }, wantErr: false, @@ -55,7 +55,7 @@ func TestNotificationsReconciler_reconcileDeployment(t *testing.T) { } updatedDeployment := &appsv1.Deployment{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: resourceName, Namespace: argocdcommon.TestNamespace}, updatedDeployment) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: resourceName, Namespace: test.TestNamespace}, updatedDeployment) if err != nil { t.Fatalf("Could not get updated Deployment: %v", err) } @@ -65,8 +65,8 @@ func TestNotificationsReconciler_reconcileDeployment(t *testing.T) { } func TestNotificationsReconciler_DeleteDeployment(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *NotificationsReconciler diff --git a/controllers/argocd/notifications/notifications.go b/controllers/argocd/notifications/notifications.go index 27b6f0be5..705dce544 100644 --- a/controllers/argocd/notifications/notifications.go +++ b/controllers/argocd/notifications/notifications.go @@ -1,21 +1,20 @@ package notifications import ( - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/util" ) type NotificationsReconciler struct { Client client.Client Scheme *runtime.Scheme Instance *argoproj.ArgoCD - Logger logr.Logger + Logger *util.Logger } var ( @@ -25,8 +24,6 @@ var ( func (nr *NotificationsReconciler) Reconcile() error { - nr.Logger = ctrl.Log.WithName(common.NotificationsControllerComponent).WithValues("instance", nr.Instance.Name, "instance-namespace", nr.Instance.Namespace) - resourceName = argoutil.GenerateUniqueResourceName(nr.Instance.Name, nr.Instance.Namespace, common.NotificationsControllerComponent) resourceLabels = common.DefaultResourceLabels(resourceName, nr.Instance.Name, common.NotificationsControllerComponent) diff --git a/controllers/argocd/notifications/notifications_test.go b/controllers/argocd/notifications/notifications_test.go index c20331b2d..cc9773e69 100644 --- a/controllers/argocd/notifications/notifications_test.go +++ b/controllers/argocd/notifications/notifications_test.go @@ -6,34 +6,32 @@ import ( "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -var testExpectedLabels = common.DefaultResourceLabels(argocdcommon.TestArgoCDName, argocdcommon.TestNamespace, common.ArgoCDNotificationsControllerComponent) +var testExpectedLabels = common.DefaultResourceLabels(test.TestArgoCDName, test.TestNamespace, common.ArgoCDNotificationsControllerComponent) func makeTestNotificationsReconciler(t *testing.T, objs ...runtime.Object) *NotificationsReconciler { s := scheme.Scheme assert.NoError(t, argoproj.AddToScheme(s)) cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build() - logger := ctrl.Log.WithName(common.ArgoCDNotificationsControllerComponent) return &NotificationsReconciler{ Client: cl, Scheme: s, - Instance: argocdcommon.MakeTestArgoCD(), - Logger: logger, + Instance: test.MakeTestArgoCD(nil), + // Logger: logger, } } func TestNotificationsReconciler_Reconcile(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string resourceName string @@ -42,7 +40,7 @@ func TestNotificationsReconciler_Reconcile(t *testing.T) { }{ { name: "successful reconcile", - resourceName: argocdcommon.TestArgoCDName, + resourceName: test.TestArgoCDName, setupClient: func() *NotificationsReconciler { return makeTestNotificationsReconciler(t, ns) }, @@ -64,7 +62,7 @@ func TestNotificationsReconciler_Reconcile(t *testing.T) { } func TestNotificationsReconciler_DeleteResources(t *testing.T) { - resourceName = argocdcommon.TestArgoCDName + resourceName = test.TestArgoCDName tests := []struct { name string resourceName string @@ -73,7 +71,7 @@ func TestNotificationsReconciler_DeleteResources(t *testing.T) { }{ { name: "successful delete", - resourceName: argocdcommon.TestArgoCDName, + resourceName: test.TestArgoCDName, setupClient: func() *NotificationsReconciler { return makeTestNotificationsReconciler(t) }, diff --git a/controllers/argocd/notifications/role.go b/controllers/argocd/notifications/role.go index 165f27446..9b01c0bb0 100644 --- a/controllers/argocd/notifications/role.go +++ b/controllers/argocd/notifications/role.go @@ -8,7 +8,7 @@ import ( "github.com/argoproj-labs/argocd-operator/pkg/permissions" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -33,7 +33,7 @@ func (nr *NotificationsReconciler) reconcileRole() error { desiredRole, err := permissions.RequestRole(roleRequest) if err != nil { nr.Logger.Error(err, "reconcileRole: failed to request role", "name", desiredRole.Name, "namespace", desiredRole.Namespace) - nr.Logger.V(1).Info("reconcileRole: one or more mutations could not be applied") + nr.Logger.Debug("reconcileRole: one or more mutations could not be applied") return err } @@ -51,7 +51,7 @@ func (nr *NotificationsReconciler) reconcileRole() error { existingRole, err := permissions.GetRole(desiredRole.Name, desiredRole.Namespace, nr.Client) if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { nr.Logger.Error(err, "reconcileRole: failed to retrieve role", "name", desiredRole.Name, "namespace", desiredRole.Namespace) return err } @@ -64,7 +64,7 @@ func (nr *NotificationsReconciler) reconcileRole() error { nr.Logger.Error(err, "reconcileRole: failed to create role", "name", desiredRole.Name, "namespace", desiredRole.Namespace) return err } - nr.Logger.V(0).Info("reconcileRole: role created", "name", desiredRole.Name, "namespace", desiredRole.Namespace) + nr.Logger.Info("role created", "name", desiredRole.Name, "namespace", desiredRole.Namespace) return nil } @@ -75,7 +75,7 @@ func (nr *NotificationsReconciler) reconcileRole() error { return err } } - nr.Logger.V(0).Info("reconcileRole: role updated", "name", existingRole.Name, "namespace", existingRole.Namespace) + nr.Logger.Info("role updated", "name", existingRole.Name, "namespace", existingRole.Namespace) return nil } @@ -87,7 +87,7 @@ func (nr *NotificationsReconciler) deleteRole(name, namespace string) error { nr.Logger.Error(err, "DeleteRole: failed to delete role", "name", name, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteRole: role deleted", "name", name, "namespace", namespace) + nr.Logger.Info("role deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/notifications/role_test.go b/controllers/argocd/notifications/role_test.go index 3f9e73f69..23fe463ba 100644 --- a/controllers/argocd/notifications/role_test.go +++ b/controllers/argocd/notifications/role_test.go @@ -10,20 +10,20 @@ import ( "k8s.io/apimachinery/pkg/types" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" ) func TestNotificationsReconciler_reconcileRole(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName existingRole := &rbacv1.Role{ TypeMeta: metav1.TypeMeta{ Kind: common.RoleKind, APIVersion: common.APIGroupVersionRbacV1, }, ObjectMeta: metav1.ObjectMeta{ - Name: argocdcommon.TestArgoCDName, - Namespace: argocdcommon.TestNamespace, + Name: test.TestArgoCDName, + Namespace: test.TestNamespace, }, Rules: getPolicyRules(), } @@ -63,7 +63,7 @@ func TestNotificationsReconciler_reconcileRole(t *testing.T) { } updatedRole := &rbacv1.Role{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, updatedRole) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, updatedRole) if err != nil { t.Fatalf("Could not get updated Role: %v", err) } @@ -73,8 +73,8 @@ func TestNotificationsReconciler_reconcileRole(t *testing.T) { } func TestNotificationsReconciler_DeleteRole(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *NotificationsReconciler diff --git a/controllers/argocd/notifications/rolebinding.go b/controllers/argocd/notifications/rolebinding.go index ff7c05d9b..b8611610e 100644 --- a/controllers/argocd/notifications/rolebinding.go +++ b/controllers/argocd/notifications/rolebinding.go @@ -1,13 +1,15 @@ package notifications import ( + "reflect" + "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" "github.com/argoproj-labs/argocd-operator/pkg/cluster" "github.com/argoproj-labs/argocd-operator/pkg/permissions" + "github.com/pkg/errors" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -61,7 +63,7 @@ func (nr *NotificationsReconciler) reconcileRoleBinding() error { existingRoleBinding, err := permissions.GetRoleBinding(desiredRoleBinding.Name, desiredRoleBinding.Namespace, nr.Client) if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { nr.Logger.Error(err, "reconcileRoleBinding: failed to retrieve roleBinding", "name", desiredRoleBinding.Name, "namespace", desiredRoleBinding.Namespace) return err } @@ -78,32 +80,32 @@ func (nr *NotificationsReconciler) reconcileRoleBinding() error { return nil } - roleBindingChanged := false - fieldsToCompare := []struct { - existing, desired interface{} - }{ - { - &existingRoleBinding.RoleRef, - &desiredRoleBinding.RoleRef, - }, - { - &existingRoleBinding.Subjects, - &desiredRoleBinding.Subjects, - }, + // if roleRef differs, we must delete the rolebinding as kubernetes does not allow updation of roleRef + if !reflect.DeepEqual(existingRoleBinding.RoleRef, desiredRoleBinding.RoleRef) { + nr.Logger.Info("detected drift in roleRef for rolebinding", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) + if err := nr.deleteRoleBinding(resourceName, nr.Instance.Namespace); err != nil { + return errors.Wrapf(err, "reconcileRoleBinding: unable to delete obsolete rolebinding %s", existingRoleBinding.Name) + } + return nil + } + + rbChanged := false + + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existingRoleBinding.Subjects, Desired: &desiredRoleBinding.Subjects, ExtraAction: nil}, } - for _, field := range fieldsToCompare { - argocdcommon.UpdateIfChanged(field.existing, field.desired, nil, &roleBindingChanged) + argocdcommon.UpdateIfChanged(fieldsToCompare, &rbChanged) + + if !rbChanged { + return nil } - if roleBindingChanged { - if err = permissions.UpdateRoleBinding(existingRoleBinding, nr.Client); err != nil { - nr.Logger.Error(err, "reconcileRoleBinding: failed to update roleBinding", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) - return err - } + if err = permissions.UpdateRoleBinding(existingRoleBinding, nr.Client); err != nil { + return errors.Wrapf(err, "reconcileRoleBinding: failed to update role %s", existingRoleBinding.Name) } - nr.Logger.V(0).Info("reconcileRoleBinding: roleBinding updated", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) + nr.Logger.Info("rolebinding updated", "name", existingRoleBinding.Name, "namespace", existingRoleBinding.Namespace) return nil } @@ -116,6 +118,6 @@ func (nr *NotificationsReconciler) deleteRoleBinding(name, namespace string) err nr.Logger.Error(err, "DeleteRole: failed to delete roleBinding", "name", name, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteRoleBinding: roleBinding deleted", "name", name, "namespace", namespace) + nr.Logger.Info("roleBinding deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/notifications/rolebinding_test.go b/controllers/argocd/notifications/rolebinding_test.go index 2eb96d7c4..a94e9bebb 100644 --- a/controllers/argocd/notifications/rolebinding_test.go +++ b/controllers/argocd/notifications/rolebinding_test.go @@ -5,18 +5,21 @@ import ( "testing" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" ) func TestNotificationsReconciler_reconcileRoleBinding(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - sa := argocdcommon.MakeTestServiceAccount() - resourceName = argocdcommon.TestArgoCDName + resourceName = test.TestArgoCDName + ns := test.MakeTestNamespace(nil) + sa := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = resourceName + }) tests := []struct { name string @@ -39,8 +42,8 @@ func TestNotificationsReconciler_reconcileRoleBinding(t *testing.T) { APIVersion: common.APIGroupVersionRbacV1, }, ObjectMeta: metav1.ObjectMeta{ - Name: argocdcommon.TestArgoCDName, - Namespace: argocdcommon.TestNamespace, + Name: test.TestArgoCDName, + Namespace: test.TestNamespace, }, RoleRef: rbacv1.RoleRef{}, Subjects: []rbacv1.Subject{}, @@ -62,20 +65,20 @@ func TestNotificationsReconciler_reconcileRoleBinding(t *testing.T) { } } updatedRoleBinding := &rbacv1.RoleBinding{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, updatedRoleBinding) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, updatedRoleBinding) if err != nil { t.Fatalf("Could not get updated RoleBinding: %v", err) } - assert.Equal(t, argocdcommon.TestRoleRef, updatedRoleBinding.RoleRef) - assert.Equal(t, argocdcommon.TestSubjects, updatedRoleBinding.Subjects) + assert.Equal(t, test.MakeTestRoleRef(resourceName), updatedRoleBinding.RoleRef) + assert.Equal(t, test.MakeTestSubjects(types.NamespacedName{Name: resourceName, Namespace: test.TestNamespace}), updatedRoleBinding.Subjects) }) } } func TestNotificationsReconciler_DeleteRoleBinding(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - sa := argocdcommon.MakeTestServiceAccount() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + sa := test.MakeTestServiceAccount() + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *NotificationsReconciler diff --git a/controllers/argocd/notifications/secret.go b/controllers/argocd/notifications/secret.go index 6b84f6ac0..1fc036d1f 100644 --- a/controllers/argocd/notifications/secret.go +++ b/controllers/argocd/notifications/secret.go @@ -6,7 +6,6 @@ import ( "github.com/argoproj-labs/argocd-operator/pkg/mutation" "github.com/argoproj-labs/argocd-operator/pkg/workloads" - "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -30,7 +29,7 @@ func (nr *NotificationsReconciler) reconcileSecret() error { desiredSecret, err := workloads.RequestSecret(secretRequest) if err != nil { nr.Logger.Error(err, "reconcileSecret: failed to request secret", "name", desiredSecret.Name, "namespace", desiredSecret.Namespace) - nr.Logger.V(1).Info("reconcileSecret: one or more mutations could not be applied") + nr.Logger.Debug("reconcileSecret: one or more mutations could not be applied") return err } @@ -48,7 +47,7 @@ func (nr *NotificationsReconciler) reconcileSecret() error { _, err = workloads.GetSecret(desiredSecret.Name, desiredSecret.Namespace, nr.Client) if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { nr.Logger.Error(err, "reconcileSecret: failed to retrieve secret", "name", desiredSecret.Name, "namespace", desiredSecret.Namespace) return err } @@ -61,7 +60,7 @@ func (nr *NotificationsReconciler) reconcileSecret() error { nr.Logger.Error(err, "reconcileSecret: failed to create secret", "name", desiredSecret.Name, "namespace", desiredSecret.Namespace) return err } - nr.Logger.V(0).Info("reconcileSecret: secret created", "name", desiredSecret.Name, "namespace", desiredSecret.Namespace) + nr.Logger.Info("secret created", "name", desiredSecret.Name, "namespace", desiredSecret.Namespace) return nil } @@ -76,6 +75,6 @@ func (nr *NotificationsReconciler) deleteSecret(namespace string) error { nr.Logger.Error(err, "DeleteSecret: failed to delete secret", "name", common.NotificationsSecretName, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteSecret: secret deleted", "name", common.NotificationsSecretName, "namespace", namespace) + nr.Logger.Info("secret deleted", "name", common.NotificationsSecretName, "namespace", namespace) return nil } diff --git a/controllers/argocd/notifications/secret_test.go b/controllers/argocd/notifications/secret_test.go index f6b534af9..1af36d961 100644 --- a/controllers/argocd/notifications/secret_test.go +++ b/controllers/argocd/notifications/secret_test.go @@ -7,14 +7,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/tests/test" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" ) func TestNotificationsReconciler_reconcileSecret(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) resourceLabels = testExpectedLabels tests := []struct { name string @@ -42,7 +42,7 @@ func TestNotificationsReconciler_reconcileSecret(t *testing.T) { } currentSecret := &corev1.Secret{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: common.NotificationsSecretName, Namespace: argocdcommon.TestNamespace}, currentSecret) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: common.NotificationsSecretName, Namespace: test.TestNamespace}, currentSecret) if err != nil { t.Fatalf("Could not get current Secret: %v", err) } @@ -52,7 +52,7 @@ func TestNotificationsReconciler_reconcileSecret(t *testing.T) { } func TestNotificationsReconciler_DeleteSecret(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() + ns := test.MakeTestNamespace(nil) tests := []struct { name string setupClient func() *NotificationsReconciler diff --git a/controllers/argocd/notifications/serviceaccount.go b/controllers/argocd/notifications/serviceaccount.go index 2c9858b4c..d73e8b8f9 100644 --- a/controllers/argocd/notifications/serviceaccount.go +++ b/controllers/argocd/notifications/serviceaccount.go @@ -4,7 +4,6 @@ import ( "github.com/argoproj-labs/argocd-operator/pkg/cluster" "github.com/argoproj-labs/argocd-operator/pkg/permissions" - "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -39,7 +38,7 @@ func (nr *NotificationsReconciler) reconcileServiceAccount() error { _, err = permissions.GetServiceAccount(desiredServiceAccount.Name, desiredServiceAccount.Namespace, nr.Client) if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { nr.Logger.Error(err, "reconcileServiceAccount: failed to retrieve serviceAccount", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) return err } @@ -52,7 +51,7 @@ func (nr *NotificationsReconciler) reconcileServiceAccount() error { nr.Logger.Error(err, "reconcileServiceAccount: failed to create serviceAccount", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) return err } - nr.Logger.V(0).Info("reconcileServiceAccount: serviceAccount created", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) + nr.Logger.Info("serviceAccount created", "name", desiredServiceAccount.Name, "namespace", desiredServiceAccount.Namespace) return nil } @@ -67,6 +66,6 @@ func (nr *NotificationsReconciler) deleteServiceAccount(name, namespace string) nr.Logger.Error(err, "DeleteServiceAccount: failed to delete serviceAccount", "name", name, "namespace", namespace) return err } - nr.Logger.V(0).Info("DeleteServiceAccount: serviceAccount deleted", "name", name, "namespace", namespace) + nr.Logger.Info("serviceAccount deleted", "name", name, "namespace", namespace) return nil } diff --git a/controllers/argocd/notifications/serviceaccount_test.go b/controllers/argocd/notifications/serviceaccount_test.go index 208918af2..bfb6cc9bf 100644 --- a/controllers/argocd/notifications/serviceaccount_test.go +++ b/controllers/argocd/notifications/serviceaccount_test.go @@ -4,16 +4,15 @@ import ( "context" "testing" + "github.com/argoproj-labs/argocd-operator/tests/test" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - - "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" ) func TestNotificationsReconciler_reconcileServiceAccount(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName resourceLabels = testExpectedLabels tests := []struct { @@ -42,7 +41,7 @@ func TestNotificationsReconciler_reconcileServiceAccount(t *testing.T) { } currentServiceAccount := &corev1.ServiceAccount{} - err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: argocdcommon.TestArgoCDName, Namespace: argocdcommon.TestNamespace}, currentServiceAccount) + err = nr.Client.Get(context.TODO(), types.NamespacedName{Name: test.TestArgoCDName, Namespace: test.TestNamespace}, currentServiceAccount) if err != nil { t.Fatalf("Could not get current ServiceAccount: %v", err) } @@ -52,8 +51,8 @@ func TestNotificationsReconciler_reconcileServiceAccount(t *testing.T) { } func TestNotificationsReconciler_DeleteServiceAccount(t *testing.T) { - ns := argocdcommon.MakeTestNamespace() - resourceName = argocdcommon.TestArgoCDName + ns := test.MakeTestNamespace(nil) + resourceName = test.TestArgoCDName tests := []struct { name string setupClient func() *NotificationsReconciler diff --git a/controllers/argocd/notifications/status.go b/controllers/argocd/notifications/status.go new file mode 100644 index 000000000..a04f3a21f --- /dev/null +++ b/controllers/argocd/notifications/status.go @@ -0,0 +1,32 @@ +package notifications + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// reconcileStatus will ensure that the notifications controller status is updated for the given ArgoCD instance +func (nr *NotificationsReconciler) ReconcileStatus() error { + + // TO DO + + return nr.updateInstanceStatus() +} + +func (nr *NotificationsReconciler) updateInstanceStatus() error { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := nr.Client.Status().Update(context.TODO(), nr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/notifications_TOBEREMOVED.go b/controllers/argocd/notifications_TOBEREMOVED.go new file mode 100644 index 000000000..0decc742f --- /dev/null +++ b/controllers/argocd/notifications_TOBEREMOVED.go @@ -0,0 +1,1098 @@ +package argocd + +import ( + "context" + "fmt" + "reflect" + "time" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// getNotificationsResources will return the ResourceRequirements for the Notifications container. +func getNotificationsResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements { + resources := corev1.ResourceRequirements{} + + // Allow override of resource requirements from CR + if cr.Spec.Notifications.Resources != nil { + resources = *cr.Spec.Notifications.Resources + } + + return resources +} + +func getNotificationsCommand(cr *argoproj.ArgoCD) []string { + + cmd := make([]string, 0) + cmd = append(cmd, "argocd-notifications") + + cmd = append(cmd, "--loglevel") + cmd = append(cmd, getLogLevel(cr.Spec.Notifications.LogLevel)) + + return cmd +} + +// reconcileNotificationsConfigMap only creates/deletes the argocd-notifications-cm based on whether notifications is enabled/disabled in the CR +// It does not reconcile/overwrite any fields or information in the configmap itself +func (r *ReconcileArgoCD) reconcileNotificationsConfigMap(cr *argoproj.ArgoCD) error { + + desiredConfigMap := newConfigMapWithName("argocd-notifications-cm", cr) + desiredConfigMap.Data = getDefaultNotificationsConfig() + + cmExists := true + existingConfigMap := &corev1.ConfigMap{} + if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredConfigMap.Name, existingConfigMap); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get the configmap associated with %s : %s", desiredConfigMap.Name, err) + } + cmExists = false + } + + if cmExists { + // CM exists but shouldn't, so it should be deleted + if !cr.Spec.Notifications.Enabled { + log.Info(fmt.Sprintf("Deleting configmap %s as notifications is disabled", existingConfigMap.Name)) + return r.Client.Delete(context.TODO(), existingConfigMap) + } + + // CM exists and should, nothing to do here + return nil + } + + // CM doesn't exist and shouldn't, nothing to do here + if !cr.Spec.Notifications.Enabled { + return nil + } + + // CM doesn't exist but should, so it should be created + if err := controllerutil.SetControllerReference(cr, desiredConfigMap, r.Scheme); err != nil { + return err + } + + log.Info(fmt.Sprintf("Creating configmap %s", desiredConfigMap.Name)) + err := r.Client.Create(context.TODO(), desiredConfigMap) + if err != nil { + return err + } + + return nil +} + +// reconcileNotificationsSecret only creates/deletes the argocd-notifications-secret based on whether notifications is enabled/disabled in the CR +// It does not reconcile/overwrite any fields or information in the secret itself +func (r *ReconcileArgoCD) reconcileNotificationsSecret(cr *argoproj.ArgoCD) error { + + desiredSecret := argoutil.NewSecretWithName(cr, "argocd-notifications-secret") + + secretExists := true + existingSecret := &corev1.Secret{} + if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredSecret.Name, existingSecret); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get the secret associated with %s : %s", desiredSecret.Name, err) + } + secretExists = false + } + + if secretExists { + // secret exists but shouldn't, so it should be deleted + if !cr.Spec.Notifications.Enabled { + log.Info(fmt.Sprintf("Deleting secret %s as notifications is disabled", existingSecret.Name)) + return r.Client.Delete(context.TODO(), existingSecret) + } + + // secret exists and should, nothing to do here + return nil + } + + // secret doesn't exist and shouldn't, nothing to do here + if !cr.Spec.Notifications.Enabled { + return nil + } + + // secret doesn't exist but should, so it should be created + if err := controllerutil.SetControllerReference(cr, desiredSecret, r.Scheme); err != nil { + return err + } + + log.Info(fmt.Sprintf("Creating secret %s", desiredSecret.Name)) + err := r.Client.Create(context.TODO(), desiredSecret) + if err != nil { + return err + } + + return nil +} + +func (r *ReconcileArgoCD) reconcileNotificationsController(cr *argoproj.ArgoCD) error { + + log.Info("reconciling notifications serviceaccount") + sa, err := r.reconcileNotificationsServiceAccount(cr) + if err != nil { + return err + } + + log.Info("reconciling notifications role") + role, err := r.reconcileNotificationsRole(cr) + if err != nil { + return err + } + + log.Info("reconciling notifications role binding") + if err := r.reconcileNotificationsRoleBinding(cr, role, sa); err != nil { + return err + } + + log.Info("reconciling notifications configmap") + if err := r.reconcileNotificationsConfigMap(cr); err != nil { + return err + } + + log.Info("reconciling notifications secret") + if err := r.reconcileNotificationsSecret(cr); err != nil { + return err + } + + log.Info("reconciling notifications deployment") + if err := r.reconcileNotificationsDeployment(cr, sa); err != nil { + return err + } + + return nil +} + +// The code to create/delete notifications resources is written within the reconciliation logic itself. However, these functions must be called +// in the right order depending on whether resources are getting created or deleted. During creation we must create the role and sa first. +// RoleBinding and deployment are dependent on these resouces. During deletion the order is reversed. +// Deployment and RoleBinding must be deleted before the role and sa. deleteNotificationsResources will only be called during +// delete events, so we don't need to worry about duplicate, recurring reconciliation calls +func (r *ReconcileArgoCD) deleteNotificationsResources(cr *argoproj.ArgoCD) error { + + sa := &corev1.ServiceAccount{} + role := &rbacv1.Role{} + + if err := argoutil.FetchObject(r.Client, cr.Namespace, fmt.Sprintf("%s-%s", cr.Name, common.ArgoCDNotificationsControllerComponent), sa); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + } + if err := argoutil.FetchObject(r.Client, cr.Namespace, fmt.Sprintf("%s-%s", cr.Name, common.ArgoCDNotificationsControllerComponent), role); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + } + + log.Info("reconciling notifications deployment") + if err := r.reconcileNotificationsDeployment(cr, sa); err != nil { + return err + } + + log.Info("reconciling notifications secret") + if err := r.reconcileNotificationsSecret(cr); err != nil { + return err + } + + log.Info("reconciling notifications configmap") + if err := r.reconcileNotificationsConfigMap(cr); err != nil { + return err + } + + log.Info("reconciling notifications role binding") + if err := r.reconcileNotificationsRoleBinding(cr, role, sa); err != nil { + return err + } + + log.Info("reconciling notifications role") + _, err := r.reconcileNotificationsRole(cr) + if err != nil { + return err + } + + log.Info("reconciling notifications serviceaccount") + _, err = r.reconcileNotificationsServiceAccount(cr) + if err != nil { + return err + } + + return nil +} + +func (r *ReconcileArgoCD) reconcileNotificationsServiceAccount(cr *argoproj.ArgoCD) (*corev1.ServiceAccount, error) { + + sa := newServiceAccountWithName(common.ArgoCDNotificationsControllerComponent, cr) + + if err := argoutil.FetchObject(r.Client, cr.Namespace, sa.Name, sa); err != nil { + if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to get the serviceAccount associated with %s : %s", sa.Name, err) + } + + // SA doesn't exist and shouldn't, nothing to do here + if !cr.Spec.Notifications.Enabled { + return nil, nil + } + + // SA doesn't exist but should, so it should be created + if err := controllerutil.SetControllerReference(cr, sa, r.Scheme); err != nil { + return nil, err + } + + log.Info(fmt.Sprintf("Creating serviceaccount %s", sa.Name)) + err := r.Client.Create(context.TODO(), sa) + if err != nil { + return nil, err + } + } + + // SA exists but shouldn't, so it should be deleted + if !cr.Spec.Notifications.Enabled { + log.Info(fmt.Sprintf("Deleting serviceaccount %s as notifications is disabled", sa.Name)) + return nil, r.Client.Delete(context.TODO(), sa) + } + + return sa, nil +} + +func (r *ReconcileArgoCD) reconcileNotificationsRole(cr *argoproj.ArgoCD) (*rbacv1.Role, error) { + + policyRules := policyRuleForNotificationsController() + desiredRole := newRole(common.ArgoCDNotificationsControllerComponent, policyRules, cr) + + existingRole := &rbacv1.Role{} + if err := argoutil.FetchObject(r.Client, cr.Namespace, desiredRole.Name, existingRole); err != nil { + if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to get the role associated with %s : %s", desiredRole.Name, err) + } + + // role does not exist and shouldn't, nothing to do here + if !cr.Spec.Notifications.Enabled { + return nil, nil + } + + // role does not exist but should, so it should be created + if err := controllerutil.SetControllerReference(cr, desiredRole, r.Scheme); err != nil { + return nil, err + } + + log.Info(fmt.Sprintf("Creating role %s", desiredRole.Name)) + err := r.Client.Create(context.TODO(), desiredRole) + if err != nil { + return nil, err + } + return desiredRole, nil + } + + // role exists but shouldn't, so it should be deleted + if !cr.Spec.Notifications.Enabled { + log.Info(fmt.Sprintf("Deleting role %s as notifications is disabled", existingRole.Name)) + return nil, r.Client.Delete(context.TODO(), existingRole) + } + + // role exists and should. Reconcile role if changed + if !reflect.DeepEqual(existingRole.Rules, desiredRole.Rules) { + existingRole.Rules = desiredRole.Rules + if err := controllerutil.SetControllerReference(cr, existingRole, r.Scheme); err != nil { + return nil, err + } + return existingRole, r.Client.Update(context.TODO(), existingRole) + } + + return desiredRole, nil +} + +func (r *ReconcileArgoCD) reconcileNotificationsRoleBinding(cr *argoproj.ArgoCD, role *rbacv1.Role, sa *corev1.ServiceAccount) error { + + desiredRoleBinding := newRoleBindingWithname(common.ArgoCDNotificationsControllerComponent, cr) + desiredRoleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: role.Name, + } + + desiredRoleBinding.Subjects = []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: sa.Name, + Namespace: sa.Namespace, + }, + } + + // fetch existing rolebinding by name + existingRoleBinding := &rbacv1.RoleBinding{} + if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: desiredRoleBinding.Name, Namespace: cr.Namespace}, existingRoleBinding); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get the rolebinding associated with %s : %s", desiredRoleBinding.Name, err) + } + + // roleBinding does not exist and shouldn't, nothing to do here + if !cr.Spec.Notifications.Enabled { + return nil + } + + // roleBinding does not exist but should, so it should be created + if err := controllerutil.SetControllerReference(cr, desiredRoleBinding, r.Scheme); err != nil { + return err + } + + log.Info(fmt.Sprintf("Creating roleBinding %s", desiredRoleBinding.Name)) + return r.Client.Create(context.TODO(), desiredRoleBinding) + } + + // roleBinding exists but shouldn't, so it should be deleted + if !cr.Spec.Notifications.Enabled { + log.Info(fmt.Sprintf("Deleting roleBinding %s as notifications is disabled", existingRoleBinding.Name)) + return r.Client.Delete(context.TODO(), existingRoleBinding) + } + + // roleBinding exists and should. Reconcile roleBinding if changed + if !reflect.DeepEqual(existingRoleBinding.RoleRef, desiredRoleBinding.RoleRef) { + // if the RoleRef changes, delete the existing role binding and create a new one + if err := r.Client.Delete(context.TODO(), existingRoleBinding); err != nil { + return err + } + } else if !reflect.DeepEqual(existingRoleBinding.Subjects, desiredRoleBinding.Subjects) { + existingRoleBinding.Subjects = desiredRoleBinding.Subjects + if err := controllerutil.SetControllerReference(cr, existingRoleBinding, r.Scheme); err != nil { + return err + } + return r.Client.Update(context.TODO(), existingRoleBinding) + } + + return nil +} + +func (r *ReconcileArgoCD) reconcileNotificationsDeployment(cr *argoproj.ArgoCD, sa *corev1.ServiceAccount) error { + + desiredDeployment := newDeploymentWithSuffix("notifications-controller", "controller", cr) + + desiredDeployment.Spec.Strategy = appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + } + + if replicas := getArgoCDNotificationsControllerReplicas(cr); replicas != nil { + desiredDeployment.Spec.Replicas = replicas + } + + notificationEnv := cr.Spec.Notifications.Env + // Let user specify their own environment first + notificationEnv = argoutil.EnvMerge(notificationEnv, proxyEnvVars(), false) + + podSpec := &desiredDeployment.Spec.Template.Spec + podSpec.SecurityContext = &corev1.PodSecurityContext{ + RunAsNonRoot: boolPtr(true), + } + AddSeccompProfileForOpenShift(r.Client, podSpec) + podSpec.ServiceAccountName = sa.ObjectMeta.Name + podSpec.Volumes = []corev1.Volume{ + { + Name: "tls-certs", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDTLSCertsConfigMapName, + }, + }, + }, + }, + { + Name: "argocd-repo-server-tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: common.ArgoCDRepoServerTLSSecretName, + Optional: boolPtr(true), + }, + }, + }, + } + + podSpec.Containers = []corev1.Container{{ + Command: getNotificationsCommand(cr), + Image: getArgoContainerImage(cr), + ImagePullPolicy: corev1.PullAlways, + Name: common.ArgoCDNotificationsControllerComponent, + Env: notificationEnv, + Resources: getNotificationsResources(cr), + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.IntOrString{ + IntVal: int32(9001), + }, + }, + }, + }, + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: boolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "tls-certs", + MountPath: "/app/config/tls", + }, + { + Name: "argocd-repo-server-tls", + MountPath: "/app/config/reposerver/tls", + }, + }, + WorkingDir: "/app", + }} + + // fetch existing deployment by name + deploymentChanged := false + existingDeployment := &appsv1.Deployment{} + if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: desiredDeployment.Name, Namespace: cr.Namespace}, existingDeployment); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get the deployment associated with %s : %s", existingDeployment.Name, err) + } + + // deployment does not exist and shouldn't, nothing to do here + if !cr.Spec.Notifications.Enabled { + return nil + } + + // deployment does not exist but should, so it should be created + if err := controllerutil.SetControllerReference(cr, desiredDeployment, r.Scheme); err != nil { + return err + } + + log.Info(fmt.Sprintf("Creating deployment %s", desiredDeployment.Name)) + return r.Client.Create(context.TODO(), desiredDeployment) + } + + // deployment exists but shouldn't, so it should be deleted + if !cr.Spec.Notifications.Enabled { + log.Info(fmt.Sprintf("Deleting deployment %s as notifications is disabled", existingDeployment.Name)) + return r.Client.Delete(context.TODO(), existingDeployment) + } + + // deployment exists and should. Reconcile deployment if changed + updateNodePlacement(existingDeployment, desiredDeployment, &deploymentChanged) + + if existingDeployment.Spec.Template.Spec.Containers[0].Image != desiredDeployment.Spec.Template.Spec.Containers[0].Image { + existingDeployment.Spec.Template.Spec.Containers[0].Image = desiredDeployment.Spec.Template.Spec.Containers[0].Image + existingDeployment.Spec.Template.ObjectMeta.Labels["image.upgraded"] = time.Now().UTC().Format("01022006-150406-MST") + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].Command, desiredDeployment.Spec.Template.Spec.Containers[0].Command) { + existingDeployment.Spec.Template.Spec.Containers[0].Command = desiredDeployment.Spec.Template.Spec.Containers[0].Command + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].Env, + desiredDeployment.Spec.Template.Spec.Containers[0].Env) { + existingDeployment.Spec.Template.Spec.Containers[0].Env = desiredDeployment.Spec.Template.Spec.Containers[0].Env + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Volumes, desiredDeployment.Spec.Template.Spec.Volumes) { + existingDeployment.Spec.Template.Spec.Volumes = desiredDeployment.Spec.Template.Spec.Volumes + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Replicas, desiredDeployment.Spec.Replicas) { + existingDeployment.Spec.Replicas = desiredDeployment.Spec.Replicas + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].VolumeMounts, desiredDeployment.Spec.Template.Spec.Containers[0].VolumeMounts) { + existingDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = desiredDeployment.Spec.Template.Spec.Containers[0].VolumeMounts + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.Containers[0].Resources, desiredDeployment.Spec.Template.Spec.Containers[0].Resources) { + existingDeployment.Spec.Template.Spec.Containers[0].Resources = desiredDeployment.Spec.Template.Spec.Containers[0].Resources + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Spec.ServiceAccountName, desiredDeployment.Spec.Template.Spec.ServiceAccountName) { + existingDeployment.Spec.Template.Spec.ServiceAccountName = desiredDeployment.Spec.Template.Spec.ServiceAccountName + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Labels, desiredDeployment.Labels) { + existingDeployment.Labels = desiredDeployment.Labels + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Template.Labels, desiredDeployment.Spec.Template.Labels) { + existingDeployment.Spec.Template.Labels = desiredDeployment.Spec.Template.Labels + deploymentChanged = true + } + + if !reflect.DeepEqual(existingDeployment.Spec.Selector, desiredDeployment.Spec.Selector) { + existingDeployment.Spec.Selector = desiredDeployment.Spec.Selector + deploymentChanged = true + } + + if deploymentChanged { + return r.Client.Update(context.TODO(), existingDeployment) + } + + return nil + +} + +// getDefaultNotificationsConfig returns a map that contains default triggers and template configurations for argocd-notifications-cm +func getDefaultNotificationsConfig() map[string]string { + + notificationsConfig := make(map[string]string) + + // configure default notifications templates + + notificationsConfig["template.app-created"] = `email: + subject: Application {{.app.metadata.name}} has been created. +message: Application {{.app.metadata.name}} has been created. +teams: + title: Application {{.app.metadata.name}} has been created.` + + notificationsConfig["template.app-deleted"] = `email: + subject: Application {{.app.metadata.name}} has been deleted. +message: Application {{.app.metadata.name}} has been deleted. +teams: + title: Application {{.app.metadata.name}} has been deleted.` + + notificationsConfig["template.app-deployed"] = `email: + subject: New version of an application {{.app.metadata.name}} is up and running. +message: | + {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} is now running new version of deployments manifests. +slack: + attachments: | + [{ + "title": "{{ .app.metadata.name}}", + "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", + "color": "#18be52", + "fields": [ + { + "title": "Sync Status", + "value": "{{.app.status.sync.status}}", + "short": true + }, + { + "title": "Repository", + "value": "{{.app.spec.source.repoURL}}", + "short": true + }, + { + "title": "Revision", + "value": "{{.app.status.sync.revision}}", + "short": true + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "title": "{{$c.type}}", + "value": "{{$c.message}}", + "short": true + } + {{end}} + ] + }] + deliveryPolicy: Post + groupingKey: "" + notifyBroadcast: false +teams: + facts: | + [{ + "name": "Sync Status", + "value": "{{.app.status.sync.status}}" + }, + { + "name": "Repository", + "value": "{{.app.spec.source.repoURL}}" + }, + { + "name": "Revision", + "value": "{{.app.status.sync.revision}}" + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "name": "{{$c.type}}", + "value": "{{$c.message}}" + } + {{end}} + ] + potentialAction: |- + [{ + "@type":"OpenUri", + "name":"Operation Application", + "targets":[{ + "os":"default", + "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}" + }] + }, + { + "@type":"OpenUri", + "name":"Open Repository", + "targets":[{ + "os":"default", + "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" + }] + }] + themeColor: '#000080' + title: New version of an application {{.app.metadata.name}} is up and running.` + + notificationsConfig["template.app-health-degraded"] = `email: + subject: Application {{.app.metadata.name}} has degraded. +message: | + {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} has degraded. + Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}. +slack: + attachments: | + [{ + "title": "{{ .app.metadata.name}}", + "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", + "color": "#f4c030", + "fields": [ + { + "title": "Health Status", + "value": "{{.app.status.health.status}}", + "short": true + }, + { + "title": "Repository", + "value": "{{.app.spec.source.repoURL}}", + "short": true + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "title": "{{$c.type}}", + "value": "{{$c.message}}", + "short": true + } + {{end}} + ] + }] + deliveryPolicy: Post + groupingKey: "" + notifyBroadcast: false +teams: + facts: | + [{ + "name": "Health Status", + "value": "{{.app.status.health.status}}" + }, + { + "name": "Repository", + "value": "{{.app.spec.source.repoURL}}" + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "name": "{{$c.type}}", + "value": "{{$c.message}}" + } + {{end}} + ] + potentialAction: | + [{ + "@type":"OpenUri", + "name":"Open Application", + "targets":[{ + "os":"default", + "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}" + }] + }, + { + "@type":"OpenUri", + "name":"Open Repository", + "targets":[{ + "os":"default", + "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" + }] + }] + themeColor: '#FF0000' + title: Application {{.app.metadata.name}} has degraded.` + + notificationsConfig["template.app-sync-failed"] = `email: + subject: Failed to sync application {{.app.metadata.name}}. +message: | + {{if eq .serviceType "slack"}}:exclamation:{{end}} The sync operation of application {{.app.metadata.name}} has failed at {{.app.status.operationState.finishedAt}} with the following error: {{.app.status.operationState.message}} + Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true . +slack: + attachments: | + [{ + "title": "{{ .app.metadata.name}}", + "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", + "color": "#E96D76", + "fields": [ + { + "title": "Sync Status", + "value": "{{.app.status.sync.status}}", + "short": true + }, + { + "title": "Repository", + "value": "{{.app.spec.source.repoURL}}", + "short": true + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "title": "{{$c.type}}", + "value": "{{$c.message}}", + "short": true + } + {{end}} + ] + }] + deliveryPolicy: Post + groupingKey: "" + notifyBroadcast: false +teams: + facts: | + [{ + "name": "Sync Status", + "value": "{{.app.status.sync.status}}" + }, + { + "name": "Failed at", + "value": "{{.app.status.operationState.finishedAt}}" + }, + { + "name": "Repository", + "value": "{{.app.spec.source.repoURL}}" + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "name": "{{$c.type}}", + "value": "{{$c.message}}" + } + {{end}} + ] + potentialAction: |- + [{ + "@type":"OpenUri", + "name":"Open Operation", + "targets":[{ + "os":"default", + "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" + }] + }, + { + "@type":"OpenUri", + "name":"Open Repository", + "targets":[{ + "os":"default", + "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" + }] + }] + themeColor: '#FF0000' + title: Failed to sync application {{.app.metadata.name}}.` + + notificationsConfig["template.app-sync-running"] = `email: + subject: Start syncing application {{.app.metadata.name}}. +message: | + The sync operation of application {{.app.metadata.name}} has started at {{.app.status.operationState.startedAt}}. + Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true . +slack: + attachments: | + [{ + "title": "{{ .app.metadata.name}}", + "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", + "color": "#0DADEA", + "fields": [ + { + "title": "Sync Status", + "value": "{{.app.status.sync.status}}", + "short": true + }, + { + "title": "Repository", + "value": "{{.app.spec.source.repoURL}}", + "short": true + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "title": "{{$c.type}}", + "value": "{{$c.message}}", + "short": true + } + {{end}} + ] + }] + deliveryPolicy: Post + groupingKey: "" + notifyBroadcast: false +teams: + facts: | + [{ + "name": "Sync Status", + "value": "{{.app.status.sync.status}}" + }, + { + "name": "Started at", + "value": "{{.app.status.operationState.startedAt}}" + }, + { + "name": "Repository", + "value": "{{.app.spec.source.repoURL}}" + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "name": "{{$c.type}}", + "value": "{{$c.message}}" + } + {{end}} + ] + potentialAction: |- + [{ + "@type":"OpenUri", + "name":"Open Operation", + "targets":[{ + "os":"default", + "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" + }] + }, + { + "@type":"OpenUri", + "name":"Open Repository", + "targets":[{ + "os":"default", + "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" + }] + }] + title: Start syncing application {{.app.metadata.name}}.` + + notificationsConfig["template.app-sync-status-unknown"] = `email: + subject: Application {{.app.metadata.name}} sync status is 'Unknown' +message: | + {{if eq .serviceType "slack"}}:exclamation:{{end}} Application {{.app.metadata.name}} sync is 'Unknown'. + Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}. + {{if ne .serviceType "slack"}} + {{range $c := .app.status.conditions}} + * {{$c.message}} + {{end}} + {{end}} +slack: + attachments: | + [{ + "title": "{{ .app.metadata.name}}", + "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", + "color": "#E96D76", + "fields": [ + { + "title": "Sync Status", + "value": "{{.app.status.sync.status}}", + "short": true + }, + { + "title": "Repository", + "value": "{{.app.spec.source.repoURL}}", + "short": true + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "title": "{{$c.type}}", + "value": "{{$c.message}}", + "short": true + } + {{end}} + ] + }] + deliveryPolicy: Post + groupingKey: "" + notifyBroadcast: false +teams: + facts: | + [{ + "name": "Sync Status", + "value": "{{.app.status.sync.status}}" + }, + { + "name": "Repository", + "value": "{{.app.spec.source.repoURL}}" + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "name": "{{$c.type}}", + "value": "{{$c.message}}" + } + {{end}} + ] + potentialAction: |- + [{ + "@type":"OpenUri", + "name":"Open Application", + "targets":[{ + "os":"default", + "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}" + }] + }, + { + "@type":"OpenUri", + "name":"Open Repository", + "targets":[{ + "os":"default", + "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" + }] + }] + title: Application {{.app.metadata.name}} sync status is 'Unknown'` + + notificationsConfig["template.app-sync-succeeded"] = `email: + subject: Application {{.app.metadata.name}} has been successfully synced. +message: | + {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} has been successfully synced at {{.app.status.operationState.finishedAt}}. + Sync operation details are available at: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true . +slack: + attachments: | + [{ + "title": "{{ .app.metadata.name}}", + "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}", + "color": "#18be52", + "fields": [ + { + "title": "Sync Status", + "value": "{{.app.status.sync.status}}", + "short": true + }, + { + "title": "Repository", + "value": "{{.app.spec.source.repoURL}}", + "short": true + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "title": "{{$c.type}}", + "value": "{{$c.message}}", + "short": true + } + {{end}} + ] + }] + deliveryPolicy: Post + groupingKey: "" + notifyBroadcast: false +teams: + facts: | + [{ + "name": "Sync Status", + "value": "{{.app.status.sync.status}}" + }, + { + "name": "Synced at", + "value": "{{.app.status.operationState.finishedAt}}" + }, + { + "name": "Repository", + "value": "{{.app.spec.source.repoURL}}" + } + {{range $index, $c := .app.status.conditions}} + {{if not $index}},{{end}} + {{if $index}},{{end}} + { + "name": "{{$c.type}}", + "value": "{{$c.message}}" + } + {{end}} + ] + potentialAction: |- + [{ + "@type":"OpenUri", + "name":"Operation Details", + "targets":[{ + "os":"default", + "uri":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" + }] + }, + { + "@type":"OpenUri", + "name":"Open Repository", + "targets":[{ + "os":"default", + "uri":"{{.app.spec.source.repoURL | call .repo.RepoURLToHTTPS}}" + }] + }] + themeColor: '#000080' + title: Application {{.app.metadata.name}} has been successfully synced` + + // configure default notifications triggers + + notificationsConfig["trigger.on-created"] = `- description: Application is created. + oncePer: app.metadata.name + send: + - app-created + when: "true"` + + notificationsConfig["trigger.on-deleted"] = `- description: Application is deleted. + oncePer: app.metadata.name + send: + - app-deleted + when: app.metadata.deletionTimestamp != nil` + + notificationsConfig["trigger.on-deployed"] = `- description: Application is synced and healthy. Triggered once per commit. + oncePer: app.status.operationState.syncResult.revision + send: + - app-deployed + when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status + == 'Healthy'` + + notificationsConfig["trigger.on-health-degraded"] = `- description: Application has degraded + send: + - app-health-degraded + when: app.status.health.status == 'Degraded'` + + notificationsConfig["trigger.on-sync-failed"] = `- description: Application syncing has failed + send: + - app-sync-failed + when: app.status.operationState.phase in ['Error', 'Failed']` + + notificationsConfig["trigger.on-sync-running"] = `- description: Application is being synced + send: + - app-sync-running + when: app.status.operationState.phase in ['Running']` + + notificationsConfig["trigger.on-sync-status-unknown"] = `- description: Application status is 'Unknown' + send: + - app-sync-status-unknown + when: app.status.sync.status == 'Unknown'` + + notificationsConfig["trigger.on-sync-succeeded"] = `- description: Application syncing has succeeded + send: + - app-sync-succeeded + when: app.status.operationState.phase in ['Succeeded']` + + return notificationsConfig +} + +// getArgoCDNotificationsControllerReplicas will return the size value for the argocd-notifications-controller replica count if it +// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or +// replicas value is < 0. +func getArgoCDNotificationsControllerReplicas(cr *argoproj.ArgoCD) *int32 { + if cr.Spec.Notifications.Replicas != nil && *cr.Spec.Notifications.Replicas >= 0 { + return cr.Spec.Notifications.Replicas + } + + return nil +} diff --git a/controllers/argocd/prometheus.go b/controllers/argocd/prometheus.go index 2d8ccdb50..28c0fd05c 100644 --- a/controllers/argocd/prometheus.go +++ b/controllers/argocd/prometheus.go @@ -169,38 +169,6 @@ func (r *ReconcileArgoCD) reconcilePrometheus(cr *argoproj.ArgoCD) error { return r.Client.Create(context.TODO(), prometheus) } -// reconcileRepoServerServiceMonitor will ensure that the ServiceMonitor is present for the Repo Server metrics Service. -func (r *ReconcileArgoCD) reconcileRepoServerServiceMonitor(cr *argoproj.ArgoCD) error { - sm := newServiceMonitorWithSuffix("repo-server-metrics", cr) - if argoutil.IsObjectFound(r.Client, cr.Namespace, sm.Name, sm) { - if !cr.Spec.Prometheus.Enabled { - // ServiceMonitor exists but enabled flag has been set to false, delete the ServiceMonitor - return r.Client.Delete(context.TODO(), sm) - } - return nil // ServiceMonitor found, do nothing - } - - if !cr.Spec.Prometheus.Enabled { - return nil // Prometheus not enabled, do nothing. - } - - sm.Spec.Selector = metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.ArgoCDKeyName: nameWithSuffix("repo-server", cr), - }, - } - sm.Spec.Endpoints = []monitoringv1.Endpoint{ - { - Port: common.ArgoCDMetrics, - }, - } - - if err := controllerutil.SetControllerReference(cr, sm, r.Scheme); err != nil { - return err - } - return r.Client.Create(context.TODO(), sm) -} - // reconcileServerMetricsServiceMonitor will ensure that the ServiceMonitor is present for the ArgoCD Server metrics Service. func (r *ReconcileArgoCD) reconcileServerMetricsServiceMonitor(cr *argoproj.ArgoCD) error { sm := newServiceMonitorWithSuffix("server-metrics", cr) diff --git a/controllers/argocd/redis/helper.go b/controllers/argocd/redis/helper.go new file mode 100644 index 000000000..b9e6602da --- /dev/null +++ b/controllers/argocd/redis/helper.go @@ -0,0 +1,41 @@ +package redis + +import ( + "reflect" + + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" +) + +// UseTLS decides whether Redis component should communicate with TLS or not +func (rr *RedisReconciler) UseTLS() bool { + tlsSecret, err := workloads.GetSecret(common.ArgoCDRedisServerTLSSecretName, rr.Instance.Namespace, rr.Client) + if err != nil { + if apierrors.IsNotFound(err) { + rr.Logger.Debug("skipping TLS enforcement") + return false + } + rr.Logger.Error(err, "UseTLS: failed to retrieve tls secret", "name", common.ArgoCDRedisServerTLSSecretName, "namespace", rr.Instance.Namespace) + return false + } + + secretOwner, err := argocdcommon.FindSecretOwnerInstance(types.NamespacedName{Name: tlsSecret.Name, Namespace: tlsSecret.Namespace}, rr.Client) + if err != nil { + rr.Logger.Error(err, "UseTLS: failed to find secret owning instance") + return false + } + + if !reflect.DeepEqual(secretOwner, types.NamespacedName{}) { + return true + } + + return false +} + +// GetServerAddress will return the Redis service address for the given ArgoCD instance +func (rr *RedisReconciler) GetServerAddress() string { + return "" +} diff --git a/controllers/argocd/redis/redis.go b/controllers/argocd/redis/redis.go index 7a5b4e58e..41e7dd624 100644 --- a/controllers/argocd/redis/redis.go +++ b/controllers/argocd/redis/redis.go @@ -1,18 +1,18 @@ package redis import ( - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/pkg/util" ) type RedisReconciler struct { Client client.Client Scheme *runtime.Scheme Instance *argoproj.ArgoCD - Logger logr.Logger + Logger *util.Logger } func (rr *RedisReconciler) Reconcile() error { diff --git a/controllers/argocd/redis/status.go b/controllers/argocd/redis/status.go new file mode 100644 index 000000000..c6202a120 --- /dev/null +++ b/controllers/argocd/redis/status.go @@ -0,0 +1,32 @@ +package redis + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// reconcileStatusRedis will ensure that the Redis status is updated for the given ArgoCD instance +func (rr *RedisReconciler) ReconcileStatus() error { + + // TO DO + + return rr.updateInstanceStatus() +} + +func (rr *RedisReconciler) updateInstanceStatus() error { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := rr.Client.Status().Update(context.TODO(), rr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/redis_TOBEREMOVED.go b/controllers/argocd/redis_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/redis_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/reposerver/constants.go b/controllers/argocd/reposerver/constants.go deleted file mode 100644 index 0a891a65f..000000000 --- a/controllers/argocd/reposerver/constants.go +++ /dev/null @@ -1,5 +0,0 @@ -package reposerver - -const ( - ArgoCDRepoServerControllerComponent = "repo-server" -) diff --git a/controllers/argocd/reposerver/deployment.go b/controllers/argocd/reposerver/deployment.go new file mode 100644 index 000000000..c6c4e8ec2 --- /dev/null +++ b/controllers/argocd/reposerver/deployment.go @@ -0,0 +1,375 @@ +package reposerver + +import ( + "fmt" + "reflect" + "time" + + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +const ( + redisTLSPath = "/app/config/reposerver/tls/redis" + cmpServerPluginsPath = "/home/argocd/cmp-server/plugins" +) + +func (rsr *RepoServerReconciler) reconcileDeployment() error { + + req := rsr.getDeploymentRequest() + + desired, err := workloads.RequestDeployment(req) + if err != nil { + return errors.Wrapf(err, "reconcileDeployment: failed to reconcile deployment %s", desired.Name) + } + + if err = controllerutil.SetControllerReference(rsr.Instance, desired, rsr.Scheme); err != nil { + rsr.Logger.Error(err, "reconcileDeployment: failed to set owner reference for deployment", "name", desired.Name, "namespace", desired.Namespace) + } + + existing, err := workloads.GetDeployment(desired.Name, desired.Namespace, rsr.Client) + if err != nil { + if !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "reconcileDeployment: failed to retrieve deployment %s", desired.Name) + } + + if err = workloads.CreateDeployment(desired, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileDeployment: failed to create deployment %s in namespace %s", desired.Name, desired.Namespace) + } + rsr.Logger.Info("deployment created", "name", desired.Name, "namespace", desired.Namespace) + return nil + } + + changed := false + + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existing.Spec.Template.Spec.Containers[0].Image, Desired: &desired.Spec.Template.Spec.Containers[0].Image, + ExtraAction: func() { + if existing.Spec.Template.ObjectMeta.Labels == nil { + existing.Spec.Template.ObjectMeta.Labels = map[string]string{} + } + existing.Spec.Template.ObjectMeta.Labels[common.ImageUpgradedKey] = time.Now().UTC().Format(common.TimeFormatMST) + }, + }, + {Existing: &existing.Spec.Template.Spec.NodeSelector, Desired: &desired.Spec.Template.Spec.NodeSelector, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.Tolerations, Desired: &desired.Spec.Template.Spec.Tolerations, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.Volumes, Desired: &desired.Spec.Template.Spec.Volumes, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.Containers[0].Command, Desired: &desired.Spec.Template.Spec.Containers[0].Command, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.Containers[0].Env, Desired: &desired.Spec.Template.Spec.Containers[0].Env, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.Containers[0].Resources, Desired: &desired.Spec.Template.Spec.Containers[0].Resources, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.Containers[0].VolumeMounts, Desired: &desired.Spec.Template.Spec.Containers[0].VolumeMounts, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.InitContainers, Desired: &desired.Spec.Template.Spec.InitContainers, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.AutomountServiceAccountToken, Desired: &desired.Spec.Template.Spec.AutomountServiceAccountToken, ExtraAction: nil}, + {Existing: &existing.Spec.Template.Spec.ServiceAccountName, Desired: &desired.Spec.Template.Spec.ServiceAccountName, ExtraAction: nil}, + {Existing: &existing.Spec.Replicas, Desired: &desired.Spec.Replicas, ExtraAction: nil}, + {Existing: &existing.Labels, Desired: &desired.Labels, ExtraAction: nil}, + {Existing: &existing.Annotations, Desired: &desired.Annotations, ExtraAction: nil}, + } + + argocdcommon.UpdateIfChanged(fieldsToCompare, &changed) + + if !reflect.DeepEqual(desired.Spec.Template.Spec.Containers[1:], existing.Spec.Template.Spec.Containers[1:]) { + existing.Spec.Template.Spec.Containers = append(existing.Spec.Template.Spec.Containers[0:1], + desired.Spec.Template.Spec.Containers[1:]...) + changed = true + } + + if !changed { + return nil + } + + if err = workloads.UpdateDeployment(existing, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileDeployment: failed to update deployment %s", existing.Name) + } + + rsr.Logger.Info("deployment updated", "name", existing.Name, "namespace", existing.Namespace) + return nil +} + +func (rsr *RepoServerReconciler) getDeploymentRequest() workloads.DeploymentRequest { + req := workloads.DeploymentRequest{ + ObjectMeta: argoutil.GetObjMeta(resourceName, rsr.Instance.Namespace, rsr.Instance.Name, rsr.Instance.Namespace, component), + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.AppK8sKeyName: resourceName, + }, + }, + Replicas: rsr.getReplicas(), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + common.AppK8sKeyName: resourceName, + }, + }, + Spec: corev1.PodSpec{ + Volumes: rsr.getPodVolumes(), + InitContainers: rsr.getRepoSeverInitContainers(), + Containers: rsr.getContainers(), + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: util.BoolPtr(true), + }, + AutomountServiceAccountToken: &rsr.Instance.Spec.Repo.MountSAToken, + NodeSelector: common.DefaultNodeSelector(), + ServiceAccountName: resourceName, + }, + }, + }, + Instance: rsr.Instance, + Mutations: []mutation.MutateFunc{mutation.ApplyReconcilerMutation}, + Client: rsr.Client, + } + + if rsr.Instance.Spec.Repo.ServiceAccount != "" { + req.Spec.Template.Spec.ServiceAccountName = rsr.Instance.Spec.Repo.ServiceAccount + } + + if rsr.Instance.Spec.NodePlacement != nil { + req.Spec.Template.Spec.NodeSelector = util.MergeMaps(req.Spec.Template.Spec.NodeSelector, rsr.Instance.Spec.NodePlacement.NodeSelector) + req.Spec.Template.Spec.Tolerations = rsr.Instance.Spec.NodePlacement.Tolerations + } + + return req +} + +func (rsr *RepoServerReconciler) getRepoSeverInitContainers() []corev1.Container { + initContainers := []corev1.Container{{ + Name: "copyutil", + Image: rsr.getContainerImage(), + Command: argocdcommon.GetArgoCmpServerInitCommand(), + ImagePullPolicy: corev1.PullAlways, + Resources: rsr.getResources(), + Env: util.ProxyEnvVars(), + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: util.BoolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + common.CapabilityDropAll, + }, + }, + RunAsNonRoot: util.BoolPtr(true), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "var-files", + MountPath: "var/run/argocd", + }, + }, + }} + if (rsr.Instance.Spec.Repo.InitContainers != nil) && len(rsr.Instance.Spec.Repo.InitContainers) > 0 { + initContainers = append(initContainers, rsr.Instance.Spec.Repo.InitContainers...) + } + return initContainers +} + +func (rsr *RepoServerReconciler) getContainers() []corev1.Container { + // Global proxy env vars go first + repoServerEnv := rsr.Instance.Spec.Repo.Env + // Environment specified in the CR take precedence over everything else + repoServerEnv = util.EnvMerge(repoServerEnv, util.ProxyEnvVars(), false) + + if rsr.Instance.Spec.Repo.ExecTimeout != nil { + repoServerEnv = util.EnvMerge(repoServerEnv, []corev1.EnvVar{{Name: common.ArgoCDExecTimeoutEnvVar, Value: fmt.Sprintf("%ds", *rsr.Instance.Spec.Repo.ExecTimeout)}}, true) + } + + volumeMounts := []corev1.VolumeMount{ + { + Name: common.SSHKnownHosts, + MountPath: common.VolumeMountPathSSH, + }, + { + Name: common.TLSCerts, + MountPath: common.VolumeMountPathTLS, + }, + { + Name: common.GPGKeys, + MountPath: common.VolumeMountPathGPG, + }, + { + Name: common.GPGKeyRing, + MountPath: common.VolumeMountPathGPGKeyring, + }, + { + Name: common.VolumeTmp, + MountPath: common.VolumeMountPathTmp, + }, + { + Name: common.ArgoCDRepoServerTLSSecretName, + MountPath: common.VolumeMountPathRepoServerTLS, + }, + { + Name: common.ArgoCDRedisServerTLSSecretName, + MountPath: redisTLSPath, + }, + { + Name: "plugins", + MountPath: cmpServerPluginsPath, + }, + } + + containers := []corev1.Container{{ + Command: rsr.getArgs(), + Image: argocdcommon.GetArgoContainerImage(rsr.Instance), + ImagePullPolicy: corev1.PullAlways, + VolumeMounts: volumeMounts, + Name: common.ArgoCDRepoServerName, + Env: repoServerEnv, + Resources: rsr.getResources(), + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(common.DefaultRepoServerPort), + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + Ports: []corev1.ContainerPort{ + { + ContainerPort: common.DefaultRepoServerPort, + Name: "server", + }, { + ContainerPort: common.ArgoCDDefaultRepoMetricsPort, + Name: common.ArgoCDMetrics, + }, + }, + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: util.BoolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + common.CapabilityDropAll, + }, + }, + RunAsNonRoot: util.BoolPtr(true), + }, + }} + + if rsr.Instance.Spec.Repo.VolumeMounts != nil { + containers[0].VolumeMounts = append(volumeMounts, rsr.Instance.Spec.Repo.VolumeMounts...) + } + + if rsr.Instance.Spec.Repo.SidecarContainers != nil { + containers = append(containers, rsr.Instance.Spec.Repo.SidecarContainers...) + } + + return containers +} + +func (rsr *RepoServerReconciler) getPodVolumes() []corev1.Volume { + volumes := []corev1.Volume{ + { + Name: common.SSHKnownHosts, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDKnownHostsConfigMapName, + }, + }, + }, + }, + { + Name: common.TLSCerts, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDTLSCertsConfigMapName, + }, + }, + }, + }, + { + Name: common.GPGKeys, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDGPGKeysConfigMapName, + }, + }, + }, + }, + { + Name: common.GPGKeyRing, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: common.VolumeTmp, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: common.ArgoCDRepoServerTLS, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: common.ArgoCDRepoServerTLSSecretName, + Optional: util.BoolPtr(true), + }, + }, + }, + { + Name: common.ArgoCDRedisServerTLSSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: common.ArgoCDRedisServerTLSSecretName, + Optional: util.BoolPtr(true), + }, + }, + }, + { + Name: "var-files", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "plugins", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + if rsr.Instance.Spec.Repo.Volumes != nil && len(rsr.Instance.Spec.Repo.Volumes) > 0 { + volumes = append(volumes, rsr.Instance.Spec.Repo.Volumes...) + } + return volumes +} + +func (rsr *RepoServerReconciler) deleteDeployment(name, namespace string) error { + if err := workloads.DeleteDeployment(name, namespace, rsr.Client); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return errors.Wrapf(err, "deleteDeployment: failed to delete deployment %s in namespace %s", name, namespace) + } + rsr.Logger.Info("deployment deleted", "name", name, "namespace", namespace) + return nil +} + +// TriggerDeploymentRollout starts repo-server deployment rollout by updating the given key +func (rsr *RepoServerReconciler) TriggerDeploymentRollout(name, namespace, key string) error { + return argocdcommon.TriggerDeploymentRollout(name, namespace, key, rsr.Client) +} diff --git a/controllers/argocd/reposerver/deployment_test.go b/controllers/argocd/reposerver/deployment_test.go new file mode 100644 index 000000000..fda0f471e --- /dev/null +++ b/controllers/argocd/reposerver/deployment_test.go @@ -0,0 +1,409 @@ +package reposerver + +import ( + "testing" + + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + "github.com/argoproj-labs/argocd-operator/tests/mock" + "github.com/argoproj-labs/argocd-operator/tests/test" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func TestReconcileDeployment_create(t *testing.T) { + mockRedisName := "test-argocd-redis" + + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedError bool + expectedDeployment *appsv1.Deployment + }{ + { + name: "Deployment does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedError: false, + expectedDeployment: getDesiredDeployment(), + }, + { + name: "Deployment exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestDeployment(getDesiredDeployment(), + func(d *appsv1.Deployment) { + d.Name = "test-argocd-repo-server" + }, + ), + ), + expectedError: false, + expectedDeployment: getDesiredDeployment(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + mockRedis := mock.NewRedis(mockRedisName, test.TestNamespace, tt.reconciler.Client) + mockRedis.SetServerAddress("http://mock-server-address") + mockRedis.SetUseTLS(true) + tt.reconciler.Redis = mockRedis + + err := tt.reconciler.reconcileDeployment() + assert.NoError(t, err) + + _, err = workloads.GetDeployment("test-argocd-repo-server", test.TestNamespace, tt.reconciler.Client) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + }) + } +} + +func TestReconcileDeployment_update(t *testing.T) { + mockRedisName := "test-argocd-redis" + + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedError bool + expectedDeployment *appsv1.Deployment + }{ + { + name: "Deployment drift", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestDeployment(getDesiredDeployment(), + func(d *appsv1.Deployment) { + d.Name = "test-argocd-repo-server" + // Modify some fields to simulate drift + d.Spec.Template.Spec.Containers[0].Image = "random-image" + }, + ), + ), + expectedError: false, + expectedDeployment: getDesiredDeployment(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + mockRedis := mock.NewRedis(mockRedisName, test.TestNamespace, tt.reconciler.Client) + mockRedis.SetServerAddress("http://mock-server-address") + mockRedis.SetUseTLS(true) + tt.reconciler.Redis = mockRedis + + err := tt.reconciler.reconcileDeployment() + assert.NoError(t, err) + + existing, err := workloads.GetDeployment("test-argocd-repo-server", test.TestNamespace, tt.reconciler.Client) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + if tt.expectedDeployment != nil { + match := true + + ftc := []argocdcommon.FieldToCompare{ + {Existing: &existing.Spec.Template.Spec.NodeSelector, Desired: &tt.expectedDeployment.Spec.Template.Spec.NodeSelector}, + {Existing: &existing.Spec.Template.Spec.Volumes, Desired: &tt.expectedDeployment.Spec.Template.Spec.Volumes}, + {Existing: &existing.Spec.Template.Spec.Containers[0].Image, Desired: &tt.expectedDeployment.Spec.Template.Spec.Containers[0].Image}, + {Existing: &existing.Spec.Template.Spec.Containers[0].Command, Desired: &tt.expectedDeployment.Spec.Template.Spec.Containers[0].Command}, + {Existing: &existing.Spec.Template.Spec.Containers[0].Ports, Desired: &tt.expectedDeployment.Spec.Template.Spec.Containers[0].Ports}, + {Existing: &existing.Spec.Template.Spec.Containers[0].SecurityContext, Desired: &tt.expectedDeployment.Spec.Template.Spec.Containers[0].SecurityContext}, + {Existing: &existing.Spec.Template.Spec.Containers[0].VolumeMounts, Desired: &tt.expectedDeployment.Spec.Template.Spec.Containers[0].VolumeMounts}, + {Existing: &existing.Spec.Template.Spec.InitContainers, Desired: &tt.expectedDeployment.Spec.Template.Spec.InitContainers}, + {Existing: &existing.Spec.Template.Spec.ServiceAccountName, Desired: &tt.expectedDeployment.Spec.Template.Spec.ServiceAccountName}, + {Existing: &existing.Spec.Template.Spec.SecurityContext, Desired: &tt.expectedDeployment.Spec.Template.Spec.SecurityContext}, + {Existing: &existing.Spec.Replicas, Desired: &tt.expectedDeployment.Spec.Replicas}, + {Existing: &existing.Spec.Selector, Desired: &tt.expectedDeployment.Spec.Selector}, + {Existing: &existing.Labels, Desired: &tt.expectedDeployment.Labels}, + {Existing: &existing.Annotations, Desired: &tt.expectedDeployment.Annotations}, + } + argocdcommon.PartialMatch(ftc, &match) + assert.True(t, match) + } + + }) + } +} + +func TestDeleteDeployment(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + deploymentExist bool + expectedError bool + }{ + { + name: "Deployment exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestDeployment(nil), + ), + deploymentExist: true, + expectedError: false, + }, + { + name: "Deployment does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + deploymentExist: false, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + err := tt.reconciler.deleteDeployment(test.TestName, test.TestNamespace) + + if tt.deploymentExist { + _, err := workloads.GetDeployment(test.TestName, test.TestNamespace, tt.reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + } + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + }) + } +} + +func getDesiredDeployment() *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-argocd-repo-server", + Namespace: "test-ns", + Labels: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server", + "app.kubernetes.io/part-of": "argocd", + "app.kubernetes.io/instance": "test-argocd", + "app.kubernetes.io/managed-by": "argocd-operator", + "app.kubernetes.io/component": "repo-server", + }, + Annotations: map[string]string{ + "argocds.argoproj.io/name": "test-argocd", + "argocds.argoproj.io/namespace": "test-ns", + }, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "ssh-known-hosts", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "argocd-ssh-known-hosts-cm", + }, + }, + }, + }, + { + Name: "tls-certs", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "argocd-tls-certs-cm", + }, + }, + }, + }, + { + Name: "gpg-keys", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "argocd-gpg-keys-cm", + }, + }, + }, + }, + { + Name: "gpg-keyring", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "tmp", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "argocd-repo-server-tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "argocd-repo-server-tls", + Optional: util.BoolPtr(true), + }, + }, + }, + { + Name: "argocd-operator-redis-tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "argocd-operator-redis-tls", + Optional: util.BoolPtr(true), + }, + }, + }, + { + Name: "var-files", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "plugins", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "copyutil", + Image: "quay.io/argoproj/argocd@sha256:8576d347f30fa4c56a0129d1c0a0f5ed1e75662f0499f1ed7e917c405fd909dc", + Command: []string{"cp", "-n", "/usr/local/bin/argocd", "/var/run/argocd/argocd-cmp-server"}, + ImagePullPolicy: "Always", + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: util.BoolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: util.BoolPtr(true), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "var-files", + MountPath: "var/run/argocd", + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "argocd-repo-server", + Image: "quay.io/argoproj/argocd@sha256:8576d347f30fa4c56a0129d1c0a0f5ed1e75662f0499f1ed7e917c405fd909dc", + Command: []string{"uid_entrypoint.sh", "argocd-repo-server", "--redis", "http://mock-server-address", "--redis-use-tls", "--redis-ca-certificate", "/app/config/reposerver/tls/redis/tls.crt", "--loglevel", "info", "--logformat", "text"}, + ImagePullPolicy: "Always", + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(8081), + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(8081), + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + Ports: []corev1.ContainerPort{ + { + ContainerPort: 8081, + Name: "server", + }, { + ContainerPort: 8084, + Name: "metrics", + }, + }, + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: util.BoolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: util.BoolPtr(true), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "ssh-known-hosts", + MountPath: "/app/config/ssh", + }, + { + Name: "tls-certs", + MountPath: "/app/config/tls", + }, + { + Name: "gpg-keys", + MountPath: "/app/config/gpg/source", + }, + { + Name: "gpg-keyring", + MountPath: "/app/config/gpg/keys", + }, + { + Name: "tmp", + MountPath: "/tmp", + }, + { + Name: "argocd-repo-server-tls", + MountPath: "/app/config/reposerver/tls", + }, + { + Name: "argocd-operator-redis-tls", + MountPath: "/app/config/reposerver/tls/redis", + }, + { + Name: "plugins", + MountPath: "/home/argocd/cmp-server/plugins", + }, + }, + }, + }, + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: util.BoolPtr(true), + }, + NodeSelector: map[string]string{ + "kubernetes.io/os": "linux", + }, + ServiceAccountName: "test-argocd-repo-server", + }, + }, + }, + } +} diff --git a/controllers/argocd/reposerver/helper.go b/controllers/argocd/reposerver/helper.go new file mode 100644 index 000000000..581f34319 --- /dev/null +++ b/controllers/argocd/reposerver/helper.go @@ -0,0 +1,98 @@ +package reposerver + +import ( + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + corev1 "k8s.io/api/core/v1" +) + +const ( + redisTLSCertPath = "/app/config/reposerver/tls/redis/tls.crt" +) + +func (rsr *RepoServerReconciler) TLSVerificationRequested() bool { + return rsr.Instance.Spec.Repo.VerifyTLS +} + +// getResources will return the ResourceRequirements for the Repo server container. +func (rsr *RepoServerReconciler) getResources() corev1.ResourceRequirements { + resources := corev1.ResourceRequirements{} + + // Allow override of resource requirements from CR + if rsr.Instance.Spec.Repo.Resources != nil { + resources = *rsr.Instance.Spec.Repo.Resources + } + + return resources +} + +// getContainerImage will return the container image for the repo-server. +func (rsr *RepoServerReconciler) getContainerImage() string { + fn := func(cr *argoproj.ArgoCD) (string, string) { + return cr.Spec.Repo.Image, cr.Spec.Repo.Version + } + return argocdcommon.GetContainerImage(fn, rsr.Instance, common.ArgoCDImageEnvVar, common.ArgoCDDefaultArgoImage, common.ArgoCDDefaultArgoVersion) +} + +// GetServerAddress will return the repo-server service address for the given ArgoCD instance +func (rsr *RepoServerReconciler) GetServerAddress() string { + if rsr.Instance.Spec.Repo.Remote != nil && *rsr.Instance.Spec.Repo.Remote != "" { + return *rsr.Instance.Spec.Repo.Remote + } + + return argoutil.FqdnServiceRef(resourceName, rsr.Instance.Namespace, common.DefaultRepoServerPort) +} + +// getReplicas will return the size value for the argocd-repo-server replica count if it +// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or +// replicas value is < 0. +func (rsr *RepoServerReconciler) getReplicas() *int32 { + if rsr.Instance.Spec.Repo.Replicas != nil && *rsr.Instance.Spec.Repo.Replicas >= 0 { + return rsr.Instance.Spec.Repo.Replicas + } + + return nil +} + +// getArgs will return the args for the repo server container +func (rsr *RepoServerReconciler) getArgs() []string { + cmd := make([]string, 0) + + cmd = append(cmd, common.UidEntryPointSh) + cmd = append(cmd, common.RepoServerCmd) + + if rsr.Instance.Spec.Redis.IsEnabled() { + cmd = append(cmd, common.RedisCmd, rsr.Redis.GetServerAddress()) + + if rsr.Redis.UseTLS() { + cmd = append(cmd, common.RedisUseTLSCmd) + if rsr.Instance.Spec.Redis.DisableTLSVerification { + cmd = append(cmd, common.RedisInsecureSkipTLSVerifyCmd) + } else { + cmd = append(cmd, common.RedisCACertificate, redisTLSCertPath) + } + } + } else { + rsr.Logger.Debug("redis is disabled; skipping redis configuration") + } + + cmd = append(cmd, common.LogLevelCmd) + cmd = append(cmd, argoutil.GetLogLevel(rsr.Instance.Spec.Repo.LogLevel)) + + cmd = append(cmd, common.LogFormatCmd) + cmd = append(cmd, argoutil.GetLogFormat(rsr.Instance.Spec.Repo.LogFormat)) + + // *** NOTE *** + // Do Not add any new default command line arguments below this. + extraArgs := rsr.Instance.Spec.Repo.ExtraRepoCommandArgs + err := argocdcommon.IsMergable(extraArgs, cmd) + if err != nil { + return cmd + } + + cmd = append(cmd, extraArgs...) + return cmd +} diff --git a/controllers/argocd/reposerver/helper_test.go b/controllers/argocd/reposerver/helper_test.go new file mode 100644 index 000000000..0adf974da --- /dev/null +++ b/controllers/argocd/reposerver/helper_test.go @@ -0,0 +1,319 @@ +package reposerver + +import ( + "os" + "testing" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/mock" + "github.com/argoproj-labs/argocd-operator/tests/test" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +func TestTLSVerificationRequested(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedResult bool + }{ + { + name: "TLS Verification Requested", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Repo.VerifyTLS = true + }, + ), + ), + expectedResult: true, + }, + { + name: "TLS Verification Not Requested", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Repo.VerifyTLS = false + }, + ), + ), + expectedResult: false, + }, + { + name: "Default (TLS Verification Not Specified)", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.reconciler.TLSVerificationRequested() + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestGetResources(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedResult corev1.ResourceRequirements + }{ + { + name: "Resource Requirements Specified", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Repo.Resources = &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + } + }, + ), + ), + expectedResult: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + }, + }, + { + name: "Default (Resource Requirements Not Specified)", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedResult: corev1.ResourceRequirements{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.reconciler.getResources() + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestGetContainerImage(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + setEnvVarFunc func() + UnsetEnvVarFunc func() + expectedResult string + }{ + { + name: "CR Spec Specifies Image", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Repo.Image = "custom-image" + cr.Spec.Repo.Version = "custom-version" + }, + ), + ), + setEnvVarFunc: func() { + os.Setenv("ARGOCD_IMAGE", "default-argocd-img:v1.0") + }, + UnsetEnvVarFunc: func() { + os.Unsetenv("ARGOCD_IMAGE") + }, + expectedResult: "custom-image:custom-version", + }, + { + name: "Env Var Specifies Image", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + setEnvVarFunc: func() { + os.Setenv("ARGOCD_IMAGE", "default-argocd-img:v1.0") + }, + UnsetEnvVarFunc: func() { + os.Unsetenv("ARGOCD_IMAGE") + }, + expectedResult: "default-argocd-img:v1.0", + }, + { + name: "Default Image and Tag Used", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + setEnvVarFunc: nil, + expectedResult: "quay.io/argoproj/argocd@sha256:8576d347f30fa4c56a0129d1c0a0f5ed1e75662f0499f1ed7e917c405fd909dc", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set environment variable if specified + if tt.setEnvVarFunc != nil { + tt.setEnvVarFunc() + defer tt.UnsetEnvVarFunc() + } + + result := tt.reconciler.getContainerImage() + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestGetServerAddress(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedResult string + }{ + { + name: "Custom Remote Specified", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Repo.Remote = util.StringPtr("https://custom.repo.server") + }, + ), + ), + expectedResult: "https://custom.repo.server", + }, + { + name: "Default (Remote Not Specified)", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedResult: "test-argocd-repo-server.test-ns.svc.cluster.local:8081", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + result := tt.reconciler.GetServerAddress() + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestGetReplicas(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedResult *int32 + }{ + { + name: "Replicas Specified", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + replicas := int32(3) + cr.Spec.Repo.Replicas = &replicas + }, + ), + ), + expectedResult: util.Int32Ptr(3), + }, + { + name: "Negative Replicas Specified", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + replicas := int32(-1) + cr.Spec.Repo.Replicas = &replicas + }, + ), + ), + expectedResult: nil, + }, + { + name: "Default (Replicas Not Specified)", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedResult: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.reconciler.getReplicas() + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestGetArgs(t *testing.T) { + + tests := []struct { + name string + reconciler *RepoServerReconciler + useTLS bool + expectedCmd []string + }{ + { + name: "redis disabled", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Redis.Enabled = util.BoolPtr(false) + }, + ), + ), + useTLS: false, + expectedCmd: []string{"uid_entrypoint.sh", "argocd-repo-server", "--loglevel", "info", "--logformat", "text"}, + }, + { + name: "redis enabled, UseTLS true, disable TLS verification true", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Redis.Enabled = util.BoolPtr(true) + cr.Spec.Redis.DisableTLSVerification = true + }, + ), + ), + useTLS: true, + expectedCmd: []string{"uid_entrypoint.sh", "argocd-repo-server", "--redis", "http://mock-redis-server", "--redis-use-tls", "--redis-insecure-skip-tls-verify", "--loglevel", "info", "--logformat", "text"}, + }, + { + name: "redis enabled, UseTLS true, disable TLS verification false", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil, + func(cr *argoproj.ArgoCD) { + cr.Spec.Redis.Enabled = util.BoolPtr(true) + cr.Spec.Redis.DisableTLSVerification = false + }, + ), + ), + useTLS: true, + expectedCmd: []string{"uid_entrypoint.sh", "argocd-repo-server", "--redis", "http://mock-redis-server", "--redis-use-tls", "--redis-ca-certificate", "/app/config/reposerver/tls/redis/tls.crt", "--loglevel", "info", "--logformat", "text"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRedisName := "test-argocd-redis" + mockRedis := mock.NewRedis(mockRedisName, test.TestNamespace, tt.reconciler.Client) + mockRedis.SetUseTLS(tt.useTLS) + mockRedis.SetServerAddress("http://mock-redis-server") + tt.reconciler.Redis = mockRedis + gotArgs := tt.reconciler.getArgs() + + assert.Equal(t, tt.expectedCmd, gotArgs) + + }) + } +} diff --git a/controllers/argocd/reposerver/interfaces.go b/controllers/argocd/reposerver/interfaces.go new file mode 100644 index 000000000..cb249756e --- /dev/null +++ b/controllers/argocd/reposerver/interfaces.go @@ -0,0 +1,14 @@ +package reposerver + +type AppController interface { + TriggerRollout(string) error +} + +type ServerController interface { + TriggerRollout(string) error +} + +type RedisController interface { + UseTLS() bool + GetServerAddress() string +} diff --git a/controllers/argocd/reposerver/reposerver.go b/controllers/argocd/reposerver/reposerver.go index 27dca0803..81352b982 100644 --- a/controllers/argocd/reposerver/reposerver.go +++ b/controllers/argocd/reposerver/reposerver.go @@ -1,22 +1,122 @@ package reposerver import ( - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/util" ) type RepoServerReconciler struct { Client client.Client Scheme *runtime.Scheme Instance *argoproj.ArgoCD - Logger logr.Logger + Logger *util.Logger + + Appcontroller AppController + Server ServerController + Redis RedisController } +var ( + resourceName string + resourceMetricsName string + component string +) + func (rsr *RepoServerReconciler) Reconcile() error { + rsr.varSetter() + + if err := rsr.reconcileServiceAccount(); err != nil { + rsr.Logger.Error(err, "failed to reconcile serviceaccount") + return err + } + + if err := rsr.reconcileService(); err != nil { + rsr.Logger.Error(err, "failed to reconcile service") + return err + } + + if rsr.Instance.Spec.Prometheus.Enabled { + if err := rsr.reconcileServiceMonitor(); err != nil { + rsr.Logger.Error(err, "failed to reconcile service monitor") + return err + } + } else { + if err := rsr.deleteServiceMonitor(resourceMetricsName, rsr.Instance.Namespace); err != nil { + rsr.Logger.Error(err, "DeleteResources: failed to delete serviceMonitor") + return err + } + } + + if err := rsr.reconcileTLSSecret(); err != nil { + rsr.Logger.Error(err, "failed to reconcile TLS secret") + return err + } + + if err := rsr.reconcileDeployment(); err != nil { + rsr.Logger.Error(err, "failed to reconcile deployment") + return err + } - // controller logic goes here return nil } + +// DeleteResources triggers deletion of all repo-server resources +func (rsr *RepoServerReconciler) DeleteResources() error { + var deletionErr util.MultiError + + // delete deployment + err := rsr.deleteDeployment(resourceName, rsr.Instance.Namespace) + if err != nil { + rsr.Logger.Error(err, "DeleteResources") + deletionErr.Append(err) + } + + // delete service monitor + err = rsr.deleteServiceMonitor(resourceName, rsr.Instance.Namespace) + if err != nil { + rsr.Logger.Error(err, "DeleteResources") + deletionErr.Append(err) + } + + // delete service + err = rsr.deleteService(resourceName, rsr.Instance.Namespace) + if err != nil { + rsr.Logger.Error(err, "DeleteResources") + deletionErr.Append(err) + } + + // delete serviceaccount + err = rsr.deleteServiceAccount(resourceName, rsr.Instance.Namespace) + if err != nil { + rsr.Logger.Error(err, "DeleteResources") + deletionErr.Append(err) + } + + // delete TLS secret + err = rsr.deleteSecret(common.ArgoCDRepoServerTLSSecretName, rsr.Instance.Namespace) + if err != nil { + rsr.Logger.Error(err, "DeleteResources") + deletionErr.Append(err) + } + + return deletionErr.ErrOrNil() +} + +func (rsr *RepoServerReconciler) TriggerRollout(key string) error { + if err := rsr.TriggerDeploymentRollout(resourceName, rsr.Instance.Namespace, key); err != nil { + rsr.Logger.Error(err, "TriggerRollout: failed to rollout repo-server deployment") + return err + } + return nil +} + +func (rsr *RepoServerReconciler) varSetter() { + component = common.RepoServerComponent + resourceName = argoutil.GenerateResourceName(rsr.Instance.Name, common.RepoServerSuffix) + resourceMetricsName = argoutil.GenerateResourceName(rsr.Instance.Name, common.RepoServerSuffix, common.MetricsSuffix) +} diff --git a/controllers/argocd/reposerver/reposerver_test.go b/controllers/argocd/reposerver/reposerver_test.go new file mode 100644 index 000000000..040d817c5 --- /dev/null +++ b/controllers/argocd/reposerver/reposerver_test.go @@ -0,0 +1,260 @@ +package reposerver + +import ( + "testing" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/pkg/monitoring" + "github.com/argoproj-labs/argocd-operator/pkg/resource" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + "github.com/argoproj-labs/argocd-operator/tests/mock" + "github.com/argoproj-labs/argocd-operator/tests/test" + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func makeTestReposerverReconciler(cr *argoproj.ArgoCD, objs ...client.Object) *RepoServerReconciler { + schemeOpt := func(s *runtime.Scheme) { + monitoringv1.AddToScheme(s) + argoproj.AddToScheme(s) + } + sch := test.MakeTestReconcilerScheme(schemeOpt) + + client := test.MakeTestReconcilerClient(sch, objs, []client.Object{cr}, []runtime.Object{cr}) + + return &RepoServerReconciler{ + Client: client, + Scheme: sch, + Instance: cr, + Logger: util.NewLogger(common.RepoServerController), + } +} + +func TestReconcile(t *testing.T) { + mockServerName := "test-argocd-server" + mockAppControllerName := "test-argocd-app-controller" + mockRedisName := "test-argocd-redis" + + testArgoCD := test.MakeTestArgoCD(nil) + reconciler := makeTestReposerverReconciler( + testArgoCD, + ) + + mockRedis := mock.NewRedis(mockRedisName, test.TestNamespace, reconciler.Client) + mockRedis.SetUseTLS(true) + mockRedis.SetServerAddress("http://mock-redis-server") + + reconciler.varSetter() + reconciler.Server = mock.NewServer(mockServerName, test.TestNamespace, reconciler.Client) + reconciler.Appcontroller = mock.NewAppController(mockAppControllerName, test.TestNamespace, reconciler.Client) + reconciler.Redis = mockRedis + + expectedResources := []client.Object{ + test.MakeTestServiceAccount( + func(sa *corev1.ServiceAccount) { + sa.Name = resourceName + }, + ), + test.MakeTestService(nil, + func(s *corev1.Service) { + s.Name = resourceName + }, + ), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = resourceName + }, + ), + } + + err := reconciler.Reconcile() + assert.NoError(t, err) + + for _, obj := range expectedResources { + _, err := resource.GetObject(resourceName, test.TestNamespace, obj, reconciler.Client) + assert.NoError(t, err) + } + + monitoring.SetPrometheusAPIFound(true) + defer monitoring.SetPrometheusAPIFound(false) + + testArgoCD.Spec.Prometheus.Enabled = true + reconciler.Instance = testArgoCD + + err = reconciler.Reconcile() + assert.NoError(t, err) + + sm, err := resource.GetObject(resourceMetricsName, test.TestNamespace, test.MakeTestServiceMonitor(nil), reconciler.Client) + assert.NoError(t, err) + assert.NotNil(t, sm) + + testArgoCD.Spec.Prometheus.Enabled = false + reconciler.Instance = testArgoCD + + err = reconciler.Reconcile() + assert.NoError(t, err) + + _, err = resource.GetObject(resourceMetricsName, test.TestNamespace, test.MakeTestServiceMonitor(nil), reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + +} + +func TestDeleteResources(t *testing.T) { + tests := []struct { + name string + resources []client.Object + prometheusAPIAvailable bool + expectedErrors []string + }{ + { + name: "Prometheus API available", + resources: []client.Object{ + test.MakeTestServiceAccount( + func(sa *corev1.ServiceAccount) { + sa.Name = resourceName + }, + ), + test.MakeTestService(nil, + func(s *corev1.Service) { + s.Name = resourceName + }, + ), + test.MakeTestServiceMonitor(nil, + func(sm *monitoringv1.ServiceMonitor) { + sm.Name = resourceMetricsName + }, + ), + test.MakeTestSecret(nil, + func(sec *corev1.Secret) { + sec.Name = common.ArgoCDRepoServerTLSSecretName + }, + ), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = resourceName + }, + ), + }, + prometheusAPIAvailable: true, + expectedErrors: nil, + }, + { + name: "Prometheus API not available", + resources: []client.Object{ + test.MakeTestServiceAccount( + func(sa *corev1.ServiceAccount) { + sa.Name = resourceName + }, + ), + test.MakeTestService(nil, + func(s *corev1.Service) { + s.Name = resourceName + }, + ), + test.MakeTestSecret(nil, + func(sec *corev1.Secret) { + sec.Name = common.ArgoCDRepoServerTLSSecretName + }, + ), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = resourceName + }, + ), + }, + prometheusAPIAvailable: false, + expectedErrors: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reconciler := makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + tt.resources..., + ) + + reconciler.varSetter() + + monitoring.SetPrometheusAPIFound(tt.prometheusAPIAvailable) + defer monitoring.SetPrometheusAPIFound(false) + + err := reconciler.DeleteResources() + + if len(tt.expectedErrors) > 0 { + assert.Error(t, err, "Expected an error but got none.") + for _, expectedError := range tt.expectedErrors { + assert.Contains(t, err.Error(), expectedError, "Error message did not contain the expected substring.") + } + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + for _, obj := range tt.resources { + _, err := resource.GetObject(resourceName, test.TestNamespace, obj, reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + } + }) + } +} + +func TestTriggerRollout(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + deploymentexists bool + expectedError bool + }{ + { + name: "Deployment exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = "test-argocd-repo-server" + }, + ), + ), + deploymentexists: true, + expectedError: false, + }, + { + name: "Deployment does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + deploymentexists: false, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + err := tt.reconciler.TriggerRollout(test.TestKey) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + if !tt.expectedError { + dep, err := workloads.GetDeployment(resourceName, test.TestNamespace, tt.reconciler.Client) + assert.NoError(t, err) + + _, ok := dep.Spec.Template.ObjectMeta.Labels[test.TestKey] + assert.True(t, ok) + } + + }) + } +} diff --git a/controllers/argocd/reposerver/secret.go b/controllers/argocd/reposerver/secret.go new file mode 100644 index 000000000..ee7ac01f6 --- /dev/null +++ b/controllers/argocd/reposerver/secret.go @@ -0,0 +1,73 @@ +package reposerver + +import ( + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + "github.com/pkg/errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" +) + +// reconcileRepoServerTLSSecret checks whether the argocd-repo-server-tls secret +// has changed since our last reconciliation loop. It does so by comparing the +// checksum of tls.crt and tls.key in the status of the ArgoCD CR against the +// values calculated from the live state in the cluster. +func (rsr *RepoServerReconciler) reconcileTLSSecret() error { + var reconErrs util.MultiError + var sha256sum string + + sha256sum, err := argocdcommon.TLSSecretChecksum(types.NamespacedName{Name: common.ArgoCDRepoServerTLSSecretName, Namespace: rsr.Instance.Namespace}, rsr.Client) + if err != nil { + reconErrs.Append(errors.Wrapf(err, "reconcileTLSSecret: failed to calculate checksum for %s in namespace %s", common.ArgoCDRepoServerTLSSecretName, rsr.Instance.Namespace)) + return reconErrs + } + + if sha256sum == "" { + rsr.Logger.Debug("reconcileTLSSecret: received empty checksum; secret either not found, or is of type other than kubernetes.io/tls", "name", common.ArgoCDRepoServerTLSSecretName) + return nil + } + + // The content of the TLS secret has changed since we last looked if the + // calculated checksum doesn't match the one stored in the status. + if rsr.Instance.Status.RepoTLSChecksum != sha256sum { + // We store the value early to prevent a possible restart loop, for the + // cost of a possibly missed restart when we cannot update the status + // field of the resource. + rsr.Instance.Status.RepoTLSChecksum = sha256sum + err = rsr.updateInstanceStatus() + if err != nil { + reconErrs.Append(errors.Wrapf(err, "reconcileTLSSecret")) + } + + // trigger server rollout + if err := rsr.Server.TriggerRollout(common.RepoTLSCertChangedKey); err != nil { + reconErrs.Append(errors.Wrapf(err, "reconcileTLSSecret")) + } + + // trigger repo-server rollout + if err := rsr.TriggerRollout(common.RepoTLSCertChangedKey); err != nil { + reconErrs.Append(errors.Wrapf(err, "reconcileTLSSecret")) + } + + // trigger app-controller rollout + if err := rsr.Appcontroller.TriggerRollout(common.RepoTLSCertChangedKey); err != nil { + reconErrs.Append(errors.Wrapf(err, "reconcileTLSSecret")) + } + } + return reconErrs.ErrOrNil() + +} + +func (rsr *RepoServerReconciler) deleteSecret(name, namespace string) error { + if err := workloads.DeleteSecret(name, namespace, rsr.Client); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return errors.Wrapf(err, "deleteSecret: failed to delete secret %s in namespace %s", name, namespace) + } + rsr.Logger.Info("secret deleted", "name", name, "namespace", namespace) + return nil +} diff --git a/controllers/argocd/reposerver/secret_test.go b/controllers/argocd/reposerver/secret_test.go new file mode 100644 index 000000000..eb1db04fd --- /dev/null +++ b/controllers/argocd/reposerver/secret_test.go @@ -0,0 +1,193 @@ +package reposerver + +import ( + "testing" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/pkg/resource" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + "github.com/argoproj-labs/argocd-operator/tests/mock" + "github.com/argoproj-labs/argocd-operator/tests/test" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestReconcileTLSSecret(t *testing.T) { + mockServerName := "test-argocd-server" + mockAppControllerName := "test-argocd-app-controller" + + tests := []struct { + name string + resources []client.Object + secretExist bool + expectedError bool + }{ + { + name: "no TLS secret", + resources: []client.Object{ + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = mockServerName + }, + ), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = "test-argocd-repo-server" + }, + ), + test.MakeTestStatefulSet(nil, + func(d *appsv1.StatefulSet) { + d.Name = mockAppControllerName + }, + ), + }, + secretExist: false, + expectedError: false, + }, + { + name: "missing target deployments and statefulset", + resources: []client.Object{ + test.MakeTestSecret(nil, + func(s *corev1.Secret) { + s.Name = "argocd-repo-server-tls" + s.Type = corev1.SecretTypeTLS + s.Data = map[string][]byte{ + "tls.crt": []byte(test.TestCert), + "tls.key": []byte(test.TestKey), + } + }, + ), + }, + secretExist: true, + expectedError: true, + }, + { + name: "reconcile TLS secret", + resources: []client.Object{ + test.MakeTestSecret(nil, + func(s *corev1.Secret) { + s.Name = "argocd-repo-server-tls" + s.Type = corev1.SecretTypeTLS + s.Data = map[string][]byte{ + "tls.crt": []byte(test.TestCert), + "tls.key": []byte(test.TestKey), + } + }, + ), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = mockServerName + }, + ), + test.MakeTestDeployment(nil, + func(d *appsv1.Deployment) { + d.Name = "test-argocd-repo-server" + }, + ), + test.MakeTestStatefulSet(nil, + func(d *appsv1.StatefulSet) { + d.Name = mockAppControllerName + }, + ), + }, + secretExist: true, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + reconciler := makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + tt.resources..., + ) + reconciler.varSetter() + + reconciler.Server = mock.NewServer(mockServerName, test.TestNamespace, reconciler.Client) + reconciler.Appcontroller = mock.NewAppController(mockAppControllerName, test.TestNamespace, reconciler.Client) + + err := reconciler.reconcileTLSSecret() + if !tt.secretExist { + assert.NoError(t, err) + return + } + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + return + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + res, err := resource.GetObject(test.TestArgoCDName, test.TestNamespace, &argoproj.ArgoCD{}, reconciler.Client) + assert.NoError(t, err) + argocd := res.(*argoproj.ArgoCD) + assert.NotEqual(t, "", argocd.Status.RepoTLSChecksum) + + dep, err := workloads.GetDeployment(mockServerName, test.TestNamespace, reconciler.Client) + assert.NoError(t, err) + _, ok := dep.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"] + assert.True(t, ok) + + dep, err = workloads.GetDeployment(resourceName, test.TestNamespace, reconciler.Client) + assert.NoError(t, err) + _, ok = dep.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"] + assert.True(t, ok) + + ss, err := workloads.GetStatefulSet(mockAppControllerName, test.TestNamespace, reconciler.Client) + assert.NoError(t, err) + _, ok = ss.Spec.Template.ObjectMeta.Labels["repo.tls.cert.changed"] + assert.True(t, ok) + + }) + } +} + +func TestDeleteSecret(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + secretExist bool + expectedError bool + }{ + { + name: "Secret exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestSecret(nil), + ), + secretExist: true, + expectedError: false, + }, + { + name: "Secret does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + secretExist: false, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + err := tt.reconciler.deleteSecret(test.TestName, test.TestNamespace) + + if tt.secretExist { + _, err := workloads.GetSecret(test.TestName, test.TestNamespace, tt.reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + } + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + }) + } +} diff --git a/controllers/argocd/reposerver/service.go b/controllers/argocd/reposerver/service.go new file mode 100644 index 000000000..0186e7fb7 --- /dev/null +++ b/controllers/argocd/reposerver/service.go @@ -0,0 +1,104 @@ +package reposerver + +import ( + "strconv" + + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/pkg/networking" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/pkg/errors" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (rsr *RepoServerReconciler) reconcileService() error { + req := networking.ServiceRequest{ + ObjectMeta: argoutil.GetObjMeta(resourceName, rsr.Instance.Namespace, rsr.Instance.Name, rsr.Instance.Namespace, component), + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "server", + Port: common.DefaultRepoServerPort, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(common.DefaultRepoServerPort), + }, + { + Name: common.ArgoCDMetrics, + Port: common.DefaultRepoServerMetricsPort, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(common.DefaultRepoServerMetricsPort), + }, + }, + Selector: map[string]string{ + common.AppK8sKeyName: resourceName, + }, + }, + Instance: rsr.Instance, + Mutations: []mutation.MutateFunc{mutation.ApplyReconcilerMutation}, + MutationArgs: util.ConvertStringMapToInterfaces(map[string]string{ + common.TLSSecretNameKey: common.ArgoCDRepoServerTLSSecretName, + common.WantAutoTLSKey: strconv.FormatBool(rsr.Instance.Spec.Repo.WantsAutoTLS()), + }), + Client: rsr.Client, + } + + desired, err := networking.RequestService(req) + if err != nil { + return errors.Wrapf(err, "reconcileService: failed to request service %s", desired.Name) + } + + if err = controllerutil.SetControllerReference(rsr.Instance, desired, rsr.Scheme); err != nil { + rsr.Logger.Error(err, "reconcileService: failed to set owner reference for service", "name", desired.Name) + } + + existing, err := networking.GetService(desired.Name, desired.Namespace, rsr.Client) + if err != nil { + if !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "reconcileService: failed to retrieve service %s", desired.Name) + } + + if err = networking.CreateService(desired, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileService: failed to create service %s", desired.Name) + } + rsr.Logger.Info("service created", "name", desired.Name, "namespace", desired.Namespace) + return nil + } + + changed := false + + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existing.Labels, Desired: &desired.Labels, ExtraAction: nil}, + {Existing: &existing.Annotations, Desired: &desired.Annotations, ExtraAction: nil}, + {Existing: &existing.Spec, Desired: &desired.Spec, ExtraAction: nil}, + } + + argocdcommon.UpdateIfChanged(fieldsToCompare, &changed) + + if !changed { + return nil + } + + if err = networking.UpdateService(existing, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileService: failed to update service %s", existing.Name) + } + + rsr.Logger.Info("service updated", "name", existing.Name, "namespace", existing.Namespace) + return nil +} + +func (rsr *RepoServerReconciler) deleteService(name, namespace string) error { + if err := networking.DeleteService(name, namespace, rsr.Client); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return errors.Wrapf(err, "deleteService: failed to delete service %s in namespace %s", name, namespace) + } + rsr.Logger.Info("service deleted", "name", name, "namespace", namespace) + return nil +} diff --git a/controllers/argocd/reposerver/service_test.go b/controllers/argocd/reposerver/service_test.go new file mode 100644 index 000000000..bd60aafba --- /dev/null +++ b/controllers/argocd/reposerver/service_test.go @@ -0,0 +1,216 @@ +package reposerver + +import ( + "testing" + + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/networking" + "github.com/argoproj-labs/argocd-operator/tests/test" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func TestReconcileService_create(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedError bool + expectedService *corev1.Service + }{ + { + name: "Service does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedError: false, + expectedService: getDesiredSvc(), + }, + { + name: "Service exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestService(nil, + func(svc *corev1.Service) { + svc.Name = "test-argocd-repo-server" + }, + ), + ), + expectedError: false, + expectedService: getDesiredSvc(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + err := tt.reconciler.reconcileService() + assert.NoError(t, err) + + _, err = networking.GetService("test-argocd-repo-server", test.TestNamespace, tt.reconciler.Client) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + }) + } +} + +func TestReconcileService_update(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedError bool + expectedService *corev1.Service + }{ + { + name: "Service drift", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestService(getDesiredSvc(), + func(svc *corev1.Service) { + svc.Name = "test-argocd-repo-server" + // Modify some fields to simulate drift + svc.Spec.Ports = []corev1.ServicePort{ + { + Name: "server", + Port: 8087, + }, + } + }, + ), + ), + expectedError: false, + expectedService: getDesiredSvc(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + err := tt.reconciler.reconcileService() + assert.NoError(t, err) + + existing, err := networking.GetService("test-argocd-repo-server", test.TestNamespace, tt.reconciler.Client) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + if tt.expectedService != nil { + match := true + + // Check for partial match on relevant fields + ftc := []argocdcommon.FieldToCompare{ + { + Existing: existing.Labels, + Desired: tt.expectedService.Labels, + }, + { + Existing: existing.Annotations, + Desired: tt.expectedService.Annotations, + }, + { + Existing: existing.Spec, + Desired: tt.expectedService.Spec, + }, + } + argocdcommon.PartialMatch(ftc, &match) + assert.True(t, match) + } + }) + } +} + +func TestDeleteService(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + serviceExist bool + expectedError bool + }{ + { + name: "Service exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestService(nil), + ), + serviceExist: true, + expectedError: false, + }, + { + name: "Service does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + serviceExist: false, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + err := tt.reconciler.deleteService(test.TestName, test.TestNamespace) + + if tt.serviceExist { + _, err := networking.GetService(test.TestName, test.TestNamespace, tt.reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + } + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + }) + } +} + +func getDesiredSvc() *corev1.Service { + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-argocd-repo-server", + Namespace: "test-ns", + Labels: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server", + "app.kubernetes.io/part-of": "argocd", + "app.kubernetes.io/instance": "test-argocd", + "app.kubernetes.io/managed-by": "argocd-operator", + "app.kubernetes.io/component": "repo-server", + }, + Annotations: map[string]string{ + "argocds.argoproj.io/name": "test-argocd", + "argocds.argoproj.io/namespace": "test-ns", + }, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server", + }, + Ports: []corev1.ServicePort{ + { + Name: "server", + Port: 8081, + Protocol: "TCP", + TargetPort: intstr.FromInt(8081), + }, + { + Name: "metrics", + Port: 8084, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(8084), + }, + }, + }, + } +} diff --git a/controllers/argocd/reposerver/serviceaccount.go b/controllers/argocd/reposerver/serviceaccount.go new file mode 100644 index 000000000..1907ecb28 --- /dev/null +++ b/controllers/argocd/reposerver/serviceaccount.go @@ -0,0 +1,47 @@ +package reposerver + +import ( + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/permissions" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (rsr *RepoServerReconciler) reconcileServiceAccount() error { + + req := permissions.ServiceAccountRequest{ + ObjectMeta: argoutil.GetObjMeta(resourceName, rsr.Instance.Namespace, rsr.Instance.Name, rsr.Instance.Namespace, component), + } + + desired := permissions.RequestServiceAccount(req) + + if err := controllerutil.SetControllerReference(rsr.Instance, desired, rsr.Scheme); err != nil { + rsr.Logger.Error(err, "reconcileServiceAccount: failed to set owner reference for serviceaccount") + } + + _, err := permissions.GetServiceAccount(desired.Name, desired.Namespace, rsr.Client) + if err != nil { + if !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "reconcileServiceAccount: failed to retrieve serviceaccount") + } + + if err = permissions.CreateServiceAccount(desired, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileServiceAccount: failed to create serviceaccount") + } + rsr.Logger.Info("serviceaccount created", "name", desired.Name, "namespace", desired.Namespace) + return nil + } + return nil +} + +func (rsr *RepoServerReconciler) deleteServiceAccount(name, namespace string) error { + if err := permissions.DeleteServiceAccount(name, namespace, rsr.Client); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return errors.Wrapf(err, "deleteServiceAccount: failed to delete serviceaccount %s in namespace %s", name, namespace) + } + rsr.Logger.Info("service account deleted", "name", name, "namespace", namespace) + return nil +} diff --git a/controllers/argocd/reposerver/serviceaccount_test.go b/controllers/argocd/reposerver/serviceaccount_test.go new file mode 100644 index 000000000..f8f6a17c8 --- /dev/null +++ b/controllers/argocd/reposerver/serviceaccount_test.go @@ -0,0 +1,104 @@ +package reposerver + +import ( + "testing" + + "github.com/argoproj-labs/argocd-operator/pkg/permissions" + "github.com/argoproj-labs/argocd-operator/tests/test" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func TestReconcileServiceAccount(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + expectedError bool + expectedCreateLogMessage string + }{ + { + name: "ServiceAccount does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + expectedError: false, + expectedCreateLogMessage: "serviceaccount created", + }, + { + name: "ServiceAccount exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestServiceAccount( + func(sa *corev1.ServiceAccount) { + sa.Name = "test-argocd-repo-server" + }, + ), + ), + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + err := tt.reconciler.reconcileServiceAccount() + assert.NoError(t, err) + + _, err = permissions.GetServiceAccount("test-argocd-repo-server", test.TestNamespace, tt.reconciler.Client) + + // Validate the error condition + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + }) + } +} + +func TestDeleteServiceAccount(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + serviceAccountExist bool + expectedError bool + }{ + { + name: "ServiceAccount exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestServiceAccount(), + ), + serviceAccountExist: true, + expectedError: false, + }, + { + name: "ServiceAccount does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + serviceAccountExist: false, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + err := tt.reconciler.deleteServiceAccount(test.TestName, test.TestNamespace) + + if tt.serviceAccountExist { + _, err := permissions.GetServiceAccount(test.TestName, test.TestNamespace, tt.reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + } + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + }) + } +} diff --git a/controllers/argocd/reposerver/servicemonitor.go b/controllers/argocd/reposerver/servicemonitor.go new file mode 100644 index 000000000..e86fdd58d --- /dev/null +++ b/controllers/argocd/reposerver/servicemonitor.go @@ -0,0 +1,101 @@ +package reposerver + +import ( + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/monitoring" + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/pkg/errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (rsr *RepoServerReconciler) reconcileServiceMonitor() error { + + // return if prometheus API is not present on cluster + if !monitoring.IsPrometheusAPIAvailable() { + rsr.Logger.Debug("prometheus API unavailable, skip service monitor reconciliation") + return nil + } + + req := monitoring.ServiceMonitorRequest{ + ObjectMeta: argoutil.GetObjMeta(resourceMetricsName, rsr.Instance.Namespace, rsr.Instance.Name, rsr.Instance.Namespace, component), + Spec: monitoringv1.ServiceMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.AppK8sKeyName: resourceName, + }, + }, + Endpoints: []monitoringv1.Endpoint{ + { + Port: common.ArgoCDMetrics, + }, + }, + }, + } + + req.ObjectMeta.Labels[common.PrometheusReleaseKey] = common.PrometheusOperator + + desired, err := monitoring.RequestServiceMonitor(req) + if err != nil { + return errors.Wrapf(err, "reconcileServiceMonitor: failed to request service monitor %s", desired.Name) + } + + if err = controllerutil.SetControllerReference(rsr.Instance, desired, rsr.Scheme); err != nil { + rsr.Logger.Error(err, "reconcileServiceMonitor: failed to set owner reference for service monitor", "name", desired.Name) + } + + existing, err := monitoring.GetServiceMonitor(desired.Name, desired.Namespace, rsr.Client) + if err != nil { + if !apierrors.IsNotFound(err) { + return errors.Wrapf(err, "reconcileServiceMonitor: failed to retrieve service monitor %s", desired.Name) + } + + if err = monitoring.CreateServiceMonitor(desired, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileServiceMonitor: failed to create service monitor %s", desired.Name) + } + rsr.Logger.Info("service monitor created", "name", desired.Name, "namespace", desired.Namespace) + return nil + } + + changed := false + + fieldsToCompare := []argocdcommon.FieldToCompare{ + {Existing: &existing.Labels, Desired: &desired.Labels, ExtraAction: nil}, + {Existing: &existing.Annotations, Desired: &desired.Annotations, ExtraAction: nil}, + {Existing: &existing.Spec, Desired: &desired.Spec, ExtraAction: nil}, + } + + argocdcommon.UpdateIfChanged(fieldsToCompare, &changed) + + if !changed { + return nil + } + + if err = monitoring.UpdateServiceMonitor(existing, rsr.Client); err != nil { + return errors.Wrapf(err, "reconcileServiceMonitor: failed to update service monitor %s", existing.Name) + } + + rsr.Logger.Info("service monitor updated", "name", existing.Name, "namespace", existing.Namespace) + return nil +} + +func (rsr *RepoServerReconciler) deleteServiceMonitor(name, namespace string) error { + // return if prometheus API is not present on cluster + if !monitoring.IsPrometheusAPIAvailable() { + rsr.Logger.Debug("prometheus API unavailable, skip service monitor deletion") + return nil + } + + if err := monitoring.DeleteServiceMonitor(name, namespace, rsr.Client); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return errors.Wrapf(err, "deleteServiceMonitor: failed to delete service monitor %s in namespace %s", name, namespace) + } + rsr.Logger.Info("service monitor deleted", "name", name, "namespace", namespace) + return nil +} diff --git a/controllers/argocd/reposerver/servicemonitor_test.go b/controllers/argocd/reposerver/servicemonitor_test.go new file mode 100644 index 000000000..6b969b816 --- /dev/null +++ b/controllers/argocd/reposerver/servicemonitor_test.go @@ -0,0 +1,239 @@ +package reposerver + +import ( + "testing" + + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/monitoring" + "github.com/argoproj-labs/argocd-operator/tests/test" + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestReconcileServiceMonitor_create(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + prometheusAPIAvailable bool + expectedError bool + expectedServiceMonitor *monitoringv1.ServiceMonitor + }{ + { + name: "Prometheus API absent", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + prometheusAPIAvailable: false, + expectedError: true, + }, + { + name: "ServiceMonitor does not exist", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + prometheusAPIAvailable: true, + expectedError: false, + expectedServiceMonitor: getDesiredSvcMonitor(), + }, + { + name: "ServiceMonitor exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestServiceMonitor(nil, + func(sm *monitoringv1.ServiceMonitor) { + sm.Name = "test-argocd-repo-server-metrics" + }, + ), + ), + prometheusAPIAvailable: true, + expectedError: false, + expectedServiceMonitor: getDesiredSvcMonitor(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + monitoring.SetPrometheusAPIFound(tt.prometheusAPIAvailable) + defer monitoring.SetPrometheusAPIFound(false) + + err := tt.reconciler.reconcileServiceMonitor() + assert.NoError(t, err) + + _, err = monitoring.GetServiceMonitor("test-argocd-repo-server-metrics", test.TestNamespace, tt.reconciler.Client) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + }) + } +} + +func TestReconcileServiceMonitor_update(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + prometheusAPIAvailable bool + expectedError bool + expectedServiceMonitor *monitoringv1.ServiceMonitor + }{ + { + name: "ServiceMonitor drift", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestServiceMonitor( + getDesiredSvcMonitor(), + func(sm *monitoringv1.ServiceMonitor) { + sm.Name = "test-argocd-repo-server-metrics" + sm.Spec.Endpoints = []monitoringv1.Endpoint{ + { + Port: "diff-port", + }, + } + }, + ), + ), + prometheusAPIAvailable: true, + expectedError: false, + expectedServiceMonitor: getDesiredSvcMonitor(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.reconciler.varSetter() + + monitoring.SetPrometheusAPIFound(tt.prometheusAPIAvailable) + defer monitoring.SetPrometheusAPIFound(false) + + err := tt.reconciler.reconcileServiceMonitor() + assert.NoError(t, err) + + existing, err := monitoring.GetServiceMonitor("test-argocd-repo-server-metrics", test.TestNamespace, tt.reconciler.Client) + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + + if tt.expectedServiceMonitor != nil { + match := true + + ftc := []argocdcommon.FieldToCompare{ + { + Existing: existing.Labels, + Desired: tt.expectedServiceMonitor.Labels, + }, + { + Existing: existing.Annotations, + Desired: tt.expectedServiceMonitor.Annotations, + }, + { + Existing: existing.Spec, + Desired: tt.expectedServiceMonitor.Spec, + }, + } + argocdcommon.PartialMatch(ftc, &match) + assert.True(t, match) + } + }) + } +} + +func TestDeleteServiceMonitor(t *testing.T) { + tests := []struct { + name string + reconciler *RepoServerReconciler + prometheusAPIAvailable bool + svcMonitorExist bool + expectedError bool + }{ + { + name: "Prometheus API absent", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + prometheusAPIAvailable: false, + svcMonitorExist: false, + expectedError: false, + }, + { + name: "ServiceMonitor not found", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + ), + prometheusAPIAvailable: true, + svcMonitorExist: false, + expectedError: false, + }, + { + name: "ServiceMonitor exists", + reconciler: makeTestReposerverReconciler( + test.MakeTestArgoCD(nil), + test.MakeTestServiceMonitor(nil), + ), + prometheusAPIAvailable: true, + svcMonitorExist: true, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + monitoring.SetPrometheusAPIFound(tt.prometheusAPIAvailable) + defer monitoring.SetPrometheusAPIFound(false) + + err := tt.reconciler.deleteServiceMonitor(test.TestName, test.TestNamespace) + + if tt.svcMonitorExist { + _, err := monitoring.GetServiceMonitor(test.TestName, test.TestNamespace, tt.reconciler.Client) + assert.True(t, apierrors.IsNotFound(err)) + } + + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none.") + } else { + assert.NoError(t, err, "Expected no error but got one.") + } + }) + } +} + +func getDesiredSvcMonitor() *monitoringv1.ServiceMonitor { + return &monitoringv1.ServiceMonitor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-argocd-repo-server-metrics", + Namespace: "test-ns", + Labels: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server-metrics", + "app.kubernetes.io/part-of": "argocd", + "app.kubernetes.io/instance": "test-argocd", + "app.kubernetes.io/managed-by": "argocd-operator", + "app.kubernetes.io/component": "repo-server", + "release": "prometheus-operator", + }, + Annotations: map[string]string{ + "argocds.argoproj.io/name": "test-argocd", + "argocds.argoproj.io/namespace": "test-ns", + }, + }, + Spec: monitoringv1.ServiceMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": "test-argocd-repo-server", + }, + }, + Endpoints: []monitoringv1.Endpoint{ + { + Port: "metrics", + }, + }, + }, + } +} diff --git a/controllers/argocd/reposerver/status.go b/controllers/argocd/reposerver/status.go new file mode 100644 index 000000000..949efd1ad --- /dev/null +++ b/controllers/argocd/reposerver/status.go @@ -0,0 +1,51 @@ +package reposerver + +import ( + "context" + + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/pkg/workloads" + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// ReconcileStatus will ensure that the Repo-server status is updated for the given ArgoCD instance +func (rsr *RepoServerReconciler) ReconcileStatus() error { + status := common.ArgoCDStatusUnknown + + deploy, err := workloads.GetDeployment(resourceName, rsr.Instance.Namespace, rsr.Client) + if err != nil { + return errors.Wrapf(err, "reconcileStatus: failed to retrieve deployment %s", resourceName) + } + + status = common.ArgoCDStatusPending + + if deploy.Spec.Replicas != nil { + if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas { + status = common.ArgoCDStatusRunning + } + } + + if rsr.Instance.Status.Repo != status { + rsr.Instance.Status.Repo = status + } + + return rsr.updateInstanceStatus() +} + +func (rsr *RepoServerReconciler) updateInstanceStatus() error { + + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := rsr.Client.Status().Update(context.TODO(), rsr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/reposerver/util.go b/controllers/argocd/reposerver/util.go deleted file mode 100644 index 395de6e7a..000000000 --- a/controllers/argocd/reposerver/util.go +++ /dev/null @@ -1,11 +0,0 @@ -package reposerver - -import ( - "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/pkg/argoutil" -) - -// GetRepoServerAddress will return the Argo CD repo server address. -func GetRepoServerAddress(name string, namespace string) string { - return argoutil.FqdnServiceRef(argoutil.NameWithSuffix(name, ArgoCDRepoServerControllerComponent), namespace, common.ArgoCDDefaultRepoServerPort) -} diff --git a/controllers/argocd/reposerver_TOBEREMOVED.go b/controllers/argocd/reposerver_TOBEREMOVED.go new file mode 100644 index 000000000..c9b40a3ff --- /dev/null +++ b/controllers/argocd/reposerver_TOBEREMOVED.go @@ -0,0 +1,615 @@ +package argocd + +import ( + "context" + "crypto/sha256" + "fmt" + "os" + "reflect" + "time" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// reconcileStatusRepo will ensure that the Repo status is updated for the given ArgoCD. +func (r *ReconcileArgoCD) reconcileStatusRepo(cr *argoproj.ArgoCD) error { + status := "Unknown" + + deploy := newDeploymentWithSuffix("repo-server", "repo-server", cr) + if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) { + status = "Pending" + + if deploy.Spec.Replicas != nil { + if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas { + status = "Running" + } + } + } + + if cr.Status.Repo != status { + cr.Status.Repo = status + return r.Client.Status().Update(context.TODO(), cr) + } + return nil +} + +// reconcileRepoServerTLSSecret checks whether the argocd-repo-server-tls secret +// has changed since our last reconciliation loop. It does so by comparing the +// checksum of tls.crt and tls.key in the status of the ArgoCD CR against the +// values calculated from the live state in the cluster. +func (r *ReconcileArgoCD) reconcileRepoServerTLSSecret(cr *argoproj.ArgoCD) error { + var tlsSecretObj corev1.Secret + var sha256sum string + + log.Info("reconciling repo-server TLS secret") + + tlsSecretName := types.NamespacedName{Namespace: cr.Namespace, Name: common.ArgoCDRepoServerTLSSecretName} + err := r.Client.Get(context.TODO(), tlsSecretName, &tlsSecretObj) + if err != nil { + if !apierrors.IsNotFound(err) { + return err + } + } else if tlsSecretObj.Type != corev1.SecretTypeTLS { + // We only process secrets of type kubernetes.io/tls + return nil + } else { + // We do the checksum over a concatenated byte stream of cert + key + crt, crtOk := tlsSecretObj.Data[corev1.TLSCertKey] + key, keyOk := tlsSecretObj.Data[corev1.TLSPrivateKeyKey] + if crtOk && keyOk { + var sumBytes []byte + sumBytes = append(sumBytes, crt...) + sumBytes = append(sumBytes, key...) + sha256sum = fmt.Sprintf("%x", sha256.Sum256(sumBytes)) + } + } + + // The content of the TLS secret has changed since we last looked if the + // calculated checksum doesn't match the one stored in the status. + if cr.Status.RepoTLSChecksum != sha256sum { + // We store the value early to prevent a possible restart loop, for the + // cost of a possibly missed restart when we cannot update the status + // field of the resource. + cr.Status.RepoTLSChecksum = sha256sum + err = r.Client.Status().Update(context.TODO(), cr) + if err != nil { + return err + } + + // Trigger rollout of API server + apiDepl := newDeploymentWithSuffix("server", "server", cr) + err = r.triggerRollout(apiDepl, "repo.tls.cert.changed") + if err != nil { + return err + } + + // Trigger rollout of repository server + repoDepl := newDeploymentWithSuffix("repo-server", "repo-server", cr) + err = r.triggerRollout(repoDepl, "repo.tls.cert.changed") + if err != nil { + return err + } + + // Trigger rollout of application controller + controllerSts := newStatefulSetWithSuffix("application-controller", "application-controller", cr) + err = r.triggerRollout(controllerSts, "repo.tls.cert.changed") + if err != nil { + return err + } + } + + return nil +} + +// reconcileRepoService will ensure that the Service for the Argo CD repo server is present. +func (r *ReconcileArgoCD) reconcileRepoService(cr *argoproj.ArgoCD) error { + svc := newServiceWithSuffix("repo-server", "repo-server", cr) + + if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { + if !cr.Spec.Repo.IsEnabled() { + return r.Client.Delete(context.TODO(), svc) + } + if ensureAutoTLSAnnotation(svc, common.ArgoCDRepoServerTLSSecretName, cr.Spec.Repo.WantsAutoTLS()) { + return r.Client.Update(context.TODO(), svc) + } + return nil // Service found, do nothing + } + + if !cr.Spec.Repo.IsEnabled() { + return nil + } + + ensureAutoTLSAnnotation(svc, common.ArgoCDRepoServerTLSSecretName, cr.Spec.Repo.WantsAutoTLS()) + + svc.Spec.Selector = map[string]string{ + common.ArgoCDKeyName: nameWithSuffix("repo-server", cr), + } + + svc.Spec.Ports = []corev1.ServicePort{ + { + Name: "server", + Port: common.ArgoCDDefaultRepoServerPort, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), + }, { + Name: "metrics", + Port: common.ArgoCDDefaultRepoMetricsPort, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(common.ArgoCDDefaultRepoMetricsPort), + }, + } + + if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { + return err + } + return r.Client.Create(context.TODO(), svc) +} + +// getArgoRepoResources will return the ResourceRequirements for the Argo CD Repo server container. +func getArgoRepoResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements { + resources := corev1.ResourceRequirements{} + + // Allow override of resource requirements from CR + if cr.Spec.Repo.Resources != nil { + resources = *cr.Spec.Repo.Resources + } + + return resources +} + +func isRepoServerTLSVerificationRequested(cr *argoproj.ArgoCD) bool { + return cr.Spec.Repo.VerifyTLS +} + +// getRepoServerContainerImage will return the container image for the Repo server. +// +// There are three possible options for configuring the image, and this is the +// order of preference. +// +// 1. from the Spec, the spec.repo field has an image and version to use for +// generating an image reference. +// 2. from the Environment, this looks for the `ARGOCD_REPOSERVER_IMAGE` field and uses +// that if the spec is not configured. +// 3. the default is configured in common.ArgoCDDefaultRepoServerVersion and +// common.ArgoCDDefaultRepoServerImage. +func getRepoServerContainerImage(cr *argoproj.ArgoCD) string { + defaultImg, defaultTag := false, false + img := cr.Spec.Repo.Image + if img == "" { + img = common.ArgoCDDefaultArgoImage + defaultImg = true + } + + tag := cr.Spec.Repo.Version + if tag == "" { + tag = common.ArgoCDDefaultArgoVersion + defaultTag = true + } + if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) { + return e + } + return argoutil.CombineImageTag(img, tag) +} + +// getRepoServerAddress will return the Argo CD repo server address. +func getRepoServerAddress(cr *argoproj.ArgoCD) string { + if cr.Spec.Repo.Remote != nil && *cr.Spec.Repo.Remote != "" { + return *cr.Spec.Repo.Remote + } + return fqdnServiceRef("repo-server", common.ArgoCDDefaultRepoServerPort, cr) +} + +// getArgoCDRepoServerReplicas will return the size value for the argocd-repo-server replica count if it +// has been set in argocd CR. Otherwise, nil is returned if the replicas is not set in the argocd CR or +// replicas value is < 0. +func getArgoCDRepoServerReplicas(cr *argoproj.ArgoCD) *int32 { + if cr.Spec.Repo.Replicas != nil && *cr.Spec.Repo.Replicas >= 0 { + return cr.Spec.Repo.Replicas + } + + return nil +} + +// getArgoRepoCommand will return the command for the ArgoCD Repo component. +func getArgoRepoCommand(cr *argoproj.ArgoCD, useTLSForRedis bool) []string { + cmd := make([]string, 0) + + cmd = append(cmd, "uid_entrypoint.sh") + cmd = append(cmd, "argocd-repo-server") + + if cr.Spec.Redis.IsEnabled() { + cmd = append(cmd, "--redis", getRedisServerAddress(cr)) + } else { + log.Info("Redis is Disabled. Skipping adding Redis configuration to Repo Server.") + } + if useTLSForRedis { + cmd = append(cmd, "--redis-use-tls") + if isRedisTLSVerificationDisabled(cr) { + cmd = append(cmd, "--redis-insecure-skip-tls-verify") + } else { + cmd = append(cmd, "--redis-ca-certificate", "/app/config/reposerver/tls/redis/tls.crt") + } + } + + cmd = append(cmd, "--loglevel") + cmd = append(cmd, getLogLevel(cr.Spec.Repo.LogLevel)) + + cmd = append(cmd, "--logformat") + cmd = append(cmd, getLogFormat(cr.Spec.Repo.LogFormat)) + + // *** NOTE *** + // Do Not add any new default command line arguments below this. + extraArgs := cr.Spec.Repo.ExtraRepoCommandArgs + err := isMergable(extraArgs, cmd) + if err != nil { + return cmd + } + + cmd = append(cmd, extraArgs...) + return cmd +} + +// reconcileRepoServerServiceMonitor will ensure that the ServiceMonitor is present for the Repo Server metrics Service. +func (r *ReconcileArgoCD) reconcileRepoServerServiceMonitor(cr *argoproj.ArgoCD) error { + sm := newServiceMonitorWithSuffix("repo-server-metrics", cr) + if argoutil.IsObjectFound(r.Client, cr.Namespace, sm.Name, sm) { + if !cr.Spec.Prometheus.Enabled { + // ServiceMonitor exists but enabled flag has been set to false, delete the ServiceMonitor + return r.Client.Delete(context.TODO(), sm) + } + return nil // ServiceMonitor found, do nothing + } + + if !cr.Spec.Prometheus.Enabled { + return nil // Prometheus not enabled, do nothing. + } + + sm.Spec.Selector = metav1.LabelSelector{ + MatchLabels: map[string]string{ + common.ArgoCDKeyName: nameWithSuffix("repo-server", cr), + }, + } + sm.Spec.Endpoints = []monitoringv1.Endpoint{ + { + Port: common.ArgoCDMetrics, + }, + } + + if err := controllerutil.SetControllerReference(cr, sm, r.Scheme); err != nil { + return err + } + return r.Client.Create(context.TODO(), sm) +} + +// getArgoCmpServerInitCommand will return the command for the ArgoCD CMP Server init container +func getArgoCmpServerInitCommand() []string { + cmd := make([]string, 0) + cmd = append(cmd, "cp") + cmd = append(cmd, "-n") + cmd = append(cmd, "/usr/local/bin/argocd") + cmd = append(cmd, "/var/run/argocd/argocd-cmp-server") + return cmd +} + +// reconcileRepoDeployment will ensure the Deployment resource is present for the ArgoCD Repo component. +func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argoproj.ArgoCD, useTLSForRedis bool) error { + deploy := newDeploymentWithSuffix("repo-server", "repo-server", cr) + automountToken := false + if cr.Spec.Repo.MountSAToken { + automountToken = cr.Spec.Repo.MountSAToken + } + + deploy.Spec.Template.Spec.AutomountServiceAccountToken = &automountToken + + if cr.Spec.Repo.ServiceAccount != "" { + deploy.Spec.Template.Spec.ServiceAccountName = cr.Spec.Repo.ServiceAccount + } + + // Global proxy env vars go first + repoEnv := cr.Spec.Repo.Env + // Environment specified in the CR take precedence over everything else + repoEnv = argoutil.EnvMerge(repoEnv, proxyEnvVars(), false) + if cr.Spec.Repo.ExecTimeout != nil { + repoEnv = argoutil.EnvMerge(repoEnv, []corev1.EnvVar{{Name: "ARGOCD_EXEC_TIMEOUT", Value: fmt.Sprintf("%ds", *cr.Spec.Repo.ExecTimeout)}}, true) + } + + AddSeccompProfileForOpenShift(r.Client, &deploy.Spec.Template.Spec) + + deploy.Spec.Template.Spec.InitContainers = []corev1.Container{{ + Name: "copyutil", + Image: getArgoContainerImage(cr), + Command: getArgoCmpServerInitCommand(), + ImagePullPolicy: corev1.PullAlways, + Resources: getArgoRepoResources(cr), + Env: proxyEnvVars(), + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: boolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: boolPtr(true), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "var-files", + MountPath: "/var/run/argocd", + }, + }, + }} + + if cr.Spec.Repo.InitContainers != nil { + deploy.Spec.Template.Spec.InitContainers = append(deploy.Spec.Template.Spec.InitContainers, cr.Spec.Repo.InitContainers...) + } + + repoServerVolumeMounts := []corev1.VolumeMount{ + { + Name: "ssh-known-hosts", + MountPath: "/app/config/ssh", + }, + { + Name: "tls-certs", + MountPath: "/app/config/tls", + }, + { + Name: "gpg-keys", + MountPath: "/app/config/gpg/source", + }, + { + Name: "gpg-keyring", + MountPath: "/app/config/gpg/keys", + }, + { + Name: "tmp", + MountPath: "/tmp", + }, + { + Name: "argocd-repo-server-tls", + MountPath: "/app/config/reposerver/tls", + }, + { + Name: common.ArgoCDRedisServerTLSSecretName, + MountPath: "/app/config/reposerver/tls/redis", + }, + { + Name: "plugins", + MountPath: "/home/argocd/cmp-server/plugins", + }, + } + + if cr.Spec.Repo.VolumeMounts != nil { + repoServerVolumeMounts = append(repoServerVolumeMounts, cr.Spec.Repo.VolumeMounts...) + } + + deploy.Spec.Template.Spec.Containers = []corev1.Container{{ + Command: getArgoRepoCommand(cr, useTLSForRedis), + Image: getRepoServerContainerImage(cr), + ImagePullPolicy: corev1.PullAlways, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + Env: repoEnv, + Name: "argocd-repo-server", + Ports: []corev1.ContainerPort{ + { + ContainerPort: common.ArgoCDDefaultRepoServerPort, + Name: "server", + }, { + ContainerPort: common.ArgoCDDefaultRepoMetricsPort, + Name: "metrics", + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), + }, + }, + InitialDelaySeconds: 5, + PeriodSeconds: 10, + }, + Resources: getArgoRepoResources(cr), + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: boolPtr(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: boolPtr(true), + }, + VolumeMounts: repoServerVolumeMounts, + }} + + if cr.Spec.Repo.SidecarContainers != nil { + deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, cr.Spec.Repo.SidecarContainers...) + } + + repoServerVolumes := []corev1.Volume{ + { + Name: "ssh-known-hosts", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDKnownHostsConfigMapName, + }, + }, + }, + }, + { + Name: "tls-certs", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDTLSCertsConfigMapName, + }, + }, + }, + }, + { + Name: "gpg-keys", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: common.ArgoCDGPGKeysConfigMapName, + }, + }, + }, + }, + { + Name: "gpg-keyring", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "tmp", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "argocd-repo-server-tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: common.ArgoCDRepoServerTLSSecretName, + Optional: boolPtr(true), + }, + }, + }, + { + Name: common.ArgoCDRedisServerTLSSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: common.ArgoCDRedisServerTLSSecretName, + Optional: boolPtr(true), + }, + }, + }, + { + Name: "var-files", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "plugins", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + + if cr.Spec.Repo.Volumes != nil { + repoServerVolumes = append(repoServerVolumes, cr.Spec.Repo.Volumes...) + } + + deploy.Spec.Template.Spec.Volumes = repoServerVolumes + + if replicas := getArgoCDRepoServerReplicas(cr); replicas != nil { + deploy.Spec.Replicas = replicas + } + + existing := newDeploymentWithSuffix("repo-server", "repo-server", cr) + if argoutil.IsObjectFound(r.Client, cr.Namespace, existing.Name, existing) { + + if !cr.Spec.Repo.IsEnabled() { + log.Info("Existing ArgoCD Repo Server found but should be disabled. Deleting Repo Server") + // Delete existing deployment for ArgoCD Repo Server, if any .. + return r.Client.Delete(context.TODO(), existing) + } + + changed := false + actualImage := existing.Spec.Template.Spec.Containers[0].Image + desiredImage := getRepoServerContainerImage(cr) + if actualImage != desiredImage { + existing.Spec.Template.Spec.Containers[0].Image = desiredImage + if existing.Spec.Template.ObjectMeta.Labels == nil { + existing.Spec.Template.ObjectMeta.Labels = map[string]string{ + "image.upgraded": time.Now().UTC().Format("01022006-150406-MST"), + } + } + existing.Spec.Template.ObjectMeta.Labels["image.upgraded"] = time.Now().UTC().Format("01022006-150406-MST") + changed = true + } + updateNodePlacement(existing, deploy, &changed) + if !reflect.DeepEqual(deploy.Spec.Template.Spec.Volumes, existing.Spec.Template.Spec.Volumes) { + existing.Spec.Template.Spec.Volumes = deploy.Spec.Template.Spec.Volumes + changed = true + } + if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].VolumeMounts, + existing.Spec.Template.Spec.Containers[0].VolumeMounts) { + existing.Spec.Template.Spec.Containers[0].VolumeMounts = deploy.Spec.Template.Spec.Containers[0].VolumeMounts + changed = true + } + if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Env, + existing.Spec.Template.Spec.Containers[0].Env) { + existing.Spec.Template.Spec.Containers[0].Env = deploy.Spec.Template.Spec.Containers[0].Env + changed = true + } + if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Resources, existing.Spec.Template.Spec.Containers[0].Resources) { + existing.Spec.Template.Spec.Containers[0].Resources = deploy.Spec.Template.Spec.Containers[0].Resources + changed = true + } + if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[0].Command, existing.Spec.Template.Spec.Containers[0].Command) { + existing.Spec.Template.Spec.Containers[0].Command = deploy.Spec.Template.Spec.Containers[0].Command + changed = true + } + if !reflect.DeepEqual(deploy.Spec.Template.Spec.Containers[1:], + existing.Spec.Template.Spec.Containers[1:]) { + existing.Spec.Template.Spec.Containers = append(existing.Spec.Template.Spec.Containers[0:1], + deploy.Spec.Template.Spec.Containers[1:]...) + changed = true + } + if !reflect.DeepEqual(deploy.Spec.Template.Spec.InitContainers, existing.Spec.Template.Spec.InitContainers) { + existing.Spec.Template.Spec.InitContainers = deploy.Spec.Template.Spec.InitContainers + changed = true + } + + if !reflect.DeepEqual(deploy.Spec.Replicas, existing.Spec.Replicas) { + existing.Spec.Replicas = deploy.Spec.Replicas + changed = true + } + + if deploy.Spec.Template.Spec.AutomountServiceAccountToken != existing.Spec.Template.Spec.AutomountServiceAccountToken { + existing.Spec.Template.Spec.AutomountServiceAccountToken = deploy.Spec.Template.Spec.AutomountServiceAccountToken + changed = true + } + + if deploy.Spec.Template.Spec.ServiceAccountName != existing.Spec.Template.Spec.ServiceAccountName { + existing.Spec.Template.Spec.ServiceAccountName = deploy.Spec.Template.Spec.ServiceAccountName + changed = true + } + + if changed { + return r.Client.Update(context.TODO(), existing) + } + return nil // Deployment found with nothing to do, move along... + } + + if !cr.Spec.Repo.IsEnabled() { + log.Info("ArgoCD Repo Server disabled. Skipping starting ArgoCD Repo Server.") + return nil + } + + if err := controllerutil.SetControllerReference(cr, deploy, r.Scheme); err != nil { + return err + } + return r.Client.Create(context.TODO(), deploy) +} diff --git a/controllers/argocd/secret.go b/controllers/argocd/secret.go index d8a299f20..8612df752 100644 --- a/controllers/argocd/secret.go +++ b/controllers/argocd/secret.go @@ -460,74 +460,6 @@ func (r *ReconcileArgoCD) reconcileClusterPermissionsSecret(cr *argoproj.ArgoCD) return r.Client.Create(context.TODO(), secret) } -// reconcileRepoServerTLSSecret checks whether the argocd-repo-server-tls secret -// has changed since our last reconciliation loop. It does so by comparing the -// checksum of tls.crt and tls.key in the status of the ArgoCD CR against the -// values calculated from the live state in the cluster. -func (r *ReconcileArgoCD) reconcileRepoServerTLSSecret(cr *argoproj.ArgoCD) error { - var tlsSecretObj corev1.Secret - var sha256sum string - - log.Info("reconciling repo-server TLS secret") - - tlsSecretName := types.NamespacedName{Namespace: cr.Namespace, Name: common.ArgoCDRepoServerTLSSecretName} - err := r.Client.Get(context.TODO(), tlsSecretName, &tlsSecretObj) - if err != nil { - if !apierrors.IsNotFound(err) { - return err - } - } else if tlsSecretObj.Type != corev1.SecretTypeTLS { - // We only process secrets of type kubernetes.io/tls - return nil - } else { - // We do the checksum over a concatenated byte stream of cert + key - crt, crtOk := tlsSecretObj.Data[corev1.TLSCertKey] - key, keyOk := tlsSecretObj.Data[corev1.TLSPrivateKeyKey] - if crtOk && keyOk { - var sumBytes []byte - sumBytes = append(sumBytes, crt...) - sumBytes = append(sumBytes, key...) - sha256sum = fmt.Sprintf("%x", sha256.Sum256(sumBytes)) - } - } - - // The content of the TLS secret has changed since we last looked if the - // calculated checksum doesn't match the one stored in the status. - if cr.Status.RepoTLSChecksum != sha256sum { - // We store the value early to prevent a possible restart loop, for the - // cost of a possibly missed restart when we cannot update the status - // field of the resource. - cr.Status.RepoTLSChecksum = sha256sum - err = r.Client.Status().Update(context.TODO(), cr) - if err != nil { - return err - } - - // Trigger rollout of API server - apiDepl := newDeploymentWithSuffix("server", "server", cr) - err = r.triggerRollout(apiDepl, "repo.tls.cert.changed") - if err != nil { - return err - } - - // Trigger rollout of repository server - repoDepl := newDeploymentWithSuffix("repo-server", "repo-server", cr) - err = r.triggerRollout(repoDepl, "repo.tls.cert.changed") - if err != nil { - return err - } - - // Trigger rollout of application controller - controllerSts := newStatefulSetWithSuffix("application-controller", "application-controller", cr) - err = r.triggerRollout(controllerSts, "repo.tls.cert.changed") - if err != nil { - return err - } - } - - return nil -} - // reconcileRedisTLSSecret checks whether the argocd-operator-redis-tls secret // has changed since our last reconciliation loop. It does so by comparing the // checksum of tls.crt and tls.key in the status of the ArgoCD CR against the diff --git a/controllers/argocd/secret_TOBEREMOVED.go b/controllers/argocd/secret_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/secret_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/server/deployment.go b/controllers/argocd/server/deployment.go new file mode 100644 index 000000000..7d3e12776 --- /dev/null +++ b/controllers/argocd/server/deployment.go @@ -0,0 +1,10 @@ +package server + +import ( + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" +) + +// TriggerDeploymentRollout starts server deployment rollout by updating the given key +func (sr *ServerReconciler) TriggerDeploymentRollout(name, namespace, key string) error { + return argocdcommon.TriggerDeploymentRollout(name, namespace, key, sr.Client) +} diff --git a/controllers/argocd/server/server.go b/controllers/argocd/server/server.go index a696a4e2a..1926beb79 100644 --- a/controllers/argocd/server/server.go +++ b/controllers/argocd/server/server.go @@ -23,3 +23,8 @@ func (sr *ServerReconciler) Reconcile() error { // controller logic goes here return nil } + +// TO DO: fix this +func (acr *ServerReconciler) TriggerRollout(key string) error { + return acr.TriggerDeploymentRollout("", "", key) +} diff --git a/controllers/argocd/server/status.go b/controllers/argocd/server/status.go new file mode 100644 index 000000000..42dd2b1d0 --- /dev/null +++ b/controllers/argocd/server/status.go @@ -0,0 +1,32 @@ +package server + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// reconcileStatus will ensure that the server status is updated for the given ArgoCD instance +func (sr *ServerReconciler) ReconcileStatus() error { + + // TO DO + + return sr.updateInstanceStatus() +} + +func (sr *ServerReconciler) updateInstanceStatus() error { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := sr.Client.Status().Update(context.TODO(), sr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/server_TOBEREMOVED.go b/controllers/argocd/server_TOBEREMOVED.go new file mode 100644 index 000000000..ab81a56a4 --- /dev/null +++ b/controllers/argocd/server_TOBEREMOVED.go @@ -0,0 +1 @@ +package argocd diff --git a/controllers/argocd/service.go b/controllers/argocd/service.go index b0f5ed7d7..150cd0cb1 100644 --- a/controllers/argocd/service.go +++ b/controllers/argocd/service.go @@ -356,50 +356,6 @@ func ensureAutoTLSAnnotation(svc *corev1.Service, secretName string, enabled boo return false } -// reconcileRepoService will ensure that the Service for the Argo CD repo server is present. -func (r *ReconcileArgoCD) reconcileRepoService(cr *argoproj.ArgoCD) error { - svc := newServiceWithSuffix("repo-server", "repo-server", cr) - - if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) { - if !cr.Spec.Repo.IsEnabled() { - return r.Client.Delete(context.TODO(), svc) - } - if ensureAutoTLSAnnotation(svc, common.ArgoCDRepoServerTLSSecretName, cr.Spec.Repo.WantsAutoTLS()) { - return r.Client.Update(context.TODO(), svc) - } - return nil // Service found, do nothing - } - - if !cr.Spec.Repo.IsEnabled() { - return nil - } - - ensureAutoTLSAnnotation(svc, common.ArgoCDRepoServerTLSSecretName, cr.Spec.Repo.WantsAutoTLS()) - - svc.Spec.Selector = map[string]string{ - common.ArgoCDKeyName: nameWithSuffix("repo-server", cr), - } - - svc.Spec.Ports = []corev1.ServicePort{ - { - Name: "server", - Port: common.ArgoCDDefaultRepoServerPort, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(common.ArgoCDDefaultRepoServerPort), - }, { - Name: "metrics", - Port: common.ArgoCDDefaultRepoMetricsPort, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(common.ArgoCDDefaultRepoMetricsPort), - }, - } - - if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil { - return err - } - return r.Client.Create(context.TODO(), svc) -} - // reconcileServerMetricsService will ensure that the Service for the Argo CD server metrics is present. func (r *ReconcileArgoCD) reconcileServerMetricsService(cr *argoproj.ArgoCD) error { svc := newServiceWithSuffix("server-metrics", "server", cr) diff --git a/controllers/argocd/sso/sso.go b/controllers/argocd/sso/sso.go index f4a20dc24..8e0b30f61 100644 --- a/controllers/argocd/sso/sso.go +++ b/controllers/argocd/sso/sso.go @@ -9,7 +9,7 @@ import ( ) type SSOReconciler struct { - Client *client.Client + Client client.Client Scheme *runtime.Scheme Instance *argoproj.ArgoCD Logger logr.Logger diff --git a/controllers/argocd/sso/status.go b/controllers/argocd/sso/status.go new file mode 100644 index 000000000..b8c2b695c --- /dev/null +++ b/controllers/argocd/sso/status.go @@ -0,0 +1,32 @@ +package sso + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/util/retry" +) + +// reconcileStatus will ensure that the sso status is updated for the given ArgoCD instance +func (sr *SSOReconciler) ReconcileStatus() error { + + // TO DO + + return sr.updateInstanceStatus() +} + +func (sr *SSOReconciler) updateInstanceStatus() error { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := sr.Client.Status().Update(context.TODO(), sr.Instance); err != nil { + return errors.Wrap(err, "UpdateInstanceStatus: failed to update instance status") + } + return nil + }) + + if err != nil { + // May be conflict if max retries were hit, or may be something unrelated + // like permissions or a network error + return err + } + return nil +} diff --git a/controllers/argocd/status.go b/controllers/argocd/status.go index a288f8c81..6f35a5dc9 100644 --- a/controllers/argocd/status.go +++ b/controllers/argocd/status.go @@ -28,8 +28,61 @@ import ( argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" "github.com/argoproj-labs/argocd-operator/pkg/argoutil" + "github.com/argoproj-labs/argocd-operator/pkg/util" ) +// reconcileStatus will ensure that all of the Status properties are updated for the given ArgoCD. +func (r *ArgoCDReconciler) reconcileStatus() error { + var statusErr util.MultiError + + if err := r.AppController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + if err := r.ServerController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + if err := r.RedisController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + if err := r.ReposerverController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + if err := r.AppsetController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + if err := r.NotificationsController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + if err := r.SSOController.ReconcileStatus(); err != nil { + r.Logger.Error(err, "reconcileStatus") + statusErr.Append(err) + } + + // TO DO + + // if err := r.reconcileStatusHost(cr); err != nil { + // return err + // } + + // if err := r.reconcileStatusPhase(cr); err != nil { + // return err + // } + + return statusErr.ErrOrNil() +} + // reconcileStatus will ensure that all of the Status properties are updated for the given ArgoCD. func (r *ReconcileArgoCD) reconcileStatus(cr *argoproj.ArgoCD) error { if err := r.reconcileStatusApplicationController(cr); err != nil { @@ -257,28 +310,6 @@ func (r *ReconcileArgoCD) reconcileStatusRedis(cr *argoproj.ArgoCD) error { return nil } -// reconcileStatusRepo will ensure that the Repo status is updated for the given ArgoCD. -func (r *ReconcileArgoCD) reconcileStatusRepo(cr *argoproj.ArgoCD) error { - status := "Unknown" - - deploy := newDeploymentWithSuffix("repo-server", "repo-server", cr) - if argoutil.IsObjectFound(r.Client, cr.Namespace, deploy.Name, deploy) { - status = "Pending" - - if deploy.Spec.Replicas != nil { - if deploy.Status.ReadyReplicas == *deploy.Spec.Replicas { - status = "Running" - } - } - } - - if cr.Status.Repo != status { - cr.Status.Repo = status - return r.Client.Status().Update(context.TODO(), cr) - } - return nil -} - // reconcileStatusServer will ensure that the Server status is updated for the given ArgoCD. func (r *ReconcileArgoCD) reconcileStatusServer(cr *argoproj.ArgoCD) error { status := "Unknown" diff --git a/controllers/argocd/testing.go b/controllers/argocd/testing.go index b0aada082..33653a138 100644 --- a/controllers/argocd/testing.go +++ b/controllers/argocd/testing.go @@ -25,10 +25,6 @@ import ( v1 "k8s.io/api/rbac/v1" resourcev1 "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/argoproj-labs/argocd-operator/pkg/argoutil" @@ -36,6 +32,8 @@ import ( argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" ) +type argoCDOpt func(*argoproj.ArgoCD) + const ( testNamespace = "argocd" testArgoCDName = "argocd" @@ -46,68 +44,6 @@ func ZapLogger(development bool) logr.Logger { return zap.New(zap.UseDevMode(development)) } -type namespaceOpt func(*corev1.Namespace) - -func makeTestNs(opts ...namespaceOpt) *corev1.Namespace { - a := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ns", - Labels: make(map[string]string), - }, - } - for _, o := range opts { - o(a) - } - return a -} - -type SchemeOpt func(*runtime.Scheme) error - -func makeNewTestReconciler(client client.Client, sch *runtime.Scheme) *ArgoCDReconciler { - return &ArgoCDReconciler{ - Client: client, - Scheme: sch, - } -} - -func makeTestReconcilerClient(sch *runtime.Scheme, resObjs, subresObjs []client.Object, runtimeObj []runtime.Object) client.Client { - client := fake.NewClientBuilder().WithScheme(sch) - if len(resObjs) > 0 { - client = client.WithObjects(resObjs...) - } - if len(subresObjs) > 0 { - client = client.WithStatusSubresource(subresObjs...) - } - if len(runtimeObj) > 0 { - client = client.WithRuntimeObjects(runtimeObj...) - } - return client.Build() -} - -func makeTestReconcilerScheme(sOpts ...SchemeOpt) *runtime.Scheme { - s := scheme.Scheme - for _, opt := range sOpts { - _ = opt(s) - } - - return s -} - -type argoCDOpt func(*argoproj.ArgoCD) - -func makeTestArgoCD(opts ...argoCDOpt) *argoproj.ArgoCD { - a := &argoproj.ArgoCD{ - ObjectMeta: metav1.ObjectMeta{ - Name: testArgoCDName, - Namespace: testNamespace, - }, - } - for _, o := range opts { - o(a) - } - return a -} - func makeTestArgoCDForKeycloak() *argoproj.ArgoCD { a := &argoproj.ArgoCD{ ObjectMeta: metav1.ObjectMeta{ @@ -278,17 +214,3 @@ func makeTestDexResources() *corev1.ResourceRequirements { }, } } - -// func createNs(r *ArgoCDReconciler, n string, managedBy string) error { -// ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: n}} -// if managedBy != "" { -// ns.Labels = map[string]string{common.ArgoCDArgoprojKeyManagedBy: managedBy} -// } - -// if r.ResourceManagedNamespaces == nil { -// r.ResourceManagedNamespaces = make(map[string]string) -// } -// r.ResourceManagedNamespaces[ns.Name] = "" - -// return r.Client.Create(context.TODO(), ns) -// } diff --git a/controllers/argocd/util.go b/controllers/argocd/util.go index f7a715b5f..3ebf145b9 100644 --- a/controllers/argocd/util.go +++ b/controllers/argocd/util.go @@ -105,57 +105,11 @@ func getArgoApplicationControllerCommand(cr *argoproj.ArgoCD, useTLSForRedis boo return cmd } -// getRepoServerContainerImage will return the container image for the Repo server. -// -// There are three possible options for configuring the image, and this is the -// order of preference. -// -// 1. from the Spec, the spec.repo field has an image and version to use for -// generating an image reference. -// 2. from the Environment, this looks for the `ARGOCD_REPOSERVER_IMAGE` field and uses -// that if the spec is not configured. -// 3. the default is configured in common.ArgoCDDefaultRepoServerVersion and -// common.ArgoCDDefaultRepoServerImage. -func getRepoServerContainerImage(cr *argoproj.ArgoCD) string { - defaultImg, defaultTag := false, false - img := cr.Spec.Repo.Image - if img == "" { - img = common.ArgoCDDefaultArgoImage - defaultImg = true - } - - tag := cr.Spec.Repo.Version - if tag == "" { - tag = common.ArgoCDDefaultArgoVersion - defaultTag = true - } - if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) { - return e - } - return argoutil.CombineImageTag(img, tag) -} - -// getArgoRepoResources will return the ResourceRequirements for the Argo CD Repo server container. -func getArgoRepoResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements { - resources := corev1.ResourceRequirements{} - - // Allow override of resource requirements from CR - if cr.Spec.Repo.Resources != nil { - resources = *cr.Spec.Repo.Resources - } - - return resources -} - // getArgoServerInsecure returns the insecure value for the ArgoCD Server component. func getArgoServerInsecure(cr *argoproj.ArgoCD) bool { return cr.Spec.Server.Insecure } -func isRepoServerTLSVerificationRequested(cr *argoproj.ArgoCD) bool { - return cr.Spec.Repo.VerifyTLS -} - func isRedisTLSVerificationDisabled(cr *argoproj.ArgoCD) bool { return cr.Spec.Redis.DisableTLSVerification } diff --git a/main.go b/main.go index 27eac3c6c..f2170057f 100644 --- a/main.go +++ b/main.go @@ -105,7 +105,7 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.BoolVar(&enableHTTP2, "enable-http2", enableHTTP2, "If HTTP/2 should be enabled for the metrics and webhook servers.") flag.BoolVar(&secureMetrics, "metrics-secure", secureMetrics, "If the metrics endpoint should be served securely.") - flag.StringVar(&logLevel, "loglevel", "info", "The desired logger level") + flag.StringVar(&logLevel, "loglevel", "debug", "The desired logger level") opts := ctrlzap.Options{ Development: true, diff --git a/pkg/argoutil/resource.go b/pkg/argoutil/resource.go index 94f3dc91d..ce7605685 100644 --- a/pkg/argoutil/resource.go +++ b/pkg/argoutil/resource.go @@ -16,6 +16,7 @@ package argoutil import ( "fmt" + "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,19 +30,19 @@ func FqdnServiceRef(serviceName, namespace string, port int) string { return fmt.Sprintf("%s.%s.svc.cluster.local:%d", serviceName, namespace, port) } -// NameWithSuffix will return a string using the Name from the given ObjectMeta with the provded suffix appended. -func NameWithSuffix(name, suffix string) string { - return fmt.Sprintf("%s-%s", name, suffix) +// NameWithSuffix will return a string using the Name from the given ObjectMeta with the provded suffixes appended. +func NameWithSuffix(name string, suffixes ...string) string { + return fmt.Sprintf("%s-%s", name, strings.Join(suffixes, "-")) } // GenerateResourceName generates names for namespace scoped resources -func GenerateResourceName(instanceName, suffix string) string { - return NameWithSuffix(instanceName, suffix) +func GenerateResourceName(instanceName string, suffixes ...string) string { + return NameWithSuffix(instanceName, suffixes...) } // GenerateUniqueResourceName generates unique names for cluster scoped resources -func GenerateUniqueResourceName(instanceName, instanceNamespace, suffix string) string { - return fmt.Sprintf("%s-%s-%s", instanceName, instanceNamespace, suffix) +func GenerateUniqueResourceName(instanceName, instanceNamespace string, suffixes ...string) string { + return NameWithSuffix(NameWithSuffix(instanceName, instanceName), suffixes...) } func GetObjMeta(resName, resNs, instanceName, instanceNs, component string) metav1.ObjectMeta { diff --git a/pkg/cluster/namespace_test.go b/pkg/cluster/namespace_test.go index a2743e2a7..5fb07543a 100644 --- a/pkg/cluster/namespace_test.go +++ b/pkg/cluster/namespace_test.go @@ -19,53 +19,20 @@ import ( argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -var ( - testName = "test-name" - testKey = "test-key" - testVal = "test-value" - testValMutated = "test-value-mutated" - - testKVP = map[string]string{ - testKey: testVal, - } - - testKVPMutated = map[string]string{ - testKey: testValMutated, - } -) - -func testMutationFuncFailed(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { - return errors.New("test-mutation-error") -} - func testMutationFuncSuccessful(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { switch obj := resource.(type) { case *corev1.Namespace: - if _, ok := obj.Labels[testKey]; ok { - obj.Labels[testKey] = testValMutated + if _, ok := obj.Labels[test.TestKey]; ok { + obj.Labels[test.TestKey] = test.TestValMutated return nil } } return errors.New("test-mutation-error") } -type NamespaceOpt func(*corev1.Namespace) - -func getTestNamespace(opts ...NamespaceOpt) *corev1.Namespace { - desiredNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - }, - } - - for _, opt := range opts { - opt(desiredNs) - } - return desiredNs -} - func TestRequestNamespace(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -76,16 +43,16 @@ func TestRequestNamespace(t *testing.T) { wantErr bool }{ { - name: "request namespace, no mutation, custom name, labels, annotations", + name: "request namespace", nsReq: NamespaceRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, + Name: test.TestName, + Labels: test.TestKVP, }, }, - desiredNamespace: getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName - ns.Labels = testKVP + desiredNamespace: test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName + ns.Labels = test.TestKVP }), wantErr: false, }, @@ -93,17 +60,17 @@ func TestRequestNamespace(t *testing.T) { name: "request namespace, successful mutation", nsReq: NamespaceRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, + Name: test.TestName, + Labels: test.TestKVP, }, Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, - desiredNamespace: getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName - ns.Labels = testKVPMutated + desiredNamespace: test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName + ns.Labels = test.TestKVPMutated }), wantErr: false, }, @@ -111,17 +78,17 @@ func TestRequestNamespace(t *testing.T) { name: "request namespace, failed mutation", nsReq: NamespaceRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, + Name: test.TestName, + Labels: test.TestKVP, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredNamespace: getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName - ns.Labels = testKVP + desiredNamespace: test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName + ns.Labels = test.TestKVP }), wantErr: true, }, @@ -144,48 +111,48 @@ func TestRequestNamespace(t *testing.T) { func TestCreateNamespace(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredNamespace := getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName + desiredNamespace := test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName ns.TypeMeta = metav1.TypeMeta{ Kind: "Namespace", APIVersion: "v1", } - ns.Labels = testKVP + ns.Labels = test.TestKVP }) err := CreateNamespace(desiredNamespace, testClient) assert.NoError(t, err) createdNamespace := &corev1.Namespace{} - err = testClient.Get(context.TODO(), cntrlClient.ObjectKey{Name: testName}, createdNamespace) + err = testClient.Get(context.TODO(), cntrlClient.ObjectKey{Name: test.TestName}, createdNamespace) assert.NoError(t, err) assert.Equal(t, desiredNamespace, createdNamespace) } func TestGetNamespace(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName })).Build() - _, err := GetNamespace(testName, testClient) + _, err := GetNamespace(test.TestName, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetNamespace(testName, testClient) + _, err = GetNamespace(test.TestName, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListNamespaces(t *testing.T) { - namespace1 := getTestNamespace(func(ns *corev1.Namespace) { + namespace1 := test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { ns.Name = "namespace-1" ns.Labels = map[string]string{ common.AppK8sKeyComponent: "new-component-1", } }) - namespace2 := getTestNamespace(func(ns *corev1.Namespace) { ns.Name = "namespace-2" }) - namespace3 := getTestNamespace(func(ns *corev1.Namespace) { + namespace2 := test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { ns.Name = "namespace-2" }) + namespace3 := test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { ns.Name = "namespace-3" ns.Labels = map[string]string{ common.AppK8sKeyComponent: "new-component-2", @@ -219,29 +186,29 @@ func TestListNamespaces(t *testing.T) { } func TestUpdateNamespace(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName })).Build() - desiredNamespace := getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName - ns.Labels = testKVP + desiredNamespace := test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName + ns.Labels = test.TestKVP }) err := UpdateNamespace(desiredNamespace, testClient) assert.NoError(t, err) existingNamespace := &corev1.Namespace{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, existingNamespace) assert.NoError(t, err) assert.Equal(t, desiredNamespace.Labels, existingNamespace.Labels) testClient = fake.NewClientBuilder().Build() - existingNamespace = getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName - ns.Labels = testKVP + existingNamespace = test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName + ns.Labels = test.TestKVP }) err = UpdateNamespace(existingNamespace, testClient) assert.Error(t, err) @@ -249,18 +216,18 @@ func TestUpdateNamespace(t *testing.T) { } func TestDeleteNamespace(t *testing.T) { - testNamespace := getTestNamespace(func(ns *corev1.Namespace) { - ns.Name = testName + testNamespace := test.MakeTestNamespace(nil, func(ns *corev1.Namespace) { + ns.Name = test.TestName }) testClient := fake.NewClientBuilder().WithObjects(testNamespace).Build() - err := DeleteNamespace(testName, testClient) + err := DeleteNamespace(test.TestName, testClient) assert.NoError(t, err) existingNamespace := &corev1.Namespace{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, existingNamespace) assert.Error(t, err) diff --git a/pkg/monitoring/monitoring_test.go b/pkg/monitoring/monitoring_test.go index 834b1c34c..734b1eaf6 100644 --- a/pkg/monitoring/monitoring_test.go +++ b/pkg/monitoring/monitoring_test.go @@ -7,36 +7,16 @@ import ( cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -// common test variables used across workloads tests -var ( - testName = "test-name" - testInstance = "test-instance" - testInstanceNamespace = "test-instance-ns" - testNamespace = "test-ns" - testComponent = "test-component" - testKey = "test-key" - testVal = "test-value" - - testPrometheusRuleNameMutated = "mutated-name" - testServiceMonitorNameMutated = "mutated-name" - testKVP = map[string]string{ - testKey: testVal, - } -) - -func testMutationFuncFailed(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { - return errors.New("test-mutation-error") -} - func testMutationFuncSuccessful(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { switch obj := resource.(type) { case *monitoringv1.PrometheusRule: - obj.Name = testPrometheusRuleNameMutated + obj.Name = test.TestNameMutated return nil case *monitoringv1.ServiceMonitor: - obj.Name = testServiceMonitorNameMutated + obj.Name = test.TestNameMutated return nil } return errors.New("test-mutation-error") diff --git a/pkg/monitoring/prometheusRule_test.go b/pkg/monitoring/prometheusRule_test.go index 5ae8eb674..3174bf9d0 100644 --- a/pkg/monitoring/prometheusRule_test.go +++ b/pkg/monitoring/prometheusRule_test.go @@ -19,47 +19,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" - "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type prometheusRuleOpt func(*monitoringv1.PrometheusRule) - -func getTestPrometheusRule(opts ...prometheusRuleOpt) *monitoringv1.PrometheusRule { - desiredPrometheusRule := &monitoringv1.PrometheusRule{ - ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: monitoringv1.PrometheusRuleSpec{ - Groups: []monitoringv1.RuleGroup{ - { - Name: common.ArgoCDComponentStatus, - Rules: []monitoringv1.Rule{ - { - Alert: "test alert", - }, - }, - }, - }, - }, - } - - for _, opt := range opts { - opt(desiredPrometheusRule) - } - return desiredPrometheusRule -} - func TestRequestPrometheusRule(t *testing.T) { s := scheme.Scheme @@ -74,76 +36,20 @@ func TestRequestPrometheusRule(t *testing.T) { wantErr bool }{ { - name: "request prometheusRule, no mutation", + name: "request prometheusRule", prometheusRuleReq: PrometheusRuleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: monitoringv1.PrometheusRuleSpec{ - Groups: []monitoringv1.RuleGroup{ - { - Name: common.ArgoCDComponentStatus, - Rules: []monitoringv1.Rule{ - { - Alert: "test alert", - }, - }, - }, - }, - }, - }, - mutation: false, - desiredPrometheusRule: getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) {}), - wantErr: false, - }, - { - name: "request prometheusRule, no mutation, custom name, labels, annotations", - prometheusRuleReq: PrometheusRuleRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - testKey: testVal, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - testKey: testVal, - }, - }, - Spec: monitoringv1.PrometheusRuleSpec{ - Groups: []monitoringv1.RuleGroup{ - { - Name: common.ArgoCDComponentStatus, - Rules: []monitoringv1.Rule{ - { - Alert: "test alert", - }, - }, - }, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, }, mutation: false, - desiredPrometheusRule: getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { - pr.Name = testName - pr.Labels = util.MergeMaps(pr.Labels, testKVP) - pr.Annotations = util.MergeMaps(pr.Annotations, testKVP) + desiredPrometheusRule: test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Labels = test.TestKVP + pr.Annotations = test.TestKVP + }), wantErr: false, }, @@ -151,77 +57,46 @@ func TestRequestPrometheusRule(t *testing.T) { name: "request prometheusRule, successful mutation", prometheusRuleReq: PrometheusRuleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testPrometheusRuleNameMutated, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: monitoringv1.PrometheusRuleSpec{ - Groups: []monitoringv1.RuleGroup{ - { - Name: common.ArgoCDComponentStatus, - Rules: []monitoringv1.Rule{ - { - Alert: "test alert", - }, - }, - }, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, + Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, - mutation: true, - desiredPrometheusRule: getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { pr.Name = testPrometheusRuleNameMutated }), - wantErr: false, + mutation: true, + desiredPrometheusRule: test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Name = test.TestNameMutated + pr.Labels = test.TestKVP + pr.Annotations = test.TestKVP + }), + wantErr: false, }, { name: "request prometheusRule, failed mutation", prometheusRuleReq: PrometheusRuleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: monitoringv1.PrometheusRuleSpec{ - Groups: []monitoringv1.RuleGroup{ - { - Name: common.ArgoCDComponentStatus, - Rules: []monitoringv1.Rule{ - { - Alert: "test alert", - }, - }, - }, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, + Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - mutation: true, - desiredPrometheusRule: getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) {}), - wantErr: true, + mutation: true, + desiredPrometheusRule: test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Labels = test.TestKVP + pr.Annotations = test.TestKVP + }), + wantErr: true, }, } @@ -246,21 +121,23 @@ func TestCreatePrometheusRule(t *testing.T) { assert.NoError(t, monitoringv1.AddToScheme(s)) testClient := fake.NewClientBuilder().WithScheme(s).Build() - desiredPrometheusRule := getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { + desiredPrometheusRule := test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { pr.TypeMeta = metav1.TypeMeta{ Kind: "PrometheusRule", APIVersion: "monitoring.coreos.com/v1", } - pr.Name = testName - pr.Namespace = testNamespace + pr.Name = test.TestName + pr.Namespace = test.TestNamespace + pr.Labels = test.TestKVP + pr.Annotations = test.TestKVP }) err := CreatePrometheusRule(desiredPrometheusRule, testClient) assert.NoError(t, err) createdPrometheusRule := &monitoringv1.PrometheusRule{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdPrometheusRule) assert.NoError(t, err) @@ -271,31 +148,31 @@ func TestGetPrometheusRule(t *testing.T) { s := scheme.Scheme assert.NoError(t, monitoringv1.AddToScheme(s)) - testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { - pr.Name = testName - pr.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Name = test.TestName + pr.Namespace = test.TestNamespace })).Build() - _, err := GetPrometheusRule(testName, testNamespace, testClient) + _, err := GetPrometheusRule(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().WithScheme(s).Build() - _, err = GetPrometheusRule(testName, testNamespace, testClient) + _, err = GetPrometheusRule(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListPrometheusRules(t *testing.T) { - prometheusRule1 := getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { + prometheusRule1 := test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { pr.Name = "prometheusRule-1" - pr.Namespace = testNamespace + pr.Namespace = test.TestNamespace pr.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - prometheusRule2 := getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { pr.Name = "prometheusRule-2" }) - prometheusRule3 := getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { + prometheusRule2 := test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { pr.Name = "prometheusRule-2" }) + prometheusRule3 := test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { pr.Name = "prometheusRule-3" - pr.Namespace = testNamespace + pr.Namespace = test.TestNamespace pr.Labels[common.AppK8sKeyComponent] = "new-component-2" }) @@ -316,7 +193,7 @@ func TestListPrometheusRules(t *testing.T) { desiredPrometheusRules := []string{"prometheusRule-1", "prometheusRule-3"} - existingPrometheusRuleList, err := ListPrometheusRules(testNamespace, testClient, listOpts) + existingPrometheusRuleList, err := ListPrometheusRules(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingPrometheusRules := []string{} @@ -333,9 +210,9 @@ func TestUpdatePrometheusRule(t *testing.T) { assert.NoError(t, monitoringv1.AddToScheme(s)) // Create the initial PrometheusRule - initialPrometheusRule := getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { - pr.Name = testName - pr.Namespace = testNamespace + initialPrometheusRule := test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Name = test.TestName + pr.Namespace = test.TestNamespace }) // Create the client with the initial PrometheusRule @@ -343,7 +220,7 @@ func TestUpdatePrometheusRule(t *testing.T) { // Fetch the PrometheusRule from the client desiredPrometheusRule := &monitoringv1.PrometheusRule{} - err := testClient.Get(context.TODO(), types.NamespacedName{Name: testName, Namespace: testNamespace}, desiredPrometheusRule) + err := testClient.Get(context.TODO(), types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}, desiredPrometheusRule) assert.NoError(t, err) newRuleGroups := []monitoringv1.RuleGroup{ @@ -374,16 +251,16 @@ func TestUpdatePrometheusRule(t *testing.T) { existingPrometheusRule := &monitoringv1.PrometheusRule{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingPrometheusRule) assert.NoError(t, err) assert.Equal(t, desiredPrometheusRule.Spec.Groups, existingPrometheusRule.Spec.Groups) testClient = fake.NewClientBuilder().WithScheme(s).Build() - existingPrometheusRule = getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { - pr.Name = testName + existingPrometheusRule = test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Name = test.TestName pr.Labels = nil }) err = UpdatePrometheusRule(existingPrometheusRule, testClient) @@ -394,20 +271,20 @@ func TestDeletePrometheusRule(t *testing.T) { s := scheme.Scheme assert.NoError(t, monitoringv1.AddToScheme(s)) - testPrometheusRule := getTestPrometheusRule(func(pr *monitoringv1.PrometheusRule) { - pr.Name = testName - pr.Namespace = testNamespace + testPrometheusRule := test.MakeTestPrometheusRule(nil, func(pr *monitoringv1.PrometheusRule) { + pr.Name = test.TestName + pr.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(testPrometheusRule).Build() - err := DeletePrometheusRule(testName, testNamespace, testClient) + err := DeletePrometheusRule(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingPrometheusRule := &monitoringv1.PrometheusRule{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingPrometheusRule) assert.Error(t, err) diff --git a/pkg/monitoring/serviceMonitor_test.go b/pkg/monitoring/serviceMonitor_test.go index 02a016996..0267967d4 100644 --- a/pkg/monitoring/serviceMonitor_test.go +++ b/pkg/monitoring/serviceMonitor_test.go @@ -17,49 +17,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/argoproj-labs/argocd-operator/common" - "github.com/argoproj-labs/argocd-operator/pkg/argoutil" "github.com/argoproj-labs/argocd-operator/pkg/mutation" - util "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type serviceMonitorOpt func(*monitoringv1.ServiceMonitor) - -func getTestServiceMonitor(opts ...serviceMonitorOpt) *monitoringv1.ServiceMonitor { - desiredServiceMonitor := &monitoringv1.ServiceMonitor{ - ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: monitoringv1.ServiceMonitorSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.AppK8sKeyName: argoutil.GenerateResourceName(testInstance, common.ArgoCDMetrics), - }, - }, - Endpoints: []monitoringv1.Endpoint{ - { - Port: common.ArgoCDMetrics, - }, - }, - }, - } - - for _, opt := range opts { - opt(desiredServiceMonitor) - } - return desiredServiceMonitor -} - func TestRequestServiceMonitor(t *testing.T) { s := scheme.Scheme @@ -73,74 +34,44 @@ func TestRequestServiceMonitor(t *testing.T) { wantErr bool }{ { - name: "request serviceMonitor, no mutation", + name: "request serviceMonitor", serviceMonitorReq: ServiceMonitorRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: monitoringv1.ServiceMonitorSpec{ Selector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.AppK8sKeyName: argoutil.GenerateResourceName(testInstance, common.ArgoCDMetrics), - }, - }, - Endpoints: []monitoringv1.Endpoint{ - { - Port: common.ArgoCDMetrics, - }, + MatchLabels: test.TestKVP, }, }, }, - desiredServiceMonitor: getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) {}), - wantErr: false, + desiredServiceMonitor: test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Labels = test.TestKVP + sm.Annotations = test.TestKVP + }), + wantErr: false, }, { - name: "request serviceMonitor, no mutation, custom name, labels, annotations", + name: "request serviceMonitor", serviceMonitorReq: ServiceMonitorRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - testKey: testVal, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - testKey: testVal, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: monitoringv1.ServiceMonitorSpec{ Selector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.AppK8sKeyName: argoutil.GenerateResourceName(testInstance, common.ArgoCDMetrics), - }, - }, - Endpoints: []monitoringv1.Endpoint{ - { - Port: common.ArgoCDMetrics, - }, + MatchLabels: test.TestKVP, }, }, }, - desiredServiceMonitor: getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { - sm.Name = testName - sm.Labels = util.MergeMaps(sm.Labels, testKVP) - sm.Annotations = util.MergeMaps(sm.Annotations, testKVP) + desiredServiceMonitor: test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Labels = test.TestKVP + sm.Annotations = test.TestKVP }), wantErr: false, }, @@ -148,29 +79,14 @@ func TestRequestServiceMonitor(t *testing.T) { name: "request serviceMonitor, successful mutation", serviceMonitorReq: ServiceMonitorRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testServiceMonitorNameMutated, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: monitoringv1.ServiceMonitorSpec{ Selector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.AppK8sKeyName: argoutil.GenerateResourceName(testInstance, common.ArgoCDMetrics), - }, - }, - Endpoints: []monitoringv1.Endpoint{ - { - Port: common.ArgoCDMetrics, - }, + MatchLabels: test.TestKVP, }, }, Mutations: []mutation.MutateFunc{ @@ -178,44 +94,33 @@ func TestRequestServiceMonitor(t *testing.T) { }, Client: testClient, }, - desiredServiceMonitor: getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { sm.Name = testServiceMonitorNameMutated }), - wantErr: false, + desiredServiceMonitor: test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Name = test.TestNameMutated + sm.Labels = test.TestKVP + sm.Annotations = test.TestKVP + }), + wantErr: false, }, { name: "request serviceMonitor, failed mutation", serviceMonitorReq: ServiceMonitorRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: monitoringv1.ServiceMonitorSpec{ Selector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - common.AppK8sKeyName: argoutil.GenerateResourceName(testInstance, common.ArgoCDMetrics), - }, - }, - Endpoints: []monitoringv1.Endpoint{ - { - Port: common.ArgoCDMetrics, - }, + MatchLabels: test.TestKVP, }, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredServiceMonitor: getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) {}), + desiredServiceMonitor: test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) {}), wantErr: true, }, } @@ -241,21 +146,23 @@ func TestCreateServiceMonitor(t *testing.T) { assert.NoError(t, monitoringv1.AddToScheme(s)) testClient := fake.NewClientBuilder().WithScheme(s).Build() - desiredServiceMonitor := getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { + desiredServiceMonitor := test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { sm.TypeMeta = metav1.TypeMeta{ Kind: "ServiceMonitor", APIVersion: "monitoring.coreos.com/v1", } - sm.Name = testName - sm.Namespace = testNamespace + sm.Name = test.TestName + sm.Namespace = test.TestNamespace + sm.Labels = test.TestKVP + sm.Annotations = test.TestKVP }) err := CreateServiceMonitor(desiredServiceMonitor, testClient) assert.NoError(t, err) createdServiceMonitor := &monitoringv1.ServiceMonitor{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdServiceMonitor) assert.NoError(t, err) @@ -266,31 +173,31 @@ func TestGetServiceMonitor(t *testing.T) { s := scheme.Scheme assert.NoError(t, monitoringv1.AddToScheme(s)) - testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { - sm.Name = testName - sm.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Name = test.TestName + sm.Namespace = test.TestNamespace })).Build() - _, err := GetServiceMonitor(testName, testNamespace, testClient) + _, err := GetServiceMonitor(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().WithScheme(s).Build() - _, err = GetServiceMonitor(testName, testNamespace, testClient) + _, err = GetServiceMonitor(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListServiceMonitors(t *testing.T) { - serviceMonitor1 := getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { + serviceMonitor1 := test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { sm.Name = "serviceMonitor-1" - sm.Namespace = testNamespace + sm.Namespace = test.TestNamespace sm.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - serviceMonitor2 := getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { sm.Name = "serviceMonitor-2" }) - serviceMonitor3 := getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { + serviceMonitor2 := test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { sm.Name = "serviceMonitor-2" }) + serviceMonitor3 := test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { sm.Name = "serviceMonitor-3" - sm.Namespace = testNamespace + sm.Namespace = test.TestNamespace sm.Labels[common.AppK8sKeyComponent] = "new-component-2" }) @@ -311,7 +218,7 @@ func TestListServiceMonitors(t *testing.T) { desiredServiceMonitors := []string{"serviceMonitor-1", "serviceMonitor-3"} - existingServiceMonitorList, err := ListServiceMonitors(testNamespace, testClient, listOpts) + existingServiceMonitorList, err := ListServiceMonitors(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingServiceMonitors := []string{} @@ -328,9 +235,9 @@ func TestUpdateServiceMonitor(t *testing.T) { assert.NoError(t, monitoringv1.AddToScheme(s)) // Create the initial ServiceMonitor - initialServiceMonitor := getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { - sm.Name = testName - sm.Namespace = testNamespace + initialServiceMonitor := test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Name = test.TestName + sm.Namespace = test.TestNamespace }) // Create the client with the initial ServiceMonitor @@ -338,7 +245,7 @@ func TestUpdateServiceMonitor(t *testing.T) { // Fetch the ServiceMonitor from the client desiredServiceMonitor := &monitoringv1.ServiceMonitor{} - err := testClient.Get(context.TODO(), types.NamespacedName{Name: testName, Namespace: testNamespace}, desiredServiceMonitor) + err := testClient.Get(context.TODO(), types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}, desiredServiceMonitor) assert.NoError(t, err) desiredServiceMonitor.Spec.Endpoints = []monitoringv1.Endpoint{ @@ -352,16 +259,16 @@ func TestUpdateServiceMonitor(t *testing.T) { existingServiceMonitor := &monitoringv1.ServiceMonitor{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingServiceMonitor) assert.NoError(t, err) assert.Equal(t, desiredServiceMonitor.Spec.Endpoints, existingServiceMonitor.Spec.Endpoints) testClient = fake.NewClientBuilder().WithScheme(s).Build() - existingServiceMonitor = getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { - sm.Name = testName + existingServiceMonitor = test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Name = test.TestName sm.Labels = nil }) err = UpdateServiceMonitor(existingServiceMonitor, testClient) @@ -372,20 +279,20 @@ func TestDeleteServiceMonitor(t *testing.T) { s := scheme.Scheme assert.NoError(t, monitoringv1.AddToScheme(s)) - testServiceMonitor := getTestServiceMonitor(func(sm *monitoringv1.ServiceMonitor) { - sm.Name = testName - sm.Namespace = testNamespace + testServiceMonitor := test.MakeTestServiceMonitor(nil, func(sm *monitoringv1.ServiceMonitor) { + sm.Name = test.TestName + sm.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(testServiceMonitor).Build() - err := DeleteServiceMonitor(testName, testNamespace, testClient) + err := DeleteServiceMonitor(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingServiceMonitor := &monitoringv1.ServiceMonitor{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingServiceMonitor) assert.Error(t, err) diff --git a/pkg/networking/ingress_test.go b/pkg/networking/ingress_test.go index 7ca11b1d9..8311e62fd 100644 --- a/pkg/networking/ingress_test.go +++ b/pkg/networking/ingress_test.go @@ -19,6 +19,7 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/test" ) type ingressOpt func(*networkingv1.Ingress) @@ -27,17 +28,17 @@ func getTestIngress(opts ...ingressOpt) *networkingv1.Ingress { nginx := "nginx" desiredIngress := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, + Name: test.TestName, + Namespace: test.TestNamespace, Labels: map[string]string{ - common.AppK8sKeyName: testInstance, + common.AppK8sKeyName: test.TestInstance, common.AppK8sKeyPartOf: common.ArgoCDAppName, common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, + common.AppK8sKeyComponent: test.TestComponent, }, Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, + common.ArgoCDArgoprojKeyName: test.TestInstance, + common.ArgoCDArgoprojKeyNamespace: test.TestInstanceNamespace, }, }, Spec: networkingv1.IngressSpec{ @@ -82,17 +83,17 @@ func TestRequestIngress(t *testing.T) { name: "request ingress, no mutation", ingressReq: IngressRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, + Name: test.TestName, + Namespace: test.TestNamespace, Labels: map[string]string{ - common.AppK8sKeyName: testInstance, + common.AppK8sKeyName: test.TestInstance, common.AppK8sKeyPartOf: common.ArgoCDAppName, common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, + common.AppK8sKeyComponent: test.TestComponent, }, Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, + common.ArgoCDArgoprojKeyName: test.TestInstance, + common.ArgoCDArgoprojKeyNamespace: test.TestInstanceNamespace, }, }, Spec: networkingv1.IngressSpec{ @@ -120,19 +121,19 @@ func TestRequestIngress(t *testing.T) { name: "request ingress, no mutation, custom name, labels, annotations", ingressReq: IngressRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, + Name: test.TestName, + Namespace: test.TestNamespace, Labels: map[string]string{ - common.AppK8sKeyName: testInstance, + common.AppK8sKeyName: test.TestInstance, common.AppK8sKeyPartOf: common.ArgoCDAppName, common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - testKey: testVal, + common.AppK8sKeyComponent: test.TestComponent, + test.TestKey: test.TestVal, }, Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - testKey: testVal, + common.ArgoCDArgoprojKeyName: test.TestInstance, + common.ArgoCDArgoprojKeyNamespace: test.TestInstanceNamespace, + test.TestKey: test.TestVal, }, }, Spec: networkingv1.IngressSpec{ @@ -154,9 +155,9 @@ func TestRequestIngress(t *testing.T) { }, mutation: false, desiredIngress: getTestIngress(func(i *networkingv1.Ingress) { - i.Name = testName - i.Labels = util.MergeMaps(i.Labels, testKVP) - i.Annotations = util.MergeMaps(i.Annotations, testKVP) + i.Name = test.TestName + i.Labels = util.MergeMaps(i.Labels, test.TestKVP) + i.Annotations = util.MergeMaps(i.Annotations, test.TestKVP) }), wantErr: false, }, @@ -165,16 +166,16 @@ func TestRequestIngress(t *testing.T) { ingressReq: IngressRequest{ ObjectMeta: metav1.ObjectMeta{ Name: testIngressNameMutated, - Namespace: testNamespace, + Namespace: test.TestNamespace, Labels: map[string]string{ - common.AppK8sKeyName: testInstance, + common.AppK8sKeyName: test.TestInstance, common.AppK8sKeyPartOf: common.ArgoCDAppName, common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, + common.AppK8sKeyComponent: test.TestComponent, }, Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, + common.ArgoCDArgoprojKeyName: test.TestInstance, + common.ArgoCDArgoprojKeyNamespace: test.TestInstanceNamespace, }, }, Spec: networkingv1.IngressSpec{ @@ -206,17 +207,17 @@ func TestRequestIngress(t *testing.T) { name: "request ingress, failed mutation", ingressReq: IngressRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, + Name: test.TestName, + Namespace: test.TestNamespace, Labels: map[string]string{ - common.AppK8sKeyName: testInstance, + common.AppK8sKeyName: test.TestInstance, common.AppK8sKeyPartOf: common.ArgoCDAppName, common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, + common.AppK8sKeyComponent: test.TestComponent, }, Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, + common.ArgoCDArgoprojKeyName: test.TestInstance, + common.ArgoCDArgoprojKeyNamespace: test.TestInstanceNamespace, }, }, Spec: networkingv1.IngressSpec{ @@ -236,7 +237,7 @@ func TestRequestIngress(t *testing.T) { }, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, @@ -272,16 +273,16 @@ func TestCreateIngress(t *testing.T) { Kind: "Ingress", APIVersion: "networking.k8s.io/v1", } - i.Name = testName - i.Namespace = testNamespace + i.Name = test.TestName + i.Namespace = test.TestNamespace }) err := CreateIngress(desiredIngress, testClient) assert.NoError(t, err) createdIngress := &networkingv1.Ingress{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdIngress) assert.NoError(t, err) @@ -293,16 +294,16 @@ func TestGetIngress(t *testing.T) { assert.NoError(t, networkingv1.AddToScheme(s)) testClient := fake.NewClientBuilder().WithScheme(s).WithObjects(getTestIngress(func(i *networkingv1.Ingress) { - i.Name = testName - i.Namespace = testNamespace + i.Name = test.TestName + i.Namespace = test.TestNamespace })).Build() - _, err := GetIngress(testName, testNamespace, testClient) + _, err := GetIngress(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().WithScheme(s).Build() - _, err = GetIngress(testName, testNamespace, testClient) + _, err = GetIngress(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } @@ -310,13 +311,13 @@ func TestGetIngress(t *testing.T) { func TestListIngresss(t *testing.T) { ingress1 := getTestIngress(func(i *networkingv1.Ingress) { i.Name = "ingress-1" - i.Namespace = testNamespace + i.Namespace = test.TestNamespace i.Labels[common.AppK8sKeyComponent] = "new-component-1" }) ingress2 := getTestIngress(func(i *networkingv1.Ingress) { i.Name = "ingress-2" }) ingress3 := getTestIngress(func(i *networkingv1.Ingress) { i.Name = "ingress-3" - i.Namespace = testNamespace + i.Namespace = test.TestNamespace i.Labels[common.AppK8sKeyComponent] = "new-component-2" }) @@ -337,7 +338,7 @@ func TestListIngresss(t *testing.T) { desiredIngresss := []string{"ingress-1", "ingress-3"} - existingIngressList, err := ListIngresss(testNamespace, testClient, listOpts) + existingIngressList, err := ListIngresss(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingIngresss := []string{} @@ -355,8 +356,8 @@ func TestUpdateIngress(t *testing.T) { // Create the initial Ingress initialIngress := getTestIngress(func(i *networkingv1.Ingress) { - i.Name = testName - i.Namespace = testNamespace + i.Name = test.TestName + i.Namespace = test.TestNamespace }) // Create the client with the initial Ingress @@ -364,7 +365,7 @@ func TestUpdateIngress(t *testing.T) { // Fetch the Ingress from the client desiredIngress := &networkingv1.Ingress{} - err := testClient.Get(context.TODO(), types.NamespacedName{Name: testName, Namespace: testNamespace}, desiredIngress) + err := testClient.Get(context.TODO(), types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}, desiredIngress) assert.NoError(t, err) desiredIngress.Spec.Rules = []networkingv1.IngressRule{ @@ -378,8 +379,8 @@ func TestUpdateIngress(t *testing.T) { existingIngress := &networkingv1.Ingress{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingIngress) assert.NoError(t, err) @@ -387,7 +388,7 @@ func TestUpdateIngress(t *testing.T) { testClient = fake.NewClientBuilder().WithScheme(s).Build() existingIngress = getTestIngress(func(i *networkingv1.Ingress) { - i.Name = testName + i.Name = test.TestName i.Labels = nil }) err = UpdateIngress(existingIngress, testClient) @@ -396,19 +397,19 @@ func TestUpdateIngress(t *testing.T) { func TestDeleteIngress(t *testing.T) { testIngress := getTestIngress(func(i *networkingv1.Ingress) { - i.Name = testName - i.Namespace = testNamespace + i.Name = test.TestName + i.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testIngress).Build() - err := DeleteIngress(testName, testNamespace, testClient) + err := DeleteIngress(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingIngress := &networkingv1.Ingress{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingIngress) assert.Error(t, err) diff --git a/pkg/networking/networking_test.go b/pkg/networking/networking_test.go index 449fc36f8..9f4878c5c 100644 --- a/pkg/networking/networking_test.go +++ b/pkg/networking/networking_test.go @@ -11,29 +11,12 @@ import ( argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" ) -// common test variables used across workloads tests var ( - testName = "test-name" - testInstance = "test-instance" - testInstanceNamespace = "test-instance-ns" - testNamespace = "test-ns" - testComponent = "test-component" - testApplicationName = "test-application-name" - testKey = "test-key" - testVal = "test-value" - testServiceNameMutated = "mutated-name" testRouteNameMutated = "mutated-name" testIngressNameMutated = "mutated-name" - testKVP = map[string]string{ - testKey: testVal, - } ) -func testMutationFuncFailed(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { - return errors.New("test-mutation-error") -} - func testMutationFuncSuccessful(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { switch obj := resource.(type) { case *corev1.Service: diff --git a/pkg/networking/service_test.go b/pkg/networking/service_test.go index 3f8e2e65e..ad6414e58 100644 --- a/pkg/networking/service_test.go +++ b/pkg/networking/service_test.go @@ -12,52 +12,14 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" - "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type serviceOpt func(*corev1.Service) - -func getTestService(opts ...serviceOpt) *corev1.Service { - desiredService := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Name: "http", - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - }, - } - - for _, opt := range opts { - opt(desiredService) - } - return desiredService -} - func TestRequestService(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -70,74 +32,19 @@ func TestRequestService(t *testing.T) { wantErr bool }{ { - name: "request service, no mutation", + name: "request service", deployReq: ServiceRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Name: "http", - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, - }, - }, - mutation: false, - desiredService: getTestService(func(s *corev1.Service) {}), - wantErr: false, - }, - { - name: "request service, no mutation, custom name, labels, annotations", - deployReq: ServiceRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - testKey: testVal, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - testKey: testVal, - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Name: "http", - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, }, mutation: false, - desiredService: getTestService(func(s *corev1.Service) { - s.Name = testName - s.Labels = util.MergeMaps(s.Labels, testKVP) - s.Annotations = util.MergeMaps(s.Annotations, testKVP) + desiredService: test.MakeTestService(nil, func(s *corev1.Service) { + s.Labels = test.TestKVP + s.Annotations = test.TestKVP }), wantErr: false, }, @@ -145,75 +52,45 @@ func TestRequestService(t *testing.T) { name: "request service, successful mutation", deployReq: ServiceRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testServiceNameMutated, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Name: "http", - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, + Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, - mutation: true, - desiredService: getTestService(func(s *corev1.Service) { s.Name = testServiceNameMutated }), - wantErr: false, + mutation: true, + desiredService: test.MakeTestService(nil, func(s *corev1.Service) { + s.Name = testServiceNameMutated + s.Labels = test.TestKVP + s.Annotations = test.TestKVP + }), + wantErr: false, }, { name: "request service, failed mutation", deployReq: ServiceRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: map[string]string{ - common.AppK8sKeyName: testInstance, - common.AppK8sKeyPartOf: common.ArgoCDAppName, - common.AppK8sKeyManagedBy: common.ArgoCDOperatorName, - common.AppK8sKeyComponent: testComponent, - }, - Annotations: map[string]string{ - common.ArgoCDArgoprojKeyName: testInstance, - common.ArgoCDArgoprojKeyNamespace: testInstanceNamespace, - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Name: "http", - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt(8080), - }, - }, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - mutation: true, - desiredService: getTestService(func(s *corev1.Service) {}), - wantErr: true, + mutation: true, + desiredService: test.MakeTestService(nil, func(s *corev1.Service) { + s.Labels = test.TestKVP + s.Annotations = test.TestKVP + }), + wantErr: true, }, } @@ -236,21 +113,23 @@ func TestRequestService(t *testing.T) { func TestCreateService(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredService := getTestService(func(s *corev1.Service) { + desiredService := test.MakeTestService(nil, func(s *corev1.Service) { s.TypeMeta = metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", } - s.Name = testName - s.Namespace = testNamespace + s.Name = test.TestName + s.Namespace = test.TestNamespace + s.Labels = test.TestKVP + s.Annotations = test.TestKVP }) err := CreateService(desiredService, testClient) assert.NoError(t, err) createdService := &corev1.Service{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdService) assert.NoError(t, err) @@ -258,29 +137,29 @@ func TestCreateService(t *testing.T) { } func TestGetService(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestService(func(s *corev1.Service) { - s.Name = testName - s.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestService(nil, func(s *corev1.Service) { + s.Name = test.TestName + s.Namespace = test.TestNamespace })).Build() - _, err := GetService(testName, testNamespace, testClient) + _, err := GetService(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetService(testName, testNamespace, testClient) + _, err = GetService(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListServices(t *testing.T) { - service1 := getTestService(func(s *corev1.Service) { + service1 := test.MakeTestService(nil, func(s *corev1.Service) { s.Name = "service-1" - s.Namespace = testNamespace + s.Namespace = test.TestNamespace s.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - service2 := getTestService(func(s *corev1.Service) { s.Name = "service-2" }) - service3 := getTestService(func(s *corev1.Service) { + service2 := test.MakeTestService(nil, func(s *corev1.Service) { s.Name = "service-2" }) + service3 := test.MakeTestService(nil, func(s *corev1.Service) { s.Name = "service-3" s.Labels[common.AppK8sKeyComponent] = "new-component-2" }) @@ -299,7 +178,7 @@ func TestListServices(t *testing.T) { desiredServices := []string{"service-1", "service-3"} - existingServiceList, err := ListServices(testNamespace, testClient, listOpts) + existingServiceList, err := ListServices(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingServices := []string{} @@ -312,14 +191,14 @@ func TestListServices(t *testing.T) { } func TestUpdateService(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestService(func(s *corev1.Service) { - s.Name = testName - s.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestService(nil, func(s *corev1.Service) { + s.Name = test.TestName + s.Namespace = test.TestNamespace })).Build() - desiredService := getTestService(func(s *corev1.Service) { - s.Name = testName - s.Namespace = testNamespace + desiredService := test.MakeTestService(nil, func(s *corev1.Service) { + s.Name = test.TestName + s.Namespace = test.TestNamespace s.Labels = map[string]string{ "control-plane": "argocd-operator", } @@ -329,37 +208,37 @@ func TestUpdateService(t *testing.T) { existingService := &corev1.Service{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingService) assert.NoError(t, err) assert.Equal(t, desiredService.Labels, existingService.Labels) testClient = fake.NewClientBuilder().Build() - existingService = getTestService(func(s *corev1.Service) { - s.Name = testName - s.Namespace = testNamespace + existingService = test.MakeTestService(nil, func(s *corev1.Service) { + s.Name = test.TestName + s.Namespace = test.TestNamespace }) err = UpdateService(existingService, testClient) assert.Error(t, err) } func TestDeleteService(t *testing.T) { - testService := getTestService(func(s *corev1.Service) { - s.Name = testName - s.Namespace = testNamespace + testService := test.MakeTestService(nil, func(s *corev1.Service) { + s.Name = test.TestName + s.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testService).Build() - err := DeleteService(testName, testNamespace, testClient) + err := DeleteService(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingService := &corev1.Service{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingService) assert.Error(t, err) diff --git a/pkg/permissions/clusterrole_test.go b/pkg/permissions/clusterrole_test.go index 231c79748..08bd690dc 100644 --- a/pkg/permissions/clusterrole_test.go +++ b/pkg/permissions/clusterrole_test.go @@ -17,25 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type clusterRoleOpt func(*rbacv1.ClusterRole) - -func getTestClusterRole(opts ...clusterRoleOpt) *rbacv1.ClusterRole { - desiredClusterClusterRole := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - Rules: testRules, - } - - for _, opt := range opts { - opt(desiredClusterClusterRole) - } - return desiredClusterClusterRole -} - func TestRequestClusterClusterRole(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -48,20 +32,20 @@ func TestRequestClusterClusterRole(t *testing.T) { wantErr bool }{ { - name: "request clusterrole, no mutation, custom name, labels, annotations", + name: "request clusterrole", rolReq: ClusterRoleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Rules: testRules, + Rules: test.TestRules, }, mutation: false, - desiredClusterRole: getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName - r.Labels = testKVP - r.Annotations = testKVP + desiredClusterRole: test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName + r.Labels = test.TestKVP + r.Annotations = test.TestKVP }), wantErr: false, }, @@ -69,21 +53,21 @@ func TestRequestClusterClusterRole(t *testing.T) { name: "request clusterrole, successful mutation", rolReq: ClusterRoleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Rules: testRules, + Rules: test.TestRules, Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, mutation: true, - desiredClusterRole: getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName - r.Labels = testKVP - r.Annotations = testKVP + desiredClusterRole: test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName + r.Labels = test.TestKVP + r.Annotations = test.TestKVP r.Rules = testRulesMutated }), wantErr: false, @@ -92,21 +76,21 @@ func TestRequestClusterClusterRole(t *testing.T) { name: "request clusterrole, failed mutation", rolReq: ClusterRoleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Rules: testRules, + Rules: test.TestRules, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, mutation: true, - desiredClusterRole: getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName - r.Labels = testKVP - r.Annotations = testKVP + desiredClusterRole: test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName + r.Labels = test.TestKVP + r.Annotations = test.TestKVP }), wantErr: true, }, @@ -131,48 +115,48 @@ func TestRequestClusterClusterRole(t *testing.T) { func TestCreateClusterRole(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredClusterRole := getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName + desiredClusterRole := test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName r.TypeMeta = metav1.TypeMeta{ Kind: "ClusterRole", APIVersion: "rbac.authorization.k8s.io/v1", } - r.Labels = testKVP - r.Annotations = testKVP + r.Labels = test.TestKVP + r.Annotations = test.TestKVP }) err := CreateClusterRole(desiredClusterRole, testClient) assert.NoError(t, err) createdClusterRole := &rbacv1.ClusterRole{} - err = testClient.Get(context.TODO(), cntrlClient.ObjectKey{Name: testName}, createdClusterRole) + err = testClient.Get(context.TODO(), cntrlClient.ObjectKey{Name: test.TestName}, createdClusterRole) assert.NoError(t, err) assert.Equal(t, desiredClusterRole, createdClusterRole) } func TestGetClusterRole(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName })).Build() - _, err := GetClusterRole(testName, testClient) + _, err := GetClusterRole(test.TestName, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetClusterRole(testName, testClient) + _, err = GetClusterRole(test.TestName, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListClusterRoles(t *testing.T) { - role1 := getTestClusterRole(func(r *rbacv1.ClusterRole) { + role1 := test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { r.Name = "role-1" r.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - role2 := getTestClusterRole(func(r *rbacv1.ClusterRole) { r.Name = "role-2" }) - role3 := getTestClusterRole(func(r *rbacv1.ClusterRole) { + role2 := test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { r.Name = "role-2" }) + role3 := test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { r.Name = "role-3" r.Labels[common.AppK8sKeyComponent] = "new-component-2" }) @@ -204,12 +188,12 @@ func TestListClusterRoles(t *testing.T) { } func TestUpdateClusterRole(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName })).Build() - desiredClusterRole := getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName + desiredClusterRole := test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName r.Rules = testRulesMutated }) err := UpdateClusterRole(desiredClusterRole, testClient) @@ -217,33 +201,33 @@ func TestUpdateClusterRole(t *testing.T) { existingClusterRole := &rbacv1.ClusterRole{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, existingClusterRole) assert.NoError(t, err) assert.Equal(t, desiredClusterRole.Rules, existingClusterRole.Rules) testClient = fake.NewClientBuilder().Build() - existingClusterRole = getTestClusterRole(func(cr *rbacv1.ClusterRole) { - cr.Name = testName + existingClusterRole = test.MakeTestClusterRole(nil, func(cr *rbacv1.ClusterRole) { + cr.Name = test.TestName }) err = UpdateClusterRole(existingClusterRole, testClient) assert.Error(t, err) } func TestDeleteClusterRole(t *testing.T) { - testClusterRole := getTestClusterRole(func(r *rbacv1.ClusterRole) { - r.Name = testName + testClusterRole := test.MakeTestClusterRole(nil, func(r *rbacv1.ClusterRole) { + r.Name = test.TestName }) testClient := fake.NewClientBuilder().WithObjects(testClusterRole).Build() - err := DeleteClusterRole(testName, testClient) + err := DeleteClusterRole(test.TestName, testClient) assert.NoError(t, err) existingClusterRole := &rbacv1.ClusterRole{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, existingClusterRole) assert.Error(t, err) diff --git a/pkg/permissions/clusterrolebinding_test.go b/pkg/permissions/clusterrolebinding_test.go index 9a104737d..adfe16075 100644 --- a/pkg/permissions/clusterrolebinding_test.go +++ b/pkg/permissions/clusterrolebinding_test.go @@ -16,24 +16,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/argoproj-labs/argocd-operator/common" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type clusterRoleBindingOpt func(*rbacv1.ClusterRoleBinding) - -func getTestClusterRoleBinding(opts ...clusterRoleBindingOpt) *rbacv1.ClusterRoleBinding { - desiredClusterRoleBinding := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - } - - for _, opt := range opts { - opt(desiredClusterRoleBinding) - } - return desiredClusterRoleBinding -} - func TestRequestClusterRoleBinding(t *testing.T) { tests := []struct { name string @@ -42,24 +27,23 @@ func TestRequestClusterRoleBinding(t *testing.T) { }{ { - name: "request clusterrolebinding, custom name, labels, annotations", + name: "request clusterrolebinding", crbReq: ClusterRoleBindingRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - RoleRef: testRoleRef, - Subjects: testSubjects, + RoleRef: test.MakeTestRoleRef(test.TestName), + Subjects: test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}), }, - desiredCrb: getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { - crb.Name = testName - crb.Labels = testKVP - crb.Annotations = testKVP - crb.RoleRef = testRoleRef - crb.Subjects = testSubjects - + desiredCrb: test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { + crb.Name = test.TestName + crb.Labels = test.TestKVP + crb.Annotations = test.TestKVP + crb.RoleRef = test.MakeTestRoleRef(test.TestName) + crb.Subjects = test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}) }), }, } @@ -75,21 +59,21 @@ func TestRequestClusterRoleBinding(t *testing.T) { func TestCreateClusterRoleBinding(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredClusterRoleBinding := getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { + desiredClusterRoleBinding := test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { crb.TypeMeta = metav1.TypeMeta{ Kind: "ClusterRoleBinding", APIVersion: "rbac.authorization.k8s.io/v1", } - crb.Name = testName - crb.Labels = testKVP - crb.Annotations = testKVP + crb.Name = test.TestName + crb.Labels = test.TestKVP + crb.Annotations = test.TestKVP }) err := CreateClusterRoleBinding(desiredClusterRoleBinding, testClient) assert.NoError(t, err) createdClusterRoleBinding := &rbacv1.ClusterRoleBinding{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, createdClusterRoleBinding) assert.NoError(t, err) @@ -97,29 +81,29 @@ func TestCreateClusterRoleBinding(t *testing.T) { } func TestGetClusterRoleBinding(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { - crb.Name = testName + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { + crb.Name = test.TestName })).Build() - _, err := GetClusterRoleBinding(testName, testClient) + _, err := GetClusterRoleBinding(test.TestName, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetClusterRoleBinding(testName, testClient) + _, err = GetClusterRoleBinding(test.TestName, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListClusterRoleBindings(t *testing.T) { - crb1 := getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { + crb1 := test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { crb.Name = "crb-1" crb.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - crb2 := getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { + crb2 := test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { crb.Name = "crb-2" }) - crb3 := getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { + crb3 := test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { crb.Name = "crb-3" crb.Labels[common.AppK8sKeyComponent] = "new-component-2" }) @@ -151,14 +135,14 @@ func TestListClusterRoleBindings(t *testing.T) { } func TestUpdateClusterRoleBinding(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { - crb.Name = testName + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { + crb.Name = test.TestName })).Build() - desiredClusterRoleBinding := getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { - crb.Name = testName - crb.RoleRef = testRoleRef - crb.Subjects = testSubjects + desiredClusterRoleBinding := test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { + crb.Name = test.TestName + crb.RoleRef = test.MakeTestRoleRef(test.TestName) + crb.Subjects = test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}) }) err := UpdateClusterRoleBinding(desiredClusterRoleBinding, testClient) @@ -166,33 +150,33 @@ func TestUpdateClusterRoleBinding(t *testing.T) { existingClusterRoleBinding := &rbacv1.ClusterRoleBinding{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, existingClusterRoleBinding) assert.NoError(t, err) assert.Equal(t, desiredClusterRoleBinding.Subjects, existingClusterRoleBinding.Subjects) testClient = fake.NewClientBuilder().Build() - existingClusterRoleBinding = getTestClusterRoleBinding(func(crb *rbacv1.ClusterRoleBinding) { - crb.Name = testName + existingClusterRoleBinding = test.MakeTestClusterRoleBinding(nil, func(crb *rbacv1.ClusterRoleBinding) { + crb.Name = test.TestName }) err = UpdateClusterRoleBinding(existingClusterRoleBinding, testClient) assert.Error(t, err) } func TestDeleteClusterRoleBinding(t *testing.T) { - testClusterRoleBinding := getTestClusterRoleBinding(func(rb *rbacv1.ClusterRoleBinding) { - rb.Name = testName + testClusterRoleBinding := test.MakeTestClusterRoleBinding(nil, func(rb *rbacv1.ClusterRoleBinding) { + rb.Name = test.TestName }) testClient := fake.NewClientBuilder().WithObjects(testClusterRoleBinding).Build() - err := DeleteClusterRoleBinding(testName, testClient) + err := DeleteClusterRoleBinding(test.TestName, testClient) assert.NoError(t, err) existingClusterRoleBinding := &rbacv1.ClusterRoleBinding{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Name: testName, + Name: test.TestName, }, existingClusterRoleBinding) assert.Error(t, err) diff --git a/pkg/permissions/permissions_test.go b/pkg/permissions/permissions_test.go index f3b920f57..039cc05c4 100644 --- a/pkg/permissions/permissions_test.go +++ b/pkg/permissions/permissions_test.go @@ -7,31 +7,11 @@ import ( cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/tests/test" ) // common test variables used across permissions tests var ( - testName = "test-name" - testInstance = "test-instance" - testInstanceNamespace = "test-instance-ns" - testNamespace = "test-ns" - testComponent = "test-component" - testKey = "test-key" - testVal = "test-value" - - testRules = []rbacv1.PolicyRule{ - { - APIGroups: []string{ - "", - }, - Resources: []string{ - "pods", - }, - Verbs: []string{ - "create", - }, - }, - } testRulesMutated = []rbacv1.PolicyRule{ { APIGroups: []string{ @@ -45,31 +25,12 @@ var ( }, }, } - testRoleRef = rbacv1.RoleRef{ - Kind: "Role", - Name: testName, - APIGroup: "rbac.authorization.k8s.io", - } - testSubjects = []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: testName, - Namespace: testNamespace, - }, - } - testKVP = map[string]string{ - testKey: testVal, - } ) -func testMutationFuncFailed(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { - return errors.New("test-mutation-error") -} - func testMutationFuncSuccessful(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { switch obj := resource.(type) { case *rbacv1.Role: - if obj.Namespace == testNamespace { + if obj.Namespace == test.TestNamespace { obj.Rules = testRulesMutated return nil } diff --git a/pkg/permissions/role_test.go b/pkg/permissions/role_test.go index 1e02c7dab..e197fed34 100644 --- a/pkg/permissions/role_test.go +++ b/pkg/permissions/role_test.go @@ -17,24 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type roleOpt func(*rbacv1.Role) - -func getTestRole(opts ...roleOpt) *rbacv1.Role { - desiredRole := &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - } - - for _, opt := range opts { - opt(desiredRole) - } - return desiredRole -} - func TestRequestRole(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -46,22 +31,22 @@ func TestRequestRole(t *testing.T) { wantErr bool }{ { - name: "request role, no mutation, custom name, labels, annotations", + name: "request role", rolReq: RoleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Rules: testRules, + Rules: test.TestRules, }, - desiredRole: getTestRole(func(r *rbacv1.Role) { - r.Name = testName - r.Namespace = testNamespace - r.Labels = testKVP - r.Annotations = testKVP - r.Rules = testRules + desiredRole: test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName + r.Namespace = test.TestNamespace + r.Labels = test.TestKVP + r.Annotations = test.TestKVP + r.Rules = test.TestRules }), wantErr: false, }, @@ -69,22 +54,22 @@ func TestRequestRole(t *testing.T) { name: "request role, successful mutation", rolReq: RoleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Rules: testRules, + Rules: test.TestRules, Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, - desiredRole: getTestRole(func(r *rbacv1.Role) { - r.Name = testName - r.Namespace = testNamespace - r.Labels = testKVP - r.Annotations = testKVP + desiredRole: test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName + r.Namespace = test.TestNamespace + r.Labels = test.TestKVP + r.Annotations = test.TestKVP r.Rules = testRulesMutated }), wantErr: false, @@ -93,22 +78,22 @@ func TestRequestRole(t *testing.T) { name: "request role, failed mutation", rolReq: RoleRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Rules: testRules, + Rules: test.TestRules, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredRole: getTestRole(func(r *rbacv1.Role) { - r.Name = testName - r.Namespace = testNamespace - r.Labels = testKVP - r.Annotations = testKVP + desiredRole: test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName + r.Namespace = test.TestNamespace + r.Labels = test.TestKVP + r.Annotations = test.TestKVP r.Rules = testRulesMutated }), @@ -135,23 +120,23 @@ func TestRequestRole(t *testing.T) { func TestCreateRole(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredRole := getTestRole(func(r *rbacv1.Role) { + desiredRole := test.MakeTestRole(nil, func(r *rbacv1.Role) { r.TypeMeta = metav1.TypeMeta{ Kind: "Role", APIVersion: "rbac.authorization.k8s.io/v1", } - r.Name = testName - r.Namespace = testNamespace - r.Labels = testKVP - r.Annotations = testKVP + r.Name = test.TestName + r.Namespace = test.TestNamespace + r.Labels = test.TestKVP + r.Annotations = test.TestKVP }) err := CreateRole(desiredRole, testClient) assert.NoError(t, err) createdRole := &rbacv1.Role{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdRole) assert.NoError(t, err) @@ -159,35 +144,35 @@ func TestCreateRole(t *testing.T) { } func TestGetRole(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestRole(func(r *rbacv1.Role) { - r.Name = testName - r.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName + r.Namespace = test.TestNamespace })).Build() - _, err := GetRole(testName, testNamespace, testClient) + _, err := GetRole(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetRole(testName, testNamespace, testClient) + _, err = GetRole(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListRoles(t *testing.T) { - role1 := getTestRole(func(r *rbacv1.Role) { + role1 := test.MakeTestRole(nil, func(r *rbacv1.Role) { r.Name = "role-1" r.Labels[common.AppK8sKeyComponent] = "new-component-1" - r.Namespace = testNamespace + r.Namespace = test.TestNamespace }) - role2 := getTestRole(func(r *rbacv1.Role) { + role2 := test.MakeTestRole(nil, func(r *rbacv1.Role) { r.Name = "role-2" - r.Namespace = testNamespace + r.Namespace = test.TestNamespace }) - role3 := getTestRole(func(r *rbacv1.Role) { + role3 := test.MakeTestRole(nil, func(r *rbacv1.Role) { r.Name = "role-3" r.Labels[common.AppK8sKeyComponent] = "new-component-2" - r.Namespace = testNamespace + r.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -204,7 +189,7 @@ func TestListRoles(t *testing.T) { desiredRoles := []string{"role-1", "role-3"} - existingRoleList, err := ListRoles(testNamespace, testClient, listOpts) + existingRoleList, err := ListRoles(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingRoles := []string{} @@ -218,51 +203,51 @@ func TestListRoles(t *testing.T) { } func TestUpdateRole(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestRole(func(r *rbacv1.Role) { - r.Name = testName - r.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName + r.Namespace = test.TestNamespace })).Build() - desiredRole := getTestRole(func(r *rbacv1.Role) { - r.Name = testName + desiredRole := test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName r.Rules = testRulesMutated - r.Namespace = testNamespace + r.Namespace = test.TestNamespace }) err := UpdateRole(desiredRole, testClient) assert.NoError(t, err) existingRole := &rbacv1.Role{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingRole) assert.NoError(t, err) assert.Equal(t, desiredRole.Rules, existingRole.Rules) testClient = fake.NewClientBuilder().Build() - existingRole = getTestRole(func(r *rbacv1.Role) { - r.Name = testName + existingRole = test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName }) err = UpdateRole(existingRole, testClient) assert.Error(t, err) } func TestDeleteRole(t *testing.T) { - testRole := getTestRole(func(r *rbacv1.Role) { - r.Name = testName - r.Namespace = testNamespace + testRole := test.MakeTestRole(nil, func(r *rbacv1.Role) { + r.Name = test.TestName + r.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testRole).Build() - err := DeleteRole(testName, testNamespace, testClient) + err := DeleteRole(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingRole := &rbacv1.Role{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingRole) assert.Error(t, err) diff --git a/pkg/permissions/rolebinding_test.go b/pkg/permissions/rolebinding_test.go index e1cd53428..6f788d298 100644 --- a/pkg/permissions/rolebinding_test.go +++ b/pkg/permissions/rolebinding_test.go @@ -17,24 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type roleBindingOpt func(*rbacv1.RoleBinding) - -func getTestRoleBinding(opts ...roleBindingOpt) *rbacv1.RoleBinding { - desiredRoleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - } - - for _, opt := range opts { - opt(desiredRoleBinding) - } - return desiredRoleBinding -} - func TestRequestRoleBinding(t *testing.T) { tests := []struct { name string @@ -45,42 +30,42 @@ func TestRequestRoleBinding(t *testing.T) { name: "request rolebinding", rbReq: RoleBindingRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - RoleRef: testRoleRef, - Subjects: testSubjects, + RoleRef: test.MakeTestRoleRef(test.TestName), + Subjects: test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}), }, - desiredRb: getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName - rb.Namespace = testNamespace - rb.Labels = testKVP - rb.Annotations = testKVP - rb.RoleRef = testRoleRef - rb.Subjects = testSubjects + desiredRb: test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName + rb.Namespace = test.TestNamespace + rb.Labels = test.TestKVP + rb.Annotations = test.TestKVP + rb.RoleRef = test.MakeTestRoleRef(test.TestName) + rb.Subjects = test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}) }), }, { name: "request rolebinding, custom name, labels, annotations", rbReq: RoleBindingRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - RoleRef: testRoleRef, - Subjects: testSubjects, + RoleRef: test.MakeTestRoleRef(test.TestName), + Subjects: test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}), }, - desiredRb: getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName - rb.Namespace = testNamespace - rb.Labels = util.MergeMaps(rb.Labels, testKVP) - rb.Annotations = util.MergeMaps(rb.Annotations, testKVP) - rb.RoleRef = testRoleRef - rb.Subjects = testSubjects + desiredRb: test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName + rb.Namespace = test.TestNamespace + rb.Labels = util.MergeMaps(rb.Labels, test.TestKVP) + rb.Annotations = util.MergeMaps(rb.Annotations, test.TestKVP) + rb.RoleRef = test.MakeTestRoleRef(test.TestName) + rb.Subjects = test.MakeTestSubjects(types.NamespacedName{Name: test.TestName, Namespace: test.TestNamespace}) }), }, } @@ -98,23 +83,23 @@ func TestRequestRoleBinding(t *testing.T) { func TestCreateRoleBinding(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredRoleBinding := getTestRoleBinding(func(rb *rbacv1.RoleBinding) { + desiredRoleBinding := test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { rb.TypeMeta = metav1.TypeMeta{ Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1", } - rb.Name = testName - rb.Namespace = testNamespace - rb.Labels = testKVP - rb.Annotations = testKVP + rb.Name = test.TestName + rb.Namespace = test.TestNamespace + rb.Labels = test.TestKVP + rb.Annotations = test.TestKVP }) err := CreateRoleBinding(desiredRoleBinding, testClient) assert.NoError(t, err) createdRoleBinding := &rbacv1.RoleBinding{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdRoleBinding) assert.NoError(t, err) @@ -122,35 +107,35 @@ func TestCreateRoleBinding(t *testing.T) { } func TestGetRoleBinding(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName - rb.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName + rb.Namespace = test.TestNamespace })).Build() - _, err := GetRoleBinding(testName, testNamespace, testClient) + _, err := GetRoleBinding(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetRoleBinding(testName, testNamespace, testClient) + _, err = GetRoleBinding(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListRoleBindings(t *testing.T) { - rb1 := getTestRoleBinding(func(rb *rbacv1.RoleBinding) { + rb1 := test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { rb.Name = "rb-1" rb.Labels[common.AppK8sKeyComponent] = "new-component-1" - rb.Namespace = testNamespace + rb.Namespace = test.TestNamespace }) - rb2 := getTestRoleBinding(func(rb *rbacv1.RoleBinding) { + rb2 := test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { rb.Name = "rb-2" - rb.Namespace = testNamespace + rb.Namespace = test.TestNamespace }) - rb3 := getTestRoleBinding(func(rb *rbacv1.RoleBinding) { + rb3 := test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { rb.Name = "rb-3" rb.Labels[common.AppK8sKeyComponent] = "new-component-2" - rb.Namespace = testNamespace + rb.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -167,7 +152,7 @@ func TestListRoleBindings(t *testing.T) { desiredRoleBindings := []string{"rb-1", "rb-3"} - existingRoleBindingList, err := ListRoleBindings(testNamespace, testClient, listOpts) + existingRoleBindingList, err := ListRoleBindings(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingRoleBindings := []string{} @@ -180,13 +165,13 @@ func TestListRoleBindings(t *testing.T) { } func TestUpdateRoleBinding(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName - rb.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName + rb.Namespace = test.TestNamespace })).Build() - desiredRoleBinding := getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName + desiredRoleBinding := test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName rb.RoleRef = rbacv1.RoleRef{ Kind: "Role", Name: "desired-role-name", @@ -196,10 +181,10 @@ func TestUpdateRoleBinding(t *testing.T) { { Kind: "ServiceAccount", Name: "new-sa", - Namespace: testNamespace, + Namespace: test.TestNamespace, }, } - rb.Namespace = testNamespace + rb.Namespace = test.TestNamespace }) @@ -208,8 +193,8 @@ func TestUpdateRoleBinding(t *testing.T) { existingRoleBinding := &rbacv1.RoleBinding{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingRoleBinding) assert.NoError(t, err) @@ -217,28 +202,28 @@ func TestUpdateRoleBinding(t *testing.T) { assert.Equal(t, desiredRoleBinding.Subjects, existingRoleBinding.Subjects) testClient = fake.NewClientBuilder().Build() - existingRoleBinding = getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName + existingRoleBinding = test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName }) err = UpdateRoleBinding(existingRoleBinding, testClient) assert.Error(t, err) } func TestDeleteRoleBinding(t *testing.T) { - testRoleBinding := getTestRoleBinding(func(rb *rbacv1.RoleBinding) { - rb.Name = testName - rb.Namespace = testNamespace + testRoleBinding := test.MakeTestRoleBinding(nil, func(rb *rbacv1.RoleBinding) { + rb.Name = test.TestName + rb.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testRoleBinding).Build() - err := DeleteRoleBinding(testName, testNamespace, testClient) + err := DeleteRoleBinding(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingRoleBinding := &rbacv1.RoleBinding{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingRoleBinding) assert.Error(t, err) diff --git a/pkg/permissions/serviceaccount_test.go b/pkg/permissions/serviceaccount_test.go index b36b74f61..f2b3cd9c6 100644 --- a/pkg/permissions/serviceaccount_test.go +++ b/pkg/permissions/serviceaccount_test.go @@ -17,25 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/util" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type serviceAccountOpt func(*corev1.ServiceAccount) - -func getTestServiceAccount(opts ...serviceAccountOpt) *corev1.ServiceAccount { - desiredServiceAccount := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - } - - for _, opt := range opts { - opt(desiredServiceAccount) - } - - return desiredServiceAccount -} - func TestRequestServiceAccount(t *testing.T) { tests := []struct { name string @@ -43,20 +27,20 @@ func TestRequestServiceAccount(t *testing.T) { desiredSa *corev1.ServiceAccount }{ { - name: "request service account, custom name, labels, annotations", + name: "request service account", saReq: ServiceAccountRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, }, - desiredSa: getTestServiceAccount(func(sa *corev1.ServiceAccount) { - sa.Name = testName - sa.Namespace = testNamespace - sa.Labels = util.MergeMaps(sa.Labels, testKVP) - sa.Annotations = util.MergeMaps(sa.Annotations, testKVP) + desiredSa: test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = test.TestName + sa.Namespace = test.TestNamespace + sa.Labels = util.MergeMaps(sa.Labels, test.TestKVP) + sa.Annotations = util.MergeMaps(sa.Annotations, test.TestKVP) }), }, } @@ -72,23 +56,23 @@ func TestRequestServiceAccount(t *testing.T) { func TestCreateServiceAccount(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredServiceAccount := getTestServiceAccount(func(sa *corev1.ServiceAccount) { + desiredServiceAccount := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { sa.TypeMeta = metav1.TypeMeta{ Kind: "ServiceAccount", APIVersion: "v1", } - sa.Name = testName - sa.Namespace = testNamespace - sa.Labels = testKVP - sa.Annotations = testKVP + sa.Name = test.TestName + sa.Namespace = test.TestNamespace + sa.Labels = test.TestKVP + sa.Annotations = test.TestKVP }) err := CreateServiceAccount(desiredServiceAccount, testClient) assert.NoError(t, err) createdServiceAccount := &corev1.ServiceAccount{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdServiceAccount) assert.NoError(t, err) @@ -96,35 +80,35 @@ func TestCreateServiceAccount(t *testing.T) { } func TestGetServiceAccount(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestServiceAccount(func(sa *corev1.ServiceAccount) { - sa.Name = testName - sa.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = test.TestName + sa.Namespace = test.TestNamespace })).Build() - _, err := GetServiceAccount(testName, testNamespace, testClient) + _, err := GetServiceAccount(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetServiceAccount(testName, testNamespace, testClient) + _, err = GetServiceAccount(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListServiceAccounts(t *testing.T) { - sa1 := getTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa1 := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { sa.Name = "sa-1" sa.Labels[common.AppK8sKeyComponent] = "new-component-1" - sa.Namespace = testNamespace + sa.Namespace = test.TestNamespace }) - sa2 := getTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa2 := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { sa.Name = "sa-2" - sa.Namespace = testNamespace + sa.Namespace = test.TestNamespace }) - sa3 := getTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa3 := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { sa.Name = "sa-3" sa.Labels[common.AppK8sKeyComponent] = "new-component-2" - sa.Namespace = testNamespace + sa.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -141,7 +125,7 @@ func TestListServiceAccounts(t *testing.T) { desiredServiceAccounts := []string{"sa-1", "sa-3"} - existingServiceAccountList, err := ListServiceAccounts(testNamespace, testClient, listOpts) + existingServiceAccountList, err := ListServiceAccounts(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingServiceAccounts := []string{} @@ -154,19 +138,19 @@ func TestListServiceAccounts(t *testing.T) { } func TestUpdateServiceAccount(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestServiceAccount(func(sa *corev1.ServiceAccount) { - sa.Name = testName - sa.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = test.TestName + sa.Namespace = test.TestNamespace })).Build() - desiredServiceAccount := getTestServiceAccount(func(sa *corev1.ServiceAccount) { - sa.Name = testName + desiredServiceAccount := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = test.TestName sa.ImagePullSecrets = []corev1.LocalObjectReference{ { Name: "new-secret", }, } - sa.Namespace = testNamespace + sa.Namespace = test.TestNamespace }) err := UpdateServiceAccount(desiredServiceAccount, testClient) @@ -174,8 +158,8 @@ func TestUpdateServiceAccount(t *testing.T) { existingServiceAccount := &corev1.ServiceAccount{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingServiceAccount) assert.NoError(t, err) @@ -183,28 +167,28 @@ func TestUpdateServiceAccount(t *testing.T) { assert.Equal(t, desiredServiceAccount.AutomountServiceAccountToken, existingServiceAccount.AutomountServiceAccountToken) testClient = fake.NewClientBuilder().Build() - existingServiceAccount = getTestServiceAccount(func(sa *corev1.ServiceAccount) { - sa.Name = testName + existingServiceAccount = test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = test.TestName }) err = UpdateServiceAccount(existingServiceAccount, testClient) assert.Error(t, err) } func TestDeleteServiceAccount(t *testing.T) { - testServiceAccount := getTestServiceAccount(func(sa *corev1.ServiceAccount) { - sa.Name = testName - sa.Namespace = testNamespace + testServiceAccount := test.MakeTestServiceAccount(func(sa *corev1.ServiceAccount) { + sa.Name = test.TestName + sa.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testServiceAccount).Build() - err := DeleteServiceAccount(testName, testNamespace, testClient) + err := DeleteServiceAccount(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingServiceAccount := &corev1.ServiceAccount{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingServiceAccount) assert.Error(t, err) diff --git a/pkg/resource/helper.go b/pkg/resource/helper.go new file mode 100644 index 000000000..aa9fb3275 --- /dev/null +++ b/pkg/resource/helper.go @@ -0,0 +1,64 @@ +package resource + +import ( + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/pkg/util" + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + appsv1 "github.com/openshift/api/apps/v1" + configv1 "github.com/openshift/api/config/v1" + oauthv1 "github.com/openshift/api/oauth/v1" + routev1 "github.com/openshift/api/route/v1" + templatev1 "github.com/openshift/api/template/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ConvertToRuntimeObjects converts a given set of client.Object resources into a slice of runtime.Objects +func ConvertToRuntimeObjects(objs ...client.Object) ([]runtime.Object, error) { + runtimeObjs := []runtime.Object{} + var conversionErr util.MultiError + + for _, obj := range objs { + // Get the GVK (GroupVersionKind) of the client.Object + gvk := obj.GetObjectKind().GroupVersionKind() + + sch := GetScheme() + + // Create a new empty runtime.Object with the same GVK + newRuntimeObject, err := sch.New(gvk) + conversionErr.Append(err) + + // DeepCopy the client.Object into the runtime.Object + err = sch.Convert(obj, newRuntimeObject, nil) + conversionErr.Append(err) + + runtimeObjs = append(runtimeObjs, newRuntimeObject) + } + + return runtimeObjs, conversionErr.ErrOrNil() +} + +func GetScheme() *runtime.Scheme { + sOpts := func(s *runtime.Scheme) { + argoproj.AddToScheme(s) + monitoringv1.AddToScheme(s) + routev1.Install(s) + configv1.Install(s) + templatev1.Install(s) + appsv1.Install(s) + oauthv1.Install(s) + } + return MakeScheme(sOpts) +} + +type SchemeOpt func(*runtime.Scheme) + +func MakeScheme(sOpts ...SchemeOpt) *runtime.Scheme { + s := scheme.Scheme + for _, opt := range sOpts { + opt(s) + } + + return s +} diff --git a/pkg/util/string.go b/pkg/util/string.go index f456e8d69..cc50bed6d 100644 --- a/pkg/util/string.go +++ b/pkg/util/string.go @@ -68,3 +68,8 @@ func GenerateRandomString(s int) (string, error) { } return base64.URLEncoding.EncodeToString(b), nil } + +// StringPtr returns a pointer to provided string value +func StringPtr(val string) *string { + return &val +} diff --git a/pkg/workloads/configmap_test.go b/pkg/workloads/configmap_test.go index e757cf6e1..fd5ac487d 100644 --- a/pkg/workloads/configmap_test.go +++ b/pkg/workloads/configmap_test.go @@ -17,100 +17,84 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type configMapOpt func(*corev1.ConfigMap) - -func getTestConfigMap(opts ...configMapOpt) *corev1.ConfigMap { - desiredConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - Data: make(map[string]string), - } - - for _, opt := range opts { - opt(desiredConfigMap) - } - return desiredConfigMap -} - func TestRequestConfigMap(t *testing.T) { testClient := fake.NewClientBuilder().Build() tests := []struct { name string - deployReq ConfigMapRequest + cmReq ConfigMapRequest desiredConfigMap *corev1.ConfigMap wantErr bool }{ { - name: "request configMap, no mutation, custom name, labels, annotations", - deployReq: ConfigMapRequest{ + name: "request configMap", + cmReq: ConfigMapRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Data: testKVP, + Data: test.TestKVP, }, - desiredConfigMap: getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testName - cm.Namespace = testNamespace - cm.Labels = testKVP - cm.Annotations = testKVP - cm.Data = testKVP + desiredConfigMap: test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestName + cm.Namespace = test.TestNamespace + cm.Labels = test.TestKVP + cm.Annotations = test.TestKVP + cm.Data = test.TestKVP }), wantErr: false, }, { name: "request configMap, successful mutation", - deployReq: ConfigMapRequest{ + cmReq: ConfigMapRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Data: testKVP, + Data: test.TestKVP, Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, - desiredConfigMap: getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testNameMutated - cm.Namespace = testNamespace - cm.Labels = testKVP - cm.Annotations = testKVP - cm.Data = testKVPMutated + desiredConfigMap: test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestNameMutated + cm.Namespace = test.TestNamespace + cm.Labels = test.TestKVP + cm.Annotations = test.TestKVP + cm.Data = test.TestKVPMutated }), wantErr: false, }, { name: "request configMap, failed mutation", - deployReq: ConfigMapRequest{ + cmReq: ConfigMapRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - Data: testKVP, + Data: test.TestKVP, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredConfigMap: getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testName - cm.Namespace = testNamespace - cm.Labels = testKVP - cm.Annotations = testKVP - cm.Data = testKVP + desiredConfigMap: test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestName + cm.Namespace = test.TestNamespace + cm.Labels = test.TestKVP + cm.Annotations = test.TestKVP + cm.Data = test.TestKVP }), wantErr: true, }, @@ -118,7 +102,7 @@ func TestRequestConfigMap(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - gotConfigMap, err := RequestConfigMap(test.deployReq) + gotConfigMap, err := RequestConfigMap(test.cmReq) if !test.wantErr { assert.NoError(t, err) @@ -135,24 +119,24 @@ func TestRequestConfigMap(t *testing.T) { func TestCreateConfigMap(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredConfigMap := getTestConfigMap(func(cm *corev1.ConfigMap) { + desiredConfigMap := test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { cm.TypeMeta = metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: "v1", } - cm.Name = testName - cm.Namespace = testNamespace - cm.Labels = testKVP - cm.Annotations = testKVP - cm.Data = testKVP + cm.Name = test.TestName + cm.Namespace = test.TestNamespace + cm.Labels = test.TestKVP + cm.Annotations = test.TestKVP + cm.Data = test.TestKVP }) err := CreateConfigMap(desiredConfigMap, testClient) assert.NoError(t, err) createdConfigMap := &corev1.ConfigMap{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdConfigMap) assert.NoError(t, err) @@ -160,40 +144,40 @@ func TestCreateConfigMap(t *testing.T) { } func TestGetConfigMap(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testName - cm.Namespace = testNamespace - cm.Data = testKVP + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestName + cm.Namespace = test.TestNamespace + cm.Data = test.TestKVP })).Build() - _, err := GetConfigMap(testName, testNamespace, testClient) + _, err := GetConfigMap(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetConfigMap(testName, testNamespace, testClient) + _, err = GetConfigMap(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListConfigMaps(t *testing.T) { - configMap1 := getTestConfigMap(func(cm *corev1.ConfigMap) { + configMap1 := test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { cm.Name = "configMap-1" - cm.Namespace = testNamespace + cm.Namespace = test.TestNamespace cm.Labels[common.AppK8sKeyComponent] = "new-component-1" - cm.Data = testKVP + cm.Data = test.TestKVP }) - configMap2 := getTestConfigMap(func(cm *corev1.ConfigMap) { + configMap2 := test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { cm.Name = "configMap-2" - cm.Namespace = testNamespace + cm.Namespace = test.TestNamespace }) - configMap3 := getTestConfigMap(func(cm *corev1.ConfigMap) { + configMap3 := test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { cm.Name = "configMap-3" cm.Labels[common.AppK8sKeyComponent] = "new-component-2" - cm.Data = testKVP - cm.Namespace = testNamespace + cm.Data = test.TestKVP + cm.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -210,7 +194,7 @@ func TestListConfigMaps(t *testing.T) { desiredConfigMaps := []string{"configMap-1", "configMap-3"} - existingConfigMapList, err := ListConfigMaps(testNamespace, testClient, listOpts) + existingConfigMapList, err := ListConfigMaps(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingConfigMaps := []string{} @@ -223,15 +207,15 @@ func TestListConfigMaps(t *testing.T) { } func TestUpdateConfigMap(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testName - cm.Namespace = testNamespace - cm.Data = testKVP + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestName + cm.Namespace = test.TestNamespace + cm.Data = test.TestKVP })).Build() - desiredConfigMap := getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testName - cm.Namespace = testNamespace + desiredConfigMap := test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestName + cm.Namespace = test.TestNamespace cm.Data = map[string]string{ "application.instanceLabelKey": "mycompany.com/appname", "admin.enabled": "true", @@ -242,17 +226,17 @@ func TestUpdateConfigMap(t *testing.T) { existingConfigMap := &corev1.ConfigMap{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingConfigMap) assert.NoError(t, err) assert.Equal(t, desiredConfigMap.Data, existingConfigMap.Data) testClient = fake.NewClientBuilder().Build() - existingConfigMap = getTestConfigMap(func(cm *corev1.ConfigMap) { - cm.Name = testName - cm.Namespace = testNamespace + existingConfigMap = test.MakeTestConfigMap(nil, func(cm *corev1.ConfigMap) { + cm.Name = test.TestName + cm.Namespace = test.TestNamespace cm.Data = nil }) err = UpdateConfigMap(existingConfigMap, testClient) @@ -260,20 +244,20 @@ func TestUpdateConfigMap(t *testing.T) { } func TestDeleteConfigMap(t *testing.T) { - testConfigMap := getTestConfigMap(func(configMap *corev1.ConfigMap) { - configMap.Name = testName - configMap.Namespace = testNamespace + testConfigMap := test.MakeTestConfigMap(nil, func(configMap *corev1.ConfigMap) { + configMap.Name = test.TestName + configMap.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testConfigMap).Build() - err := DeleteConfigMap(testName, testNamespace, testClient) + err := DeleteConfigMap(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingConfigMap := &corev1.ConfigMap{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingConfigMap) assert.Error(t, err) diff --git a/pkg/workloads/deployment_test.go b/pkg/workloads/deployment_test.go index 9ea25bc46..349bd638b 100644 --- a/pkg/workloads/deployment_test.go +++ b/pkg/workloads/deployment_test.go @@ -17,27 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type deploymentOpt func(*appsv1.Deployment) - -func getTestDeployment(opts ...deploymentOpt) *appsv1.Deployment { - desiredDeployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{}, - }, - } - - for _, opt := range opts { - opt(desiredDeployment) - } - return desiredDeployment -} - func TestRequestDeployment(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -49,26 +31,26 @@ func TestRequestDeployment(t *testing.T) { wantErr bool }{ { - name: "request deployment, no mutation, custom name, labels, annotations", + name: "request deployment", deployReq: DeploymentRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: testKVP, + MatchLabels: test.TestKVP, }, }, }, - desiredDeployment: getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testName - d.Namespace = testNamespace - d.Labels = testKVP - d.Annotations = testKVP - d.Spec.Selector.MatchLabels = testKVP + desiredDeployment: test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestName + d.Namespace = test.TestNamespace + d.Labels = test.TestKVP + d.Annotations = test.TestKVP + d.Spec.Selector.MatchLabels = test.TestKVP }), wantErr: false, }, @@ -76,14 +58,14 @@ func TestRequestDeployment(t *testing.T) { name: "request deployment, successful mutation", deployReq: DeploymentRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: testKVP, + MatchLabels: test.TestKVP, }, }, Mutations: []mutation.MutateFunc{ @@ -91,12 +73,12 @@ func TestRequestDeployment(t *testing.T) { }, Client: testClient, }, - desiredDeployment: getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testNameMutated - d.Namespace = testNamespace - d.Labels = testKVP - d.Annotations = testKVP - d.Spec.Selector.MatchLabels = testKVP + desiredDeployment: test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestNameMutated + d.Namespace = test.TestNamespace + d.Labels = test.TestKVP + d.Annotations = test.TestKVP + d.Spec.Selector.MatchLabels = test.TestKVP d.Spec.Replicas = &testReplicasMutated }), wantErr: false, @@ -105,27 +87,27 @@ func TestRequestDeployment(t *testing.T) { name: "request deployment, failed mutation", deployReq: DeploymentRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: testKVP, + MatchLabels: test.TestKVP, }, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredDeployment: getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testNameMutated - d.Namespace = testNamespace - d.Labels = testKVP - d.Annotations = testKVP - d.Spec.Selector.MatchLabels = testKVP + desiredDeployment: test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestNameMutated + d.Namespace = test.TestNamespace + d.Labels = test.TestKVP + d.Annotations = test.TestKVP + d.Spec.Selector.MatchLabels = test.TestKVP }), wantErr: true, }, @@ -150,23 +132,23 @@ func TestRequestDeployment(t *testing.T) { func TestCreateDeployment(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredDeployment := getTestDeployment(func(d *appsv1.Deployment) { + desiredDeployment := test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { d.TypeMeta = metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", } - d.Name = testName - d.Namespace = testNamespace - d.Labels = testKVP - d.Annotations = testKVP + d.Name = test.TestName + d.Namespace = test.TestNamespace + d.Labels = test.TestKVP + d.Annotations = test.TestKVP }) err := CreateDeployment(desiredDeployment, testClient) assert.NoError(t, err) createdDeployment := &appsv1.Deployment{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdDeployment) assert.NoError(t, err) @@ -174,38 +156,38 @@ func TestCreateDeployment(t *testing.T) { } func TestGetDeployment(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testName - d.Namespace = testNamespace - d.Labels = testKVP - d.Annotations = testKVP + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestName + d.Namespace = test.TestNamespace + d.Labels = test.TestKVP + d.Annotations = test.TestKVP })).Build() - _, err := GetDeployment(testName, testNamespace, testClient) + _, err := GetDeployment(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetDeployment(testName, testNamespace, testClient) + _, err = GetDeployment(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListDeployments(t *testing.T) { - deployment1 := getTestDeployment(func(d *appsv1.Deployment) { + deployment1 := test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { d.Name = "deployment-1" - d.Namespace = testNamespace + d.Namespace = test.TestNamespace d.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - deployment2 := getTestDeployment(func(d *appsv1.Deployment) { + deployment2 := test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { d.Name = "deployment-2" - d.Namespace = testNamespace + d.Namespace = test.TestNamespace }) - deployment3 := getTestDeployment(func(d *appsv1.Deployment) { + deployment3 := test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { d.Name = "deployment-3" d.Labels[common.AppK8sKeyComponent] = "new-component-2" - d.Namespace = testNamespace + d.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -222,7 +204,7 @@ func TestListDeployments(t *testing.T) { desiredDeployments := []string{"deployment-1", "deployment-3"} - existingDeploymentList, err := ListDeployments(testNamespace, testClient, listOpts) + existingDeploymentList, err := ListDeployments(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingDeployments := []string{} @@ -235,14 +217,14 @@ func TestListDeployments(t *testing.T) { } func TestUpdateDeployment(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testName - d.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestName + d.Namespace = test.TestNamespace })).Build() - desiredDeployment := getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testName - d.Namespace = testNamespace + desiredDeployment := test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestName + d.Namespace = test.TestNamespace d.Labels = map[string]string{ "control-plane": "argocd-operator", } @@ -252,37 +234,37 @@ func TestUpdateDeployment(t *testing.T) { existingDeployment := &appsv1.Deployment{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingDeployment) assert.NoError(t, err) assert.Equal(t, desiredDeployment.Labels, existingDeployment.Labels) testClient = fake.NewClientBuilder().Build() - existingDeployment = getTestDeployment(func(d *appsv1.Deployment) { - d.Name = testName - d.Namespace = testNamespace + existingDeployment = test.MakeTestDeployment(nil, func(d *appsv1.Deployment) { + d.Name = test.TestName + d.Namespace = test.TestNamespace }) err = UpdateDeployment(existingDeployment, testClient) assert.Error(t, err) } func TestDeleteDeployment(t *testing.T) { - testDeployment := getTestDeployment(func(deployment *appsv1.Deployment) { - deployment.Name = testName - deployment.Namespace = testNamespace + testDeployment := test.MakeTestDeployment(nil, func(deployment *appsv1.Deployment) { + deployment.Name = test.TestName + deployment.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testDeployment).Build() - err := DeleteDeployment(testName, testNamespace, testClient) + err := DeleteDeployment(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingDeployment := &appsv1.Deployment{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingDeployment) assert.Error(t, err) diff --git a/pkg/workloads/hpa_test.go b/pkg/workloads/hpa_test.go index f4e21e12d..74a680018 100644 --- a/pkg/workloads/hpa_test.go +++ b/pkg/workloads/hpa_test.go @@ -17,25 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type horizontalPodAutoscalerOpt func(*autoscaling.HorizontalPodAutoscaler) - -func getTestHorizontalPodAutoscaler(opts ...horizontalPodAutoscalerOpt) *autoscaling.HorizontalPodAutoscaler { - desiredHorizontalPodAutoscaler := &autoscaling.HorizontalPodAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - Spec: autoscaling.HorizontalPodAutoscalerSpec{}, - } - - for _, opt := range opts { - opt(desiredHorizontalPodAutoscaler) - } - return desiredHorizontalPodAutoscaler -} - func TestRequestHorizontalPodAutoscaler(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -47,23 +31,23 @@ func TestRequestHorizontalPodAutoscaler(t *testing.T) { wantErr bool }{ { - name: "request horizontalPodAutoscaler, no mutation, custom name, labels, annotations", + name: "request horizontalPodAutoscaler", hpaReq: HorizontalPodAutoscalerRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: autoscaling.HorizontalPodAutoscalerSpec{ MaxReplicas: testReplicasMutated, }, }, - desiredHorizontalPodAutoscaler: getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace - hpa.Labels = testKVP - hpa.Annotations = testKVP + desiredHorizontalPodAutoscaler: test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace + hpa.Labels = test.TestKVP + hpa.Annotations = test.TestKVP hpa.Spec.MaxReplicas = testReplicasMutated }), wantErr: false, @@ -72,10 +56,10 @@ func TestRequestHorizontalPodAutoscaler(t *testing.T) { name: "request horizontalPodAutoscaler, successful mutation", hpaReq: HorizontalPodAutoscalerRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: autoscaling.HorizontalPodAutoscalerSpec{ MaxReplicas: testReplicasMutated, @@ -85,11 +69,11 @@ func TestRequestHorizontalPodAutoscaler(t *testing.T) { }, Client: testClient, }, - desiredHorizontalPodAutoscaler: getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testNameMutated - hpa.Namespace = testNamespace - hpa.Labels = testKVP - hpa.Annotations = testKVP + desiredHorizontalPodAutoscaler: test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestNameMutated + hpa.Namespace = test.TestNamespace + hpa.Labels = test.TestKVP + hpa.Annotations = test.TestKVP hpa.Spec.MaxReplicas = testReplicasMutated }), wantErr: false, @@ -98,24 +82,24 @@ func TestRequestHorizontalPodAutoscaler(t *testing.T) { name: "request horizontalPodAutoscaler, failed mutation", hpaReq: HorizontalPodAutoscalerRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: autoscaling.HorizontalPodAutoscalerSpec{ MaxReplicas: testReplicasMutated, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredHorizontalPodAutoscaler: getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace - hpa.Labels = testKVP - hpa.Annotations = testKVP + desiredHorizontalPodAutoscaler: test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace + hpa.Labels = test.TestKVP + hpa.Annotations = test.TestKVP hpa.Spec.MaxReplicas = testReplicasMutated }), wantErr: true, @@ -141,23 +125,23 @@ func TestRequestHorizontalPodAutoscaler(t *testing.T) { func TestCreateHorizontalPodAutoscaler(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredHorizontalPodAutoscaler := getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { + desiredHorizontalPodAutoscaler := test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { hpa.TypeMeta = metav1.TypeMeta{ Kind: "HorizontalPodAutoscaler", APIVersion: "autoscaling/v1", } - hpa.Name = testName - hpa.Namespace = testNamespace - hpa.Labels = testKVP - hpa.Annotations = testKVP + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace + hpa.Labels = test.TestKVP + hpa.Annotations = test.TestKVP }) err := CreateHorizontalPodAutoscaler(desiredHorizontalPodAutoscaler, testClient) assert.NoError(t, err) createdHorizontalPodAutoscaler := &autoscaling.HorizontalPodAutoscaler{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdHorizontalPodAutoscaler) assert.NoError(t, err) @@ -165,35 +149,35 @@ func TestCreateHorizontalPodAutoscaler(t *testing.T) { } func TestGetHorizontalPodAutoscaler(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace })).Build() - _, err := GetHorizontalPodAutoscaler(testName, testNamespace, testClient) + _, err := GetHorizontalPodAutoscaler(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetHorizontalPodAutoscaler(testName, testNamespace, testClient) + _, err = GetHorizontalPodAutoscaler(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListHorizontalPodAutoscalers(t *testing.T) { - horizontalPodAutoscaler1 := getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { + horizontalPodAutoscaler1 := test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { hpa.Name = "horizontalPodAutoscaler-1" - hpa.Namespace = testNamespace + hpa.Namespace = test.TestNamespace hpa.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - horizontalPodAutoscaler2 := getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { + horizontalPodAutoscaler2 := test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { hpa.Name = "horizontalPodAutoscaler-2" - hpa.Namespace = testNamespace + hpa.Namespace = test.TestNamespace }) - horizontalPodAutoscaler3 := getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { + horizontalPodAutoscaler3 := test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { hpa.Name = "horizontalPodAutoscaler-3" hpa.Labels[common.AppK8sKeyComponent] = "new-component-2" - hpa.Namespace = testNamespace + hpa.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -210,7 +194,7 @@ func TestListHorizontalPodAutoscalers(t *testing.T) { desiredHorizontalPodAutoscalers := []string{"horizontalPodAutoscaler-1", "horizontalPodAutoscaler-3"} - existingHorizontalPodAutoscalerList, err := ListHorizontalPodAutoscalers(testNamespace, testClient, listOpts) + existingHorizontalPodAutoscalerList, err := ListHorizontalPodAutoscalers(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingHorizontalPodAutoscalers := []string{} @@ -227,14 +211,14 @@ func TestUpdateHorizontalPodAutoscaler(t *testing.T) { maxReplicas int32 = 3 minReplicas int32 = 1 ) - testClient := fake.NewClientBuilder().WithObjects(getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace })).Build() - desiredHorizontalPodAutoscaler := getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace + desiredHorizontalPodAutoscaler := test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace hpa.Spec.MinReplicas = &minReplicas hpa.Spec.MaxReplicas = maxReplicas }) @@ -243,37 +227,37 @@ func TestUpdateHorizontalPodAutoscaler(t *testing.T) { existingHorizontalPodAutoscaler := &autoscaling.HorizontalPodAutoscaler{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingHorizontalPodAutoscaler) assert.NoError(t, err) assert.Equal(t, desiredHorizontalPodAutoscaler.Spec, existingHorizontalPodAutoscaler.Spec) testClient = fake.NewClientBuilder().Build() - existingHorizontalPodAutoscaler = getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace + existingHorizontalPodAutoscaler = test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace }) err = UpdateHorizontalPodAutoscaler(existingHorizontalPodAutoscaler, testClient) assert.Error(t, err) } func TestDeleteHorizontalPodAutoscaler(t *testing.T) { - testHPA := getTestHorizontalPodAutoscaler(func(hpa *autoscaling.HorizontalPodAutoscaler) { - hpa.Name = testName - hpa.Namespace = testNamespace + testHPA := test.MakeTestHPA(nil, func(hpa *autoscaling.HorizontalPodAutoscaler) { + hpa.Name = test.TestName + hpa.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testHPA).Build() - err := DeleteHorizontalPodAutoscaler(testName, testNamespace, testClient) + err := DeleteHorizontalPodAutoscaler(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingHPA := &autoscaling.HorizontalPodAutoscaler{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingHPA) assert.Error(t, err) diff --git a/pkg/workloads/secret_test.go b/pkg/workloads/secret_test.go index b1ebfd0fc..74c68cf86 100644 --- a/pkg/workloads/secret_test.go +++ b/pkg/workloads/secret_test.go @@ -17,25 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type secretOpt func(*corev1.Secret) - -func getTestSecret(opts ...secretOpt) *corev1.Secret { - desiredSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - StringData: map[string]string{}, - } - - for _, opt := range opts { - opt(desiredSecret) - } - return desiredSecret -} - func TestRequestSecret(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -47,23 +31,23 @@ func TestRequestSecret(t *testing.T) { wantErr bool }{ { - name: "request secret, no mutation, custom name, labels, annotations", + name: "request secret", deployReq: SecretRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - StringData: testKVP, + StringData: test.TestKVP, Type: corev1.SecretTypeBasicAuth, }, - desiredSecret: getTestSecret(func(s *corev1.Secret) { - s.Name = testName - s.Namespace = testNamespace - s.Labels = testKVP - s.Annotations = testKVP - s.StringData = testKVP + desiredSecret: test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestName + s.Namespace = test.TestNamespace + s.Labels = test.TestKVP + s.Annotations = test.TestKVP + s.StringData = test.TestKVP s.Type = corev1.SecretTypeBasicAuth }), wantErr: false, @@ -72,23 +56,23 @@ func TestRequestSecret(t *testing.T) { name: "request secret, successful mutation", deployReq: SecretRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, - StringData: testKVP, + StringData: test.TestKVP, Mutations: []mutation.MutateFunc{ testMutationFuncSuccessful, }, Client: testClient, }, - desiredSecret: getTestSecret(func(s *corev1.Secret) { - s.Name = testNameMutated - s.Namespace = testNamespace - s.Labels = testKVP - s.Annotations = testKVP - s.StringData = testKVPMutated + desiredSecret: test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestNameMutated + s.Namespace = test.TestNamespace + s.Labels = test.TestKVP + s.Annotations = test.TestKVP + s.StringData = test.TestKVPMutated }), wantErr: false, }, @@ -96,21 +80,21 @@ func TestRequestSecret(t *testing.T) { name: "request secret, failed mutation", deployReq: SecretRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredSecret: getTestSecret(func(s *corev1.Secret) { - s.Name = testName - s.Namespace = testNamespace - s.Labels = testKVP - s.Annotations = testKVP + desiredSecret: test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestName + s.Namespace = test.TestNamespace + s.Labels = test.TestKVP + s.Annotations = test.TestKVP }), wantErr: true, }, @@ -135,24 +119,24 @@ func TestRequestSecret(t *testing.T) { func TestCreateSecret(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredSecret := getTestSecret(func(s *corev1.Secret) { + desiredSecret := test.MakeTestSecret(nil, func(s *corev1.Secret) { s.TypeMeta = metav1.TypeMeta{ Kind: "Secret", APIVersion: "v1", } - s.Name = testName - s.Namespace = testNamespace - s.Labels = testKVP - s.Annotations = testKVP - s.StringData = testKVP + s.Name = test.TestName + s.Namespace = test.TestNamespace + s.Labels = test.TestKVP + s.Annotations = test.TestKVP + s.StringData = test.TestKVP }) err := CreateSecret(desiredSecret, testClient) assert.NoError(t, err) createdSecret := &corev1.Secret{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdSecret) assert.NoError(t, err) @@ -160,36 +144,36 @@ func TestCreateSecret(t *testing.T) { } func TestGetSecret(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestSecret(func(s *corev1.Secret) { - s.Name = testName - s.Namespace = testNamespace - s.StringData = testKVP + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestName + s.Namespace = test.TestNamespace + s.StringData = test.TestKVP })).Build() - _, err := GetSecret(testName, testNamespace, testClient) + _, err := GetSecret(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetSecret(testName, testNamespace, testClient) + _, err = GetSecret(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListSecrets(t *testing.T) { - secret1 := getTestSecret(func(s *corev1.Secret) { + secret1 := test.MakeTestSecret(nil, func(s *corev1.Secret) { s.Name = "secret-1" s.Labels[common.AppK8sKeyComponent] = "new-component-1" - s.Namespace = testNamespace + s.Namespace = test.TestNamespace }) - secret2 := getTestSecret(func(s *corev1.Secret) { + secret2 := test.MakeTestSecret(nil, func(s *corev1.Secret) { s.Name = "secret-2" - s.Namespace = testNamespace + s.Namespace = test.TestNamespace }) - secret3 := getTestSecret(func(s *corev1.Secret) { + secret3 := test.MakeTestSecret(nil, func(s *corev1.Secret) { s.Name = "secret-3" s.Labels[common.AppK8sKeyComponent] = "new-component-2" - s.Namespace = testNamespace + s.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -206,7 +190,7 @@ func TestListSecrets(t *testing.T) { desiredSecrets := []string{"secret-1", "secret-3"} - existingSecretList, err := ListSecrets(testNamespace, testClient, listOpts) + existingSecretList, err := ListSecrets(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingSecrets := []string{} @@ -219,14 +203,14 @@ func TestListSecrets(t *testing.T) { } func TestUpdateSecret(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestSecret(func(s *corev1.Secret) { - s.Name = testName - s.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestName + s.Namespace = test.TestNamespace })).Build() - desiredSecret := getTestSecret(func(s *corev1.Secret) { - s.Name = testName - s.Namespace = testNamespace + desiredSecret := test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestName + s.Namespace = test.TestNamespace s.Data = map[string][]byte{ "admin.password": []byte("testpassword2023"), } @@ -236,36 +220,36 @@ func TestUpdateSecret(t *testing.T) { existingSecret := &corev1.Secret{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingSecret) assert.NoError(t, err) assert.Equal(t, desiredSecret.Data, existingSecret.Data) testClient = fake.NewClientBuilder().Build() - existingSecret = getTestSecret(func(d *corev1.Secret) { - d.Name = testName + existingSecret = test.MakeTestSecret(nil, func(d *corev1.Secret) { + d.Name = test.TestName }) err = UpdateSecret(existingSecret, testClient) assert.Error(t, err) } func TestDeleteSecret(t *testing.T) { - testSecret := getTestSecret(func(s *corev1.Secret) { - s.Name = testName - s.Namespace = testNamespace + testSecret := test.MakeTestSecret(nil, func(s *corev1.Secret) { + s.Name = test.TestName + s.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testSecret).Build() - err := DeleteSecret(testName, testNamespace, testClient) + err := DeleteSecret(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingSecret := &corev1.Secret{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingSecret) assert.Error(t, err) diff --git a/pkg/workloads/statefulset_test.go b/pkg/workloads/statefulset_test.go index fe2f8b74e..02107a89a 100644 --- a/pkg/workloads/statefulset_test.go +++ b/pkg/workloads/statefulset_test.go @@ -17,27 +17,9 @@ import ( "github.com/argoproj-labs/argocd-operator/common" "github.com/argoproj-labs/argocd-operator/pkg/mutation" + "github.com/argoproj-labs/argocd-operator/tests/test" ) -type statefulSetOpt func(*appsv1.StatefulSet) - -func getTestStatefulSet(opts ...statefulSetOpt) *appsv1.StatefulSet { - desiredStatefulSet := &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - Spec: appsv1.StatefulSetSpec{ - Selector: &metav1.LabelSelector{}, - }, - } - - for _, opt := range opts { - opt(desiredStatefulSet) - } - return desiredStatefulSet -} - func TestRequestStatefulSet(t *testing.T) { testClient := fake.NewClientBuilder().Build() @@ -49,26 +31,26 @@ func TestRequestStatefulSet(t *testing.T) { wantErr bool }{ { - name: "request StatefulSet, no mutation, custom name, labels", + name: "request StatefulSet", statefultSetReq: StatefulSetRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: appsv1.StatefulSetSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: testKVP, + MatchLabels: test.TestKVP, }, }, }, - desiredStatefulSet: getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testName - ss.Namespace = testNamespace - ss.Labels = testKVP - ss.Annotations = testKVP - ss.Spec.Selector.MatchLabels = testKVP + desiredStatefulSet: test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestName + ss.Namespace = test.TestNamespace + ss.Labels = test.TestKVP + ss.Annotations = test.TestKVP + ss.Spec.Selector.MatchLabels = test.TestKVP }), wantErr: false, }, @@ -76,14 +58,14 @@ func TestRequestStatefulSet(t *testing.T) { name: "request StatefulSet, successful mutation", statefultSetReq: StatefulSetRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Spec: appsv1.StatefulSetSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: testKVP, + MatchLabels: test.TestKVP, }, }, Mutations: []mutation.MutateFunc{ @@ -91,13 +73,13 @@ func TestRequestStatefulSet(t *testing.T) { }, Client: testClient, }, - desiredStatefulSet: getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testNameMutated - ss.Namespace = testNamespace - ss.Labels = testKVP - ss.Annotations = testKVP + desiredStatefulSet: test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestNameMutated + ss.Namespace = test.TestNamespace + ss.Labels = test.TestKVP + ss.Annotations = test.TestKVP ss.Spec.Replicas = &testReplicasMutated - ss.Spec.Selector.MatchLabels = testKVP + ss.Spec.Selector.MatchLabels = test.TestKVP }), wantErr: false, }, @@ -105,21 +87,21 @@ func TestRequestStatefulSet(t *testing.T) { name: "request StatefulSet, failed mutation", statefultSetReq: StatefulSetRequest{ ObjectMeta: metav1.ObjectMeta{ - Name: testName, - Namespace: testNamespace, - Labels: testKVP, - Annotations: testKVP, + Name: test.TestName, + Namespace: test.TestNamespace, + Labels: test.TestKVP, + Annotations: test.TestKVP, }, Mutations: []mutation.MutateFunc{ - testMutationFuncFailed, + test.TestMutationFuncFailed, }, Client: testClient, }, - desiredStatefulSet: getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testNameMutated - ss.Namespace = testNamespace - ss.Labels = testKVP - ss.Annotations = testKVP + desiredStatefulSet: test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestNameMutated + ss.Namespace = test.TestNamespace + ss.Labels = test.TestKVP + ss.Annotations = test.TestKVP }), wantErr: true, }, @@ -144,23 +126,23 @@ func TestRequestStatefulSet(t *testing.T) { func TestCreateStatefulSet(t *testing.T) { testClient := fake.NewClientBuilder().Build() - desiredStatefulSet := getTestStatefulSet(func(ss *appsv1.StatefulSet) { + desiredStatefulSet := test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { ss.TypeMeta = metav1.TypeMeta{ Kind: "StatefulSet", APIVersion: "apps/v1", } - ss.Name = testName - ss.Namespace = testNamespace - ss.Labels = testKVP - ss.Annotations = testKVP + ss.Name = test.TestName + ss.Namespace = test.TestNamespace + ss.Labels = test.TestKVP + ss.Annotations = test.TestKVP }) err := CreateStatefulSet(desiredStatefulSet, testClient) assert.NoError(t, err) createdStatefulSet := &appsv1.StatefulSet{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, createdStatefulSet) assert.NoError(t, err) @@ -168,36 +150,36 @@ func TestCreateStatefulSet(t *testing.T) { } func TestGetStatefulSet(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testName - ss.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestName + ss.Namespace = test.TestNamespace })).Build() - _, err := GetStatefulSet(testName, testNamespace, testClient) + _, err := GetStatefulSet(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) testClient = fake.NewClientBuilder().Build() - _, err = GetStatefulSet(testName, testNamespace, testClient) + _, err = GetStatefulSet(test.TestName, test.TestNamespace, testClient) assert.Error(t, err) assert.True(t, apierrors.IsNotFound(err)) } func TestListStatefulSets(t *testing.T) { - StatefulSet1 := getTestStatefulSet(func(ss *appsv1.StatefulSet) { + StatefulSet1 := test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { ss.Name = "StatefulSet-1" - ss.Namespace = testNamespace + ss.Namespace = test.TestNamespace ss.Labels[common.AppK8sKeyComponent] = "new-component-1" }) - StatefulSet2 := getTestStatefulSet(func(ss *appsv1.StatefulSet) { + StatefulSet2 := test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { ss.Name = "StatefulSet-2" - ss.Namespace = testNamespace + ss.Namespace = test.TestNamespace }) - StatefulSet3 := getTestStatefulSet(func(ss *appsv1.StatefulSet) { + StatefulSet3 := test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { ss.Name = "StatefulSet-3" ss.Labels[common.AppK8sKeyComponent] = "new-component-2" - ss.Namespace = testNamespace + ss.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects( @@ -214,7 +196,7 @@ func TestListStatefulSets(t *testing.T) { desiredStatefulSets := []string{"StatefulSet-1", "StatefulSet-3"} - existingStatefulSetList, err := ListStatefulSets(testNamespace, testClient, listOpts) + existingStatefulSetList, err := ListStatefulSets(test.TestNamespace, testClient, listOpts) assert.NoError(t, err) existingStatefulSets := []string{} @@ -227,14 +209,14 @@ func TestListStatefulSets(t *testing.T) { } func TestUpdateStatefulSet(t *testing.T) { - testClient := fake.NewClientBuilder().WithObjects(getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testName - ss.Namespace = testNamespace + testClient := fake.NewClientBuilder().WithObjects(test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestName + ss.Namespace = test.TestNamespace })).Build() - desiredStatefulSet := getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testName - ss.Namespace = testNamespace + desiredStatefulSet := test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestName + ss.Namespace = test.TestNamespace ss.Spec.Template.Spec.NodeSelector = map[string]string{ "kubernetes.io/os": "linux", } @@ -245,36 +227,36 @@ func TestUpdateStatefulSet(t *testing.T) { existingStatefulSet := &appsv1.StatefulSet{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingStatefulSet) assert.NoError(t, err) assert.Equal(t, desiredStatefulSet.Spec, existingStatefulSet.Spec) testClient = fake.NewClientBuilder().Build() - existingStatefulSet = getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testName + existingStatefulSet = test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestName }) err = UpdateStatefulSet(existingStatefulSet, testClient) assert.Error(t, err) } func TestDeleteStatefulSet(t *testing.T) { - testss := getTestStatefulSet(func(ss *appsv1.StatefulSet) { - ss.Name = testName - ss.Namespace = testNamespace + testss := test.MakeTestStatefulSet(nil, func(ss *appsv1.StatefulSet) { + ss.Name = test.TestName + ss.Namespace = test.TestNamespace }) testClient := fake.NewClientBuilder().WithObjects(testss).Build() - err := DeleteStatefulSet(testName, testNamespace, testClient) + err := DeleteStatefulSet(test.TestName, test.TestNamespace, testClient) assert.NoError(t, err) existingStatefulSet := &appsv1.StatefulSet{} err = testClient.Get(context.TODO(), types.NamespacedName{ - Namespace: testNamespace, - Name: testName, + Namespace: test.TestNamespace, + Name: test.TestName, }, existingStatefulSet) assert.Error(t, err) diff --git a/pkg/workloads/workloads_test.go b/pkg/workloads/workloads_test.go index ff378ab8b..92512077e 100644 --- a/pkg/workloads/workloads_test.go +++ b/pkg/workloads/workloads_test.go @@ -10,57 +10,38 @@ import ( cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "github.com/argoproj-labs/argocd-operator/tests/test" ) // common test variables used across workloads tests var ( - testName = "test-name" - testInstance = "test-instance" - testInstanceNamespace = "test-instance-ns" - testNamespace = "test-ns" - testComponent = "test-component" - testKey = "test-key" - testVal = "test-value" - testValMutated = "test-value-mutated" - - testNameMutated = "mutated-name" testReplicasMutated = int32(4) - testKVP = map[string]string{ - testKey: testVal, - } - testKVPMutated = map[string]string{ - testKey: testValMutated, - } ) -func testMutationFuncFailed(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { - return errors.New("test-mutation-error") -} - func testMutationFuncSuccessful(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { switch obj := resource.(type) { case *appsv1.Deployment: - obj.Name = testNameMutated + obj.Name = test.TestNameMutated obj.Spec.Replicas = &testReplicasMutated return nil case *appsv1.StatefulSet: - obj.Name = testNameMutated + obj.Name = test.TestNameMutated obj.Spec.Replicas = &testReplicasMutated return nil case *oappsv1.DeploymentConfig: - obj.Name = testNameMutated + obj.Name = test.TestNameMutated obj.Spec.Replicas = testReplicasMutated return nil case *corev1.Secret: - obj.Name = testNameMutated - obj.StringData = testKVPMutated + obj.Name = test.TestNameMutated + obj.StringData = test.TestKVPMutated return nil case *corev1.ConfigMap: - obj.Name = testNameMutated - obj.Data = testKVPMutated + obj.Name = test.TestNameMutated + obj.Data = test.TestKVPMutated return nil case *autoscaling.HorizontalPodAutoscaler: - obj.Name = testNameMutated + obj.Name = test.TestNameMutated obj.Spec.MaxReplicas = testReplicasMutated return nil } diff --git a/tests/mock/appcontroller.go b/tests/mock/appcontroller.go new file mode 100644 index 000000000..0d7a9b2d4 --- /dev/null +++ b/tests/mock/appcontroller.go @@ -0,0 +1,27 @@ +package mock + +import ( + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Appcontroller struct { + Client client.Client + Logger *util.Logger + Name string + Namespace string +} + +func NewAppController(name, namespace string, client client.Client) *Appcontroller { + return &Appcontroller{ + Client: client, + Logger: util.NewLogger("app-controller"), + Name: name, + Namespace: namespace, + } +} + +func (ac *Appcontroller) TriggerRollout(key string) error { + return argocdcommon.TriggerStatefulSetRollout(ac.Name, ac.Namespace, key, ac.Client) +} diff --git a/tests/mock/redis.go b/tests/mock/redis.go new file mode 100644 index 000000000..da0999c42 --- /dev/null +++ b/tests/mock/redis.go @@ -0,0 +1,43 @@ +package mock + +import ( + "github.com/argoproj-labs/argocd-operator/pkg/util" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Redis struct { + Client client.Client + Logger *util.Logger + Name string + Namespace string +} + +var ( + useTLS = false + redisServerAddress = "" +) + +func NewRedis(name, namespace string, client client.Client) *Redis { + return &Redis{ + Client: client, + Logger: util.NewLogger("redis"), + Name: name, + Namespace: namespace, + } +} + +func (r *Redis) SetUseTLS(val bool) { + useTLS = val +} + +func (r *Redis) SetServerAddress(val string) { + redisServerAddress = val +} + +func (r *Redis) UseTLS() bool { + return useTLS +} + +func (r *Redis) GetServerAddress() string { + return redisServerAddress +} diff --git a/tests/mock/reposerver.go b/tests/mock/reposerver.go new file mode 100644 index 000000000..1abe7826f --- /dev/null +++ b/tests/mock/reposerver.go @@ -0,0 +1,39 @@ +package mock + +import ( + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Reposerver struct { + Client client.Client + Logger *util.Logger + Name string + Namespace string +} + +var ( + reposerveraddress = "" +) + +func (r *Reposerver) SetServerAddress(val string) { + reposerveraddress = val +} + +func NewRepoServer(name, namespace string, client client.Client) *Reposerver { + return &Reposerver{ + Client: client, + Logger: util.NewLogger("repo-server"), + Name: name, + Namespace: namespace, + } +} + +func (r *Reposerver) TriggerRollout(key string) error { + return argocdcommon.TriggerDeploymentRollout(r.Name, r.Namespace, key, r.Client) +} + +func (r *Reposerver) GetServerAddress() string { + return reposerveraddress +} diff --git a/tests/mock/server.go b/tests/mock/server.go new file mode 100644 index 000000000..2dab6243e --- /dev/null +++ b/tests/mock/server.go @@ -0,0 +1,27 @@ +package mock + +import ( + "github.com/argoproj-labs/argocd-operator/controllers/argocd/argocdcommon" + "github.com/argoproj-labs/argocd-operator/pkg/util" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Server struct { + Client client.Client + Logger *util.Logger + Name string + Namespace string +} + +func NewServer(name, namespace string, client client.Client) *Server { + return &Server{ + Client: client, + Logger: util.NewLogger("server"), + Name: name, + Namespace: namespace, + } +} + +func (s *Server) TriggerRollout(key string) error { + return argocdcommon.TriggerDeploymentRollout(s.Name, s.Namespace, key, s.Client) +} diff --git a/tests/test/testing.go b/tests/test/testing.go new file mode 100644 index 000000000..e7fa1b33d --- /dev/null +++ b/tests/test/testing.go @@ -0,0 +1,429 @@ +package test + +import ( + "errors" + + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + appsv1 "k8s.io/api/apps/v1" + autoscalingv1 "k8s.io/api/autoscaling/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + + argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" + cntrlClient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + TestArgoCDName = "test-argocd" + TestName = "test-name" + TestInstance = "test-instance" + TestInstanceNamespace = "test-instance-ns" + TestNamespace = "test-ns" + TestComponent = "test-component" + TestApplicationName = "test-application-name" + TestKey = "test-key" + TestVal = "test-val" + TestValMutated = "test-val-mutated" + TestNameMutated = "test-name-mutated" + TestCert = "test-cert" +) + +var ( + TestKVP = map[string]string{ + TestKey: TestVal, + } + TestKVPMutated = map[string]string{ + TestKey: TestValMutated, + } +) + +func MakeTestReconcilerClient(sch *runtime.Scheme, resObjs, subresObjs []client.Object, runtimeObj []runtime.Object) client.Client { + client := fake.NewClientBuilder().WithScheme(sch) + if len(resObjs) > 0 { + client = client.WithObjects(resObjs...) + } + if len(subresObjs) > 0 { + client = client.WithStatusSubresource(subresObjs...) + } + if len(runtimeObj) > 0 { + client = client.WithRuntimeObjects(runtimeObj...) + } + return client.Build() +} + +type SchemeOpt func(*runtime.Scheme) + +func MakeTestReconcilerScheme(sOpts ...SchemeOpt) *runtime.Scheme { + s := scheme.Scheme + for _, opt := range sOpts { + opt(s) + } + + return s +} + +func TestMutationFuncFailed(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { + return errors.New("test-mutation-error") +} + +func TestMutationFuncSuccessful(cr *argoproj.ArgoCD, resource interface{}, client cntrlClient.Client, args ...interface{}) error { + return nil +} + +type argoCDOpt func(*argoproj.ArgoCD) + +func MakeTestArgoCD(a *argoproj.ArgoCD, opts ...argoCDOpt) *argoproj.ArgoCD { + if a == nil { + a = &argoproj.ArgoCD{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestArgoCDName, + Namespace: TestNamespace, + }, + } + } + for _, o := range opts { + o(a) + } + return a +} + +type namespaceOpt func(*corev1.Namespace) + +func MakeTestNamespace(ns *corev1.Namespace, opts ...namespaceOpt) *corev1.Namespace { + if ns == nil { + ns = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestNamespace, + Labels: make(map[string]string), + }, + } + } + for _, o := range opts { + o(ns) + } + return ns +} + +type statefulSetOpt func(*appsv1.StatefulSet) + +func MakeTestStatefulSet(ss *appsv1.StatefulSet, opts ...statefulSetOpt) *appsv1.StatefulSet { + if ss == nil { + ss = &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Spec: appsv1.StatefulSetSpec{ + Selector: &metav1.LabelSelector{}, + }, + } + } + for _, opt := range opts { + opt(ss) + } + return ss +} + +type deploymentOpt func(*appsv1.Deployment) + +func MakeTestDeployment(d *appsv1.Deployment, opts ...deploymentOpt) *appsv1.Deployment { + if d == nil { + d = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{}, + }, + } + } + for _, o := range opts { + o(d) + } + return d +} + +type hpaOpt func(*autoscalingv1.HorizontalPodAutoscaler) + +func MakeTestHPA(hpa *autoscalingv1.HorizontalPodAutoscaler, opts ...hpaOpt) *autoscalingv1.HorizontalPodAutoscaler { + if hpa == nil { + hpa = &autoscalingv1.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Spec: autoscalingv1.HorizontalPodAutoscalerSpec{}, + } + } + for _, o := range opts { + o(hpa) + } + return hpa +} + +type podOpt func(*corev1.Pod) + +func MakeTestPod(p *corev1.Pod, opts ...podOpt) *corev1.Pod { + if p == nil { + p = &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + }, + } + } + for _, o := range opts { + o(p) + } + return p +} + +type serviceOpt func(*corev1.Service) + +func MakeTestService(s *corev1.Service, opts ...serviceOpt) *corev1.Service { + if s == nil { + s = &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + } + } + for _, o := range opts { + o(s) + } + return s +} + +type configMapOpt func(*corev1.ConfigMap) + +func MakeTestConfigMap(cm *corev1.ConfigMap, opts ...configMapOpt) *corev1.ConfigMap { + if cm == nil { + cm = &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Data: make(map[string]string), + } + } + for _, o := range opts { + o(cm) + } + return cm +} + +type secretOpt func(*corev1.Secret) + +func MakeTestSecret(s *corev1.Secret, opts ...secretOpt) *corev1.Secret { + if s == nil { + s = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + StringData: map[string]string{}, + } + } + for _, o := range opts { + o(s) + } + return s +} + +type roleOpt func(*rbacv1.Role) + +func MakeTestRole(r *rbacv1.Role, opts ...roleOpt) *rbacv1.Role { + if r == nil { + r = &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Rules: TestRules, + } + } + for _, o := range opts { + o(r) + } + return r +} + +func MakeTestRoleRef(name string) rbacv1.RoleRef { + return rbacv1.RoleRef{ + Kind: "Role", + Name: name, + APIGroup: "rbac.authorization.k8s.io", + } +} + +func MakeTestSubjects(subs ...types.NamespacedName) []rbacv1.Subject { + subjects := []rbacv1.Subject{} + + for _, subj := range subs { + subjects = append(subjects, rbacv1.Subject{ + Kind: rbacv1.ServiceAccountKind, + Name: subj.Name, + Namespace: subj.Namespace, + }) + } + return subjects +} + +var ( + TestRules = []rbacv1.PolicyRule{ + { + APIGroups: []string{ + "", + }, + Resources: []string{ + "pods", + }, + Verbs: []string{ + "create", + }, + }, + } +) + +type roleBindingOpt func(*rbacv1.RoleBinding) + +func MakeTestRoleBinding(rb *rbacv1.RoleBinding, opts ...roleBindingOpt) *rbacv1.RoleBinding { + if rb == nil { + rb = &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + } + } + for _, o := range opts { + o(rb) + } + return rb +} + +type clusterRoleOpt func(*rbacv1.ClusterRole) + +func MakeTestClusterRole(cr *rbacv1.ClusterRole, opts ...clusterRoleOpt) *rbacv1.ClusterRole { + if cr == nil { + cr = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Rules: TestRules, + } + } + for _, o := range opts { + o(cr) + } + return cr +} + +type clusterRoleBindingOpt func(*rbacv1.ClusterRoleBinding) + +func MakeTestClusterRoleBinding(crb *rbacv1.ClusterRoleBinding, opts ...clusterRoleBindingOpt) *rbacv1.ClusterRoleBinding { + if crb == nil { + crb = &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: TestName, + APIGroup: "rbac.authorization.k8s.io", + }, + } + } + for _, o := range opts { + o(crb) + } + return crb +} + +type serviceAccountOpt func(*corev1.ServiceAccount) + +func MakeTestServiceAccount(opts ...serviceAccountOpt) *corev1.ServiceAccount { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + } + for _, o := range opts { + o(sa) + } + return sa +} + +type serviceMonitorOpt func(*monitoringv1.ServiceMonitor) + +func MakeTestServiceMonitor(sm *monitoringv1.ServiceMonitor, opts ...serviceMonitorOpt) *monitoringv1.ServiceMonitor { + if sm == nil { + sm = &monitoringv1.ServiceMonitor{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + Spec: monitoringv1.ServiceMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: TestKVP, + }, + }, + } + } + for _, o := range opts { + o(sm) + } + return sm +} + +type prometheusRuleOpt func(*monitoringv1.PrometheusRule) + +func MakeTestPrometheusRule(pr *monitoringv1.PrometheusRule, opts ...prometheusRuleOpt) *monitoringv1.PrometheusRule { + if pr == nil { + pr = &monitoringv1.PrometheusRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: TestName, + Namespace: TestNamespace, + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + } + } + for _, o := range opts { + o(pr) + } + return pr +}