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

     1  // Copyright 2019 ArgoCD Operator Developers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // 	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package argocd
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/rand"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"os"
    24  	"reflect"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"text/template"
    29  
    30  	"sigs.k8s.io/controller-runtime/pkg/builder"
    31  
    32  	"gopkg.in/yaml.v2"
    33  
    34  	"github.com/argoproj/argo-cd/v2/util/glob"
    35  
    36  	"github.com/argoproj-labs/argocd-operator/api/v1alpha1"
    37  	argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
    38  	"github.com/argoproj-labs/argocd-operator/common"
    39  	"github.com/argoproj-labs/argocd-operator/controllers/argoutil"
    40  
    41  	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
    42  	oappsv1 "github.com/openshift/api/apps/v1"
    43  	configv1 "github.com/openshift/api/config/v1"
    44  	routev1 "github.com/openshift/api/route/v1"
    45  	configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
    46  	"github.com/sethvargo/go-password/password"
    47  	"golang.org/x/mod/semver"
    48  	appsv1 "k8s.io/api/apps/v1"
    49  	corev1 "k8s.io/api/core/v1"
    50  	networkingv1 "k8s.io/api/networking/v1"
    51  	v1 "k8s.io/api/rbac/v1"
    52  
    53  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    54  
    55  	"k8s.io/apimachinery/pkg/api/resource"
    56  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    57  	"k8s.io/apimachinery/pkg/labels"
    58  	"k8s.io/apimachinery/pkg/selection"
    59  	"k8s.io/apimachinery/pkg/types"
    60  	"k8s.io/client-go/kubernetes"
    61  	"sigs.k8s.io/controller-runtime/pkg/client"
    62  	"sigs.k8s.io/controller-runtime/pkg/client/config"
    63  	"sigs.k8s.io/controller-runtime/pkg/event"
    64  	"sigs.k8s.io/controller-runtime/pkg/handler"
    65  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    66  )
    67  
    68  const (
    69  	grafanaDeprecatedWarning = "Warning: grafana field is deprecated from ArgoCD: field will be ignored."
    70  )
    71  
    72  var (
    73  	versionAPIFound = false
    74  )
    75  
    76  // IsVersionAPIAvailable returns true if the version api is present
    77  func IsVersionAPIAvailable() bool {
    78  	return versionAPIFound
    79  }
    80  
    81  // verifyVersionAPI will verify that the template API is present.
    82  func verifyVersionAPI() error {
    83  	found, err := argoutil.VerifyAPI(configv1.GroupName, configv1.GroupVersion.Version)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	versionAPIFound = found
    88  	return nil
    89  }
    90  
    91  // generateArgoAdminPassword will generate and return the admin password for Argo CD.
    92  func generateArgoAdminPassword() ([]byte, error) {
    93  	pass, err := password.Generate(
    94  		common.ArgoCDDefaultAdminPasswordLength,
    95  		common.ArgoCDDefaultAdminPasswordNumDigits,
    96  		common.ArgoCDDefaultAdminPasswordNumSymbols,
    97  		false, false)
    98  
    99  	return []byte(pass), err
   100  }
   101  
   102  // generateArgoServerKey will generate and return the server signature key for session validation.
   103  func generateArgoServerSessionKey() ([]byte, error) {
   104  	pass, err := password.Generate(
   105  		common.ArgoCDDefaultServerSessionKeyLength,
   106  		common.ArgoCDDefaultServerSessionKeyNumDigits,
   107  		common.ArgoCDDefaultServerSessionKeyNumSymbols,
   108  		false, false)
   109  
   110  	return []byte(pass), err
   111  }
   112  
   113  // getArgoApplicationControllerResources will return the ResourceRequirements for the Argo CD application controller container.
   114  func getArgoApplicationControllerResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements {
   115  	resources := corev1.ResourceRequirements{}
   116  
   117  	// Allow override of resource requirements from CR
   118  	if cr.Spec.Controller.Resources != nil {
   119  		resources = *cr.Spec.Controller.Resources
   120  	}
   121  
   122  	return resources
   123  }
   124  
   125  // getArgoApplicationControllerCommand will return the command for the ArgoCD Application Controller component.
   126  func getArgoApplicationControllerCommand(cr *argoproj.ArgoCD, useTLSForRedis bool) []string {
   127  	cmd := []string{
   128  		"argocd-application-controller",
   129  		"--operation-processors", fmt.Sprint(getArgoServerOperationProcessors(cr)),
   130  	}
   131  
   132  	if cr.Spec.Redis.IsEnabled() {
   133  		cmd = append(cmd, "--redis", getRedisServerAddress(cr))
   134  	} else {
   135  		log.Info("Redis is Disabled. Skipping adding Redis configuration to Application Controller.")
   136  	}
   137  
   138  	if useTLSForRedis {
   139  		cmd = append(cmd, "--redis-use-tls")
   140  		if isRedisTLSVerificationDisabled(cr) {
   141  			cmd = append(cmd, "--redis-insecure-skip-tls-verify")
   142  		} else {
   143  			cmd = append(cmd, "--redis-ca-certificate", "/app/config/controller/tls/redis/tls.crt")
   144  		}
   145  	}
   146  
   147  	if cr.Spec.Repo.IsEnabled() {
   148  		cmd = append(cmd, "--repo-server", getRepoServerAddress(cr))
   149  	} else {
   150  		log.Info("Repo Server is disabled. This would affect the functioning of Application Controller.")
   151  	}
   152  
   153  	cmd = append(cmd, "--status-processors", fmt.Sprint(getArgoServerStatusProcessors(cr)))
   154  	cmd = append(cmd, "--kubectl-parallelism-limit", fmt.Sprint(getArgoControllerParellismLimit(cr)))
   155  
   156  	if cr.Spec.SourceNamespaces != nil && len(cr.Spec.SourceNamespaces) > 0 {
   157  		cmd = append(cmd, "--application-namespaces", fmt.Sprint(strings.Join(cr.Spec.SourceNamespaces, ",")))
   158  	}
   159  
   160  	cmd = append(cmd, "--loglevel")
   161  	cmd = append(cmd, getLogLevel(cr.Spec.Controller.LogLevel))
   162  
   163  	cmd = append(cmd, "--logformat")
   164  	cmd = append(cmd, getLogFormat(cr.Spec.Controller.LogFormat))
   165  
   166  	return cmd
   167  }
   168  
   169  // getArgoContainerImage will return the container image for ArgoCD.
   170  func getArgoContainerImage(cr *argoproj.ArgoCD) string {
   171  	defaultTag, defaultImg := false, false
   172  	img := cr.Spec.Image
   173  	if img == "" {
   174  		img = common.ArgoCDDefaultArgoImage
   175  		defaultImg = true
   176  	}
   177  
   178  	tag := cr.Spec.Version
   179  	if tag == "" {
   180  		tag = common.ArgoCDDefaultArgoVersion
   181  		defaultTag = true
   182  	}
   183  	if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) {
   184  		return e
   185  	}
   186  
   187  	return argoutil.CombineImageTag(img, tag)
   188  }
   189  
   190  // getRepoServerContainerImage will return the container image for the Repo server.
   191  //
   192  // There are three possible options for configuring the image, and this is the
   193  // order of preference.
   194  //
   195  // 1. from the Spec, the spec.repo field has an image and version to use for
   196  // generating an image reference.
   197  // 2. from the Environment, this looks for the `ARGOCD_REPOSERVER_IMAGE` field and uses
   198  // that if the spec is not configured.
   199  // 3. the default is configured in common.ArgoCDDefaultRepoServerVersion and
   200  // common.ArgoCDDefaultRepoServerImage.
   201  func getRepoServerContainerImage(cr *argoproj.ArgoCD) string {
   202  	defaultImg, defaultTag := false, false
   203  	img := cr.Spec.Repo.Image
   204  	if img == "" {
   205  		img = common.ArgoCDDefaultArgoImage
   206  		defaultImg = true
   207  	}
   208  
   209  	tag := cr.Spec.Repo.Version
   210  	if tag == "" {
   211  		tag = common.ArgoCDDefaultArgoVersion
   212  		defaultTag = true
   213  	}
   214  	if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) {
   215  		return e
   216  	}
   217  	return argoutil.CombineImageTag(img, tag)
   218  }
   219  
   220  // getArgoRepoResources will return the ResourceRequirements for the Argo CD Repo server container.
   221  func getArgoRepoResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements {
   222  	resources := corev1.ResourceRequirements{}
   223  
   224  	// Allow override of resource requirements from CR
   225  	if cr.Spec.Repo.Resources != nil {
   226  		resources = *cr.Spec.Repo.Resources
   227  	}
   228  
   229  	return resources
   230  }
   231  
   232  // getArgoServerInsecure returns the insecure value for the ArgoCD Server component.
   233  func getArgoServerInsecure(cr *argoproj.ArgoCD) bool {
   234  	return cr.Spec.Server.Insecure
   235  }
   236  
   237  func isRepoServerTLSVerificationRequested(cr *argoproj.ArgoCD) bool {
   238  	return cr.Spec.Repo.VerifyTLS
   239  }
   240  
   241  func isRedisTLSVerificationDisabled(cr *argoproj.ArgoCD) bool {
   242  	return cr.Spec.Redis.DisableTLSVerification
   243  }
   244  
   245  // getArgoServerGRPCHost will return the GRPC host for the given ArgoCD.
   246  func getArgoServerGRPCHost(cr *argoproj.ArgoCD) string {
   247  	host := nameWithSuffix("grpc", cr)
   248  	if len(cr.Spec.Server.GRPC.Host) > 0 {
   249  		host = cr.Spec.Server.GRPC.Host
   250  	}
   251  	return host
   252  }
   253  
   254  // getArgoServerHost will return the host for the given ArgoCD.
   255  func getArgoServerHost(cr *argoproj.ArgoCD) string {
   256  	host := cr.Name
   257  	if len(cr.Spec.Server.Host) > 0 {
   258  		host = cr.Spec.Server.Host
   259  	}
   260  	return host
   261  }
   262  
   263  // getArgoServerResources will return the ResourceRequirements for the Argo CD server container.
   264  func getArgoServerResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements {
   265  	resources := corev1.ResourceRequirements{}
   266  
   267  	if cr.Spec.Server.Autoscale.Enabled {
   268  		resources = corev1.ResourceRequirements{
   269  			Limits: corev1.ResourceList{
   270  				corev1.ResourceCPU:    resource.MustParse(common.ArgoCDDefaultServerResourceLimitCPU),
   271  				corev1.ResourceMemory: resource.MustParse(common.ArgoCDDefaultServerResourceLimitMemory),
   272  			},
   273  			Requests: corev1.ResourceList{
   274  				corev1.ResourceCPU:    resource.MustParse(common.ArgoCDDefaultServerResourceRequestCPU),
   275  				corev1.ResourceMemory: resource.MustParse(common.ArgoCDDefaultServerResourceRequestMemory),
   276  			},
   277  		}
   278  	}
   279  
   280  	// Allow override of resource requirements from CR
   281  	if cr.Spec.Server.Resources != nil {
   282  		resources = *cr.Spec.Server.Resources
   283  	}
   284  
   285  	return resources
   286  }
   287  
   288  // getArgoServerURI will return the URI for the ArgoCD server.
   289  // The hostname for argocd-server is from the route, ingress, an external hostname or service name in that order.
   290  func (r *ReconcileArgoCD) getArgoServerURI(cr *argoproj.ArgoCD) string {
   291  	host := nameWithSuffix("server", cr) // Default to service name
   292  
   293  	// Use the external hostname provided by the user
   294  	if cr.Spec.Server.Host != "" {
   295  		host = cr.Spec.Server.Host
   296  	}
   297  
   298  	// Use Ingress host if enabled
   299  	if cr.Spec.Server.Ingress.Enabled {
   300  		ing := newIngressWithSuffix("server", cr)
   301  		if argoutil.IsObjectFound(r.Client, cr.Namespace, ing.Name, ing) {
   302  			host = ing.Spec.Rules[0].Host
   303  		}
   304  	}
   305  
   306  	// Use Route host if available, override Ingress if both exist
   307  	if IsRouteAPIAvailable() {
   308  		route := newRouteWithSuffix("server", cr)
   309  		if argoutil.IsObjectFound(r.Client, cr.Namespace, route.Name, route) {
   310  			host = route.Spec.Host
   311  		}
   312  	}
   313  
   314  	return fmt.Sprintf("https://%s", host) // TODO: Safe to assume HTTPS here?
   315  }
   316  
   317  // getArgoServerOperationProcessors will return the numeric Operation Processors value for the ArgoCD Server.
   318  func getArgoServerOperationProcessors(cr *argoproj.ArgoCD) int32 {
   319  	op := common.ArgoCDDefaultServerOperationProcessors
   320  	if cr.Spec.Controller.Processors.Operation > 0 {
   321  		op = cr.Spec.Controller.Processors.Operation
   322  	}
   323  	return op
   324  }
   325  
   326  // getArgoServerStatusProcessors will return the numeric Status Processors value for the ArgoCD Server.
   327  func getArgoServerStatusProcessors(cr *argoproj.ArgoCD) int32 {
   328  	sp := common.ArgoCDDefaultServerStatusProcessors
   329  	if cr.Spec.Controller.Processors.Status > 0 {
   330  		sp = cr.Spec.Controller.Processors.Status
   331  	}
   332  	return sp
   333  }
   334  
   335  // getArgoControllerParellismLimit returns the parallelism limit for the application controller
   336  func getArgoControllerParellismLimit(cr *argoproj.ArgoCD) int32 {
   337  	pl := common.ArgoCDDefaultControllerParallelismLimit
   338  	if cr.Spec.Controller.ParallelismLimit > 0 {
   339  		pl = cr.Spec.Controller.ParallelismLimit
   340  	}
   341  	return pl
   342  }
   343  
   344  // getRedisConfigPath will return the path for the Redis configuration templates.
   345  func getRedisConfigPath() string {
   346  	path := os.Getenv("REDIS_CONFIG_PATH")
   347  	if len(path) > 0 {
   348  		return path
   349  	}
   350  	return common.ArgoCDDefaultRedisConfigPath
   351  }
   352  
   353  // getRedisInitScript will load the redis configuration from a template on disk for the given ArgoCD.
   354  // If an error occurs, an empty string value will be returned.
   355  func getRedisConf(useTLSForRedis bool) string {
   356  	path := fmt.Sprintf("%s/redis.conf.tpl", getRedisConfigPath())
   357  	params := map[string]string{
   358  		"UseTLS": strconv.FormatBool(useTLSForRedis),
   359  	}
   360  	conf, err := loadTemplateFile(path, params)
   361  	if err != nil {
   362  		log.Error(err, "unable to load redis configuration")
   363  		return ""
   364  	}
   365  	return conf
   366  }
   367  
   368  // getRedisContainerImage will return the container image for the Redis server.
   369  func getRedisContainerImage(cr *argoproj.ArgoCD) string {
   370  	defaultImg, defaultTag := false, false
   371  	img := cr.Spec.Redis.Image
   372  	if img == "" {
   373  		img = common.ArgoCDDefaultRedisImage
   374  		defaultImg = true
   375  	}
   376  	tag := cr.Spec.Redis.Version
   377  	if tag == "" {
   378  		tag = common.ArgoCDDefaultRedisVersion
   379  		defaultTag = true
   380  	}
   381  	if e := os.Getenv(common.ArgoCDRedisImageEnvName); e != "" && (defaultTag && defaultImg) {
   382  		return e
   383  	}
   384  	return argoutil.CombineImageTag(img, tag)
   385  }
   386  
   387  // getRedisHAContainerImage will return the container image for the Redis server in HA mode.
   388  func getRedisHAContainerImage(cr *argoproj.ArgoCD) string {
   389  	defaultImg, defaultTag := false, false
   390  	img := cr.Spec.Redis.Image
   391  	if img == "" {
   392  		img = common.ArgoCDDefaultRedisImage
   393  		defaultImg = true
   394  	}
   395  	tag := cr.Spec.Redis.Version
   396  	if tag == "" {
   397  		tag = common.ArgoCDDefaultRedisVersionHA
   398  		defaultTag = true
   399  	}
   400  	if e := os.Getenv(common.ArgoCDRedisHAImageEnvName); e != "" && (defaultTag && defaultImg) {
   401  		return e
   402  	}
   403  	return argoutil.CombineImageTag(img, tag)
   404  }
   405  
   406  // getRedisHAProxyAddress will return the Redis HA Proxy service address for the given ArgoCD.
   407  func getRedisHAProxyAddress(cr *argoproj.ArgoCD) string {
   408  	return fqdnServiceRef("redis-ha-haproxy", common.ArgoCDDefaultRedisPort, cr)
   409  }
   410  
   411  // getRedisHAProxyContainerImage will return the container image for the Redis HA Proxy.
   412  func getRedisHAProxyContainerImage(cr *argoproj.ArgoCD) string {
   413  	defaultImg, defaultTag := false, false
   414  	img := cr.Spec.HA.RedisProxyImage
   415  	if len(img) <= 0 {
   416  		img = common.ArgoCDDefaultRedisHAProxyImage
   417  		defaultImg = true
   418  	}
   419  
   420  	tag := cr.Spec.HA.RedisProxyVersion
   421  	if len(tag) <= 0 {
   422  		tag = common.ArgoCDDefaultRedisHAProxyVersion
   423  		defaultTag = true
   424  	}
   425  
   426  	if e := os.Getenv(common.ArgoCDRedisHAProxyImageEnvName); e != "" && (defaultTag && defaultImg) {
   427  		return e
   428  	}
   429  
   430  	return argoutil.CombineImageTag(img, tag)
   431  }
   432  
   433  // getRedisInitScript will load the redis init script from a template on disk for the given ArgoCD.
   434  // If an error occurs, an empty string value will be returned.
   435  func getRedisInitScript(cr *argoproj.ArgoCD, useTLSForRedis bool) string {
   436  	path := fmt.Sprintf("%s/init.sh.tpl", getRedisConfigPath())
   437  	vars := map[string]string{
   438  		"ServiceName": nameWithSuffix("redis-ha", cr),
   439  		"UseTLS":      strconv.FormatBool(useTLSForRedis),
   440  	}
   441  
   442  	script, err := loadTemplateFile(path, vars)
   443  	if err != nil {
   444  		log.Error(err, "unable to load redis init-script")
   445  		return ""
   446  	}
   447  	return script
   448  }
   449  
   450  // getRedisHAProxySConfig will load the Redis HA Proxy configuration from a template on disk for the given ArgoCD.
   451  // If an error occurs, an empty string value will be returned.
   452  func getRedisHAProxyConfig(cr *argoproj.ArgoCD, useTLSForRedis bool) string {
   453  	path := fmt.Sprintf("%s/haproxy.cfg.tpl", getRedisConfigPath())
   454  	vars := map[string]string{
   455  		"ServiceName": nameWithSuffix("redis-ha", cr),
   456  		"UseTLS":      strconv.FormatBool(useTLSForRedis),
   457  	}
   458  
   459  	script, err := loadTemplateFile(path, vars)
   460  	if err != nil {
   461  		log.Error(err, "unable to load redis haproxy configuration")
   462  		return ""
   463  	}
   464  	return script
   465  }
   466  
   467  // getRedisHAProxyScript will load the Redis HA Proxy init script from a template on disk for the given ArgoCD.
   468  // If an error occurs, an empty string value will be returned.
   469  func getRedisHAProxyScript(cr *argoproj.ArgoCD) string {
   470  	path := fmt.Sprintf("%s/haproxy_init.sh.tpl", getRedisConfigPath())
   471  	vars := map[string]string{
   472  		"ServiceName": nameWithSuffix("redis-ha", cr),
   473  	}
   474  
   475  	script, err := loadTemplateFile(path, vars)
   476  	if err != nil {
   477  		log.Error(err, "unable to load redis haproxy init script")
   478  		return ""
   479  	}
   480  	return script
   481  }
   482  
   483  // getRedisResources will return the ResourceRequirements for the Redis container.
   484  func getRedisResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements {
   485  	resources := corev1.ResourceRequirements{}
   486  
   487  	// Allow override of resource requirements from CR
   488  	if cr.Spec.Redis.Resources != nil {
   489  		resources = *cr.Spec.Redis.Resources
   490  	}
   491  
   492  	return resources
   493  }
   494  
   495  // getRedisHAResources will return the ResourceRequirements for the Redis HA.
   496  func getRedisHAResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements {
   497  	resources := corev1.ResourceRequirements{}
   498  
   499  	// Allow override of resource requirements from CR
   500  	if cr.Spec.HA.Resources != nil {
   501  		resources = *cr.Spec.HA.Resources
   502  	}
   503  
   504  	return resources
   505  }
   506  
   507  // getRedisSentinelConf will load the redis sentinel configuration from a template on disk for the given ArgoCD.
   508  // If an error occurs, an empty string value will be returned.
   509  func getRedisSentinelConf(useTLSForRedis bool) string {
   510  	path := fmt.Sprintf("%s/sentinel.conf.tpl", getRedisConfigPath())
   511  	params := map[string]string{
   512  		"UseTLS": strconv.FormatBool(useTLSForRedis),
   513  	}
   514  	conf, err := loadTemplateFile(path, params)
   515  	if err != nil {
   516  		log.Error(err, "unable to load redis sentinel configuration")
   517  		return ""
   518  	}
   519  	return conf
   520  }
   521  
   522  // getRedisLivenessScript will load the redis liveness script from a template on disk for the given ArgoCD.
   523  // If an error occurs, an empty string value will be returned.
   524  func getRedisLivenessScript(useTLSForRedis bool) string {
   525  	path := fmt.Sprintf("%s/redis_liveness.sh.tpl", getRedisConfigPath())
   526  	params := map[string]string{
   527  		"UseTLS": strconv.FormatBool(useTLSForRedis),
   528  	}
   529  	conf, err := loadTemplateFile(path, params)
   530  	if err != nil {
   531  		log.Error(err, "unable to load redis liveness script")
   532  		return ""
   533  	}
   534  	return conf
   535  }
   536  
   537  // getRedisReadinessScript will load the redis readiness script from a template on disk for the given ArgoCD.
   538  // If an error occurs, an empty string value will be returned.
   539  func getRedisReadinessScript(useTLSForRedis bool) string {
   540  	path := fmt.Sprintf("%s/redis_readiness.sh.tpl", getRedisConfigPath())
   541  	params := map[string]string{
   542  		"UseTLS": strconv.FormatBool(useTLSForRedis),
   543  	}
   544  	conf, err := loadTemplateFile(path, params)
   545  	if err != nil {
   546  		log.Error(err, "unable to load redis readiness script")
   547  		return ""
   548  	}
   549  	return conf
   550  }
   551  
   552  // getSentinelLivenessScript will load the redis liveness script from a template on disk for the given ArgoCD.
   553  // If an error occurs, an empty string value will be returned.
   554  func getSentinelLivenessScript(useTLSForRedis bool) string {
   555  	path := fmt.Sprintf("%s/sentinel_liveness.sh.tpl", getRedisConfigPath())
   556  	params := map[string]string{
   557  		"UseTLS": strconv.FormatBool(useTLSForRedis),
   558  	}
   559  	conf, err := loadTemplateFile(path, params)
   560  	if err != nil {
   561  		log.Error(err, "unable to load sentinel liveness script")
   562  		return ""
   563  	}
   564  	return conf
   565  }
   566  
   567  // getRedisServerAddress will return the Redis service address for the given ArgoCD.
   568  func getRedisServerAddress(cr *argoproj.ArgoCD) string {
   569  	if cr.Spec.Redis.Remote != nil && *cr.Spec.Redis.Remote != "" {
   570  		return *cr.Spec.Redis.Remote
   571  	}
   572  	if cr.Spec.HA.Enabled {
   573  		return getRedisHAProxyAddress(cr)
   574  	}
   575  	return fqdnServiceRef(common.ArgoCDDefaultRedisSuffix, common.ArgoCDDefaultRedisPort, cr)
   576  }
   577  
   578  // loadTemplateFile will parse a template with the given path and execute it with the given params.
   579  func loadTemplateFile(path string, params map[string]string) (string, error) {
   580  	tmpl, err := template.ParseFiles(path)
   581  	if err != nil {
   582  		log.Error(err, "unable to parse template")
   583  		return "", err
   584  	}
   585  
   586  	buf := new(bytes.Buffer)
   587  	err = tmpl.Execute(buf, params)
   588  	if err != nil {
   589  		log.Error(err, "unable to execute template")
   590  		return "", err
   591  	}
   592  	return buf.String(), nil
   593  }
   594  
   595  // nameWithSuffix will return a name based on the given ArgoCD. The given suffix is appended to the generated name.
   596  // Example: Given an ArgoCD with the name "example-argocd", providing the suffix "foo" would result in the value of
   597  // "example-argocd-foo" being returned.
   598  func nameWithSuffix(suffix string, cr *argoproj.ArgoCD) string {
   599  	return fmt.Sprintf("%s-%s", cr.Name, suffix)
   600  }
   601  
   602  // fqdnServiceRef will return the FQDN referencing a specific service name, as set up by the operator, with the
   603  // given port.
   604  func fqdnServiceRef(service string, port int, cr *argoproj.ArgoCD) string {
   605  	return fmt.Sprintf("%s.%s.svc.cluster.local:%d", nameWithSuffix(service, cr), cr.Namespace, port)
   606  }
   607  
   608  // InspectCluster will verify the availability of extra features available to the cluster, such as Prometheus and
   609  // OpenShift Routes.
   610  func InspectCluster() error {
   611  	if err := verifyPrometheusAPI(); err != nil {
   612  		return err
   613  	}
   614  
   615  	if err := verifyRouteAPI(); err != nil {
   616  		return err
   617  	}
   618  
   619  	if err := verifyTemplateAPI(); err != nil {
   620  		return err
   621  	}
   622  
   623  	if err := verifyVersionAPI(); err != nil {
   624  		return err
   625  	}
   626  	return nil
   627  }
   628  
   629  // reconcileCertificateAuthority will reconcile all Certificate Authority resources.
   630  func (r *ReconcileArgoCD) reconcileCertificateAuthority(cr *argoproj.ArgoCD) error {
   631  	log.Info("reconciling CA secret")
   632  	if err := r.reconcileClusterCASecret(cr); err != nil {
   633  		return err
   634  	}
   635  
   636  	log.Info("reconciling CA config map")
   637  	if err := r.reconcileCAConfigMap(cr); err != nil {
   638  		return err
   639  	}
   640  	return nil
   641  }
   642  
   643  func (r *ReconcileArgoCD) redisShouldUseTLS(cr *argoproj.ArgoCD) bool {
   644  	var tlsSecretObj corev1.Secret
   645  	tlsSecretName := types.NamespacedName{Namespace: cr.Namespace, Name: common.ArgoCDRedisServerTLSSecretName}
   646  	err := r.Client.Get(context.TODO(), tlsSecretName, &tlsSecretObj)
   647  	if err != nil {
   648  		if !apierrors.IsNotFound(err) {
   649  			log.Error(err, "error looking up redis tls secret")
   650  		}
   651  		return false
   652  	}
   653  
   654  	secretOwnerRefs := tlsSecretObj.GetOwnerReferences()
   655  	if len(secretOwnerRefs) > 0 {
   656  		// OpenShift service CA makes the owner reference for the TLS secret to the
   657  		// service, which in turn is owned by the controller. This method performs
   658  		// a lookup of the controller through the intermediate owning service.
   659  		for _, secretOwner := range secretOwnerRefs {
   660  			if isOwnerOfInterest(secretOwner) {
   661  				key := client.ObjectKey{Name: secretOwner.Name, Namespace: tlsSecretObj.GetNamespace()}
   662  				svc := &corev1.Service{}
   663  
   664  				// Get the owning object of the secret
   665  				err := r.Client.Get(context.TODO(), key, svc)
   666  				if err != nil {
   667  					log.Error(err, fmt.Sprintf("could not get owner of secret %s", tlsSecretObj.GetName()))
   668  					return false
   669  				}
   670  
   671  				// If there's an object of kind ArgoCD in the owner's list,
   672  				// this will be our reconciled object.
   673  				serviceOwnerRefs := svc.GetOwnerReferences()
   674  				for _, serviceOwner := range serviceOwnerRefs {
   675  					if serviceOwner.Kind == "ArgoCD" {
   676  						return true
   677  					}
   678  				}
   679  			}
   680  		}
   681  	} else {
   682  		// For secrets without owner (i.e. manually created), we apply some
   683  		// heuristics. This may not be as accurate (e.g. if the user made a
   684  		// typo in the resource's name), but should be good enough for now.
   685  		if _, ok := tlsSecretObj.Annotations[common.AnnotationName]; ok {
   686  			return true
   687  		}
   688  	}
   689  	return false
   690  }
   691  
   692  // reconcileResources will reconcile common ArgoCD resources.
   693  func (r *ReconcileArgoCD) reconcileResources(cr *argoproj.ArgoCD) error {
   694  
   695  	// we reconcile SSO first so that we can catch and throw errors for any illegal SSO configurations right away, and return control from here
   696  	// preventing dex resources from getting created anyway through the other function calls, effectively bypassing the SSO checks
   697  	log.Info("reconciling SSO")
   698  	if err := r.reconcileSSO(cr); err != nil {
   699  		log.Info(err.Error())
   700  	}
   701  
   702  	log.Info("reconciling status")
   703  	if err := r.reconcileStatus(cr); err != nil {
   704  		log.Info(err.Error())
   705  	}
   706  
   707  	log.Info("reconciling roles")
   708  	if err := r.reconcileRoles(cr); err != nil {
   709  		log.Info(err.Error())
   710  		return err
   711  	}
   712  
   713  	log.Info("reconciling rolebindings")
   714  	if err := r.reconcileRoleBindings(cr); err != nil {
   715  		log.Info(err.Error())
   716  		return err
   717  	}
   718  
   719  	log.Info("reconciling service accounts")
   720  	if err := r.reconcileServiceAccounts(cr); err != nil {
   721  		log.Info(err.Error())
   722  		return err
   723  	}
   724  
   725  	log.Info("reconciling certificate authority")
   726  	if err := r.reconcileCertificateAuthority(cr); err != nil {
   727  		return err
   728  	}
   729  
   730  	log.Info("reconciling secrets")
   731  	if err := r.reconcileSecrets(cr); err != nil {
   732  		return err
   733  	}
   734  
   735  	useTLSForRedis := r.redisShouldUseTLS(cr)
   736  
   737  	log.Info("reconciling config maps")
   738  	if err := r.reconcileConfigMaps(cr, useTLSForRedis); err != nil {
   739  		return err
   740  	}
   741  
   742  	log.Info("reconciling services")
   743  	if err := r.reconcileServices(cr); err != nil {
   744  		return err
   745  	}
   746  
   747  	log.Info("reconciling deployments")
   748  	if err := r.reconcileDeployments(cr, useTLSForRedis); err != nil {
   749  		return err
   750  	}
   751  
   752  	log.Info("reconciling statefulsets")
   753  	if err := r.reconcileStatefulSets(cr, useTLSForRedis); err != nil {
   754  		return err
   755  	}
   756  
   757  	log.Info("reconciling autoscalers")
   758  	if err := r.reconcileAutoscalers(cr); err != nil {
   759  		return err
   760  	}
   761  
   762  	log.Info("reconciling ingresses")
   763  	if err := r.reconcileIngresses(cr); err != nil {
   764  		return err
   765  	}
   766  
   767  	if IsRouteAPIAvailable() {
   768  		log.Info("reconciling routes")
   769  		if err := r.reconcileRoutes(cr); err != nil {
   770  			return err
   771  		}
   772  	}
   773  
   774  	if IsPrometheusAPIAvailable() {
   775  		log.Info("reconciling prometheus")
   776  		if err := r.reconcilePrometheus(cr); err != nil {
   777  			return err
   778  		}
   779  
   780  		// Reconciles prometheusRule created to alert based on argo-cd workload status
   781  		if err := r.reconcilePrometheusRule(cr); err != nil {
   782  			return err
   783  		}
   784  
   785  		if err := r.reconcileMetricsServiceMonitor(cr); err != nil {
   786  			return err
   787  		}
   788  
   789  		if err := r.reconcileRepoServerServiceMonitor(cr); err != nil {
   790  			return err
   791  		}
   792  
   793  		if err := r.reconcileServerMetricsServiceMonitor(cr); err != nil {
   794  			return err
   795  		}
   796  	}
   797  
   798  	// check ManagedApplicationSetSourceNamespaces for proper cleanup
   799  	if cr.Spec.ApplicationSet != nil || len(r.ManagedApplicationSetSourceNamespaces) > 0 {
   800  		log.Info("reconciling ApplicationSet controller")
   801  		if err := r.reconcileApplicationSetController(cr); err != nil {
   802  			return err
   803  		}
   804  	}
   805  
   806  	if cr.Spec.Notifications.Enabled {
   807  		log.Info("reconciling Notifications controller")
   808  		if err := r.reconcileNotificationsController(cr); err != nil {
   809  			return err
   810  		}
   811  	}
   812  
   813  	if err := r.reconcileRepoServerTLSSecret(cr); err != nil {
   814  		return err
   815  	}
   816  
   817  	if err := r.reconcileRedisTLSSecret(cr, useTLSForRedis); err != nil {
   818  		return err
   819  	}
   820  
   821  	return nil
   822  }
   823  
   824  func (r *ReconcileArgoCD) deleteClusterResources(cr *argoproj.ArgoCD) error {
   825  	selector, err := argocdInstanceSelector(cr.Name)
   826  	if err != nil {
   827  		return err
   828  	}
   829  
   830  	clusterRoleList := &v1.ClusterRoleList{}
   831  	if err := filterObjectsBySelector(r.Client, clusterRoleList, selector); err != nil {
   832  		return fmt.Errorf("failed to filter ClusterRoles for %s: %w", cr.Name, err)
   833  	}
   834  
   835  	if err := deleteClusterRoles(r.Client, clusterRoleList); err != nil {
   836  		return err
   837  	}
   838  
   839  	clusterBindingsList := &v1.ClusterRoleBindingList{}
   840  	if err := filterObjectsBySelector(r.Client, clusterBindingsList, selector); err != nil {
   841  		return fmt.Errorf("failed to filter ClusterRoleBindings for %s: %w", cr.Name, err)
   842  	}
   843  
   844  	if err := deleteClusterRoleBindings(r.Client, clusterBindingsList); err != nil {
   845  		return err
   846  	}
   847  
   848  	return nil
   849  }
   850  
   851  func (r *ReconcileArgoCD) removeManagedByLabelFromNamespaces(namespace string) error {
   852  	nsList := &corev1.NamespaceList{}
   853  	listOption := client.MatchingLabels{
   854  		common.ArgoCDManagedByLabel: namespace,
   855  	}
   856  	if err := r.Client.List(context.TODO(), nsList, listOption); err != nil {
   857  		return err
   858  	}
   859  
   860  	nsList.Items = append(nsList.Items, corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}})
   861  	for _, n := range nsList.Items {
   862  		ns := &corev1.Namespace{}
   863  		if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: n.Name}, ns); err != nil {
   864  			return err
   865  		}
   866  
   867  		if ns.Labels == nil {
   868  			continue
   869  		}
   870  
   871  		if n, ok := ns.Labels[common.ArgoCDManagedByLabel]; !ok || n != namespace {
   872  			continue
   873  		}
   874  		delete(ns.Labels, common.ArgoCDManagedByLabel)
   875  		if err := r.Client.Update(context.TODO(), ns); err != nil {
   876  			log.Error(err, fmt.Sprintf("failed to remove label from namespace [%s]", ns.Name))
   877  		}
   878  	}
   879  	return nil
   880  }
   881  
   882  func filterObjectsBySelector(c client.Client, objectList client.ObjectList, selector labels.Selector) error {
   883  	return c.List(context.TODO(), objectList, client.MatchingLabelsSelector{Selector: selector})
   884  }
   885  
   886  func argocdInstanceSelector(name string) (labels.Selector, error) {
   887  	selector := labels.NewSelector()
   888  	requirement, err := labels.NewRequirement(common.ArgoCDKeyManagedBy, selection.Equals, []string{name})
   889  	if err != nil {
   890  		return nil, fmt.Errorf("failed to create a requirement for %w", err)
   891  	}
   892  	return selector.Add(*requirement), nil
   893  }
   894  
   895  func (r *ReconcileArgoCD) removeDeletionFinalizer(argocd *argoproj.ArgoCD) error {
   896  	argocd.Finalizers = removeString(argocd.GetFinalizers(), common.ArgoCDDeletionFinalizer)
   897  	if err := r.Client.Update(context.TODO(), argocd); err != nil {
   898  		return fmt.Errorf("failed to remove deletion finalizer from %s: %w", argocd.Name, err)
   899  	}
   900  	return nil
   901  }
   902  
   903  func (r *ReconcileArgoCD) addDeletionFinalizer(argocd *argoproj.ArgoCD) error {
   904  	argocd.Finalizers = append(argocd.Finalizers, common.ArgoCDDeletionFinalizer)
   905  	if err := r.Client.Update(context.TODO(), argocd); err != nil {
   906  		return fmt.Errorf("failed to add deletion finalizer for %s: %w", argocd.Name, err)
   907  	}
   908  	return nil
   909  }
   910  
   911  func removeString(slice []string, s string) []string {
   912  	var result []string
   913  	for _, item := range slice {
   914  		if item == s {
   915  			continue
   916  		}
   917  		result = append(result, item)
   918  	}
   919  	return result
   920  }
   921  
   922  // setResourceWatches will register Watches for each of the supported Resources.
   923  func (r *ReconcileArgoCD) setResourceWatches(bldr *builder.Builder, clusterResourceMapper, tlsSecretMapper, namespaceResourceMapper, clusterSecretResourceMapper, applicationSetGitlabSCMTLSConfigMapMapper handler.MapFunc) *builder.Builder {
   924  
   925  	deploymentConfigPred := predicate.Funcs{
   926  		UpdateFunc: func(e event.UpdateEvent) bool {
   927  			// Ignore updates to CR status in which case metadata.Generation does not change
   928  			var count int32 = 1
   929  			newDC, ok := e.ObjectNew.(*oappsv1.DeploymentConfig)
   930  			if !ok {
   931  				return false
   932  			}
   933  			oldDC, ok := e.ObjectOld.(*oappsv1.DeploymentConfig)
   934  			if !ok {
   935  				return false
   936  			}
   937  			if newDC.Name == defaultKeycloakIdentifier {
   938  				if newDC.Status.AvailableReplicas == count {
   939  					return true
   940  				}
   941  				if newDC.Status.AvailableReplicas == int32(0) &&
   942  					!reflect.DeepEqual(oldDC.Status.AvailableReplicas, newDC.Status.AvailableReplicas) {
   943  					// Handle the deletion of keycloak pod.
   944  					log.Info(fmt.Sprintf("Handle the pod deletion event for keycloak deployment config %s in namespace %s",
   945  						newDC.Name, newDC.Namespace))
   946  					err := handleKeycloakPodDeletion(newDC)
   947  					if err != nil {
   948  						log.Error(err, fmt.Sprintf("Failed to update Deployment Config %s for keycloak pod deletion in namespace %s",
   949  							newDC.Name, newDC.Namespace))
   950  					}
   951  				}
   952  			}
   953  			return false
   954  		},
   955  	}
   956  
   957  	deleteSSOPred := predicate.Funcs{
   958  		UpdateFunc: func(e event.UpdateEvent) bool {
   959  			newCR, ok := e.ObjectNew.(*argoproj.ArgoCD)
   960  			if !ok {
   961  				return false
   962  			}
   963  			oldCR, ok := e.ObjectOld.(*argoproj.ArgoCD)
   964  			if !ok {
   965  				return false
   966  			}
   967  
   968  			// Handle deletion of SSO from Argo CD custom resource
   969  			if !reflect.DeepEqual(oldCR.Spec.SSO, newCR.Spec.SSO) && newCR.Spec.SSO == nil {
   970  				err := r.deleteSSOConfiguration(newCR, oldCR)
   971  				if err != nil {
   972  					log.Error(err, fmt.Sprintf("Failed to delete SSO Configuration for ArgoCD %s in namespace %s",
   973  						newCR.Name, newCR.Namespace))
   974  				}
   975  			}
   976  
   977  			// Trigger reconciliation of SSO on update event
   978  			if !reflect.DeepEqual(oldCR.Spec.SSO, newCR.Spec.SSO) && newCR.Spec.SSO != nil && oldCR.Spec.SSO != nil {
   979  				err := r.reconcileSSO(newCR)
   980  				if err != nil {
   981  					log.Error(err, fmt.Sprintf("Failed to update existing SSO Configuration for ArgoCD %s in namespace %s",
   982  						newCR.Name, newCR.Namespace))
   983  				}
   984  			}
   985  			return true
   986  		},
   987  	}
   988  
   989  	// Add new predicate to delete Notifications Resources. The predicate watches the Argo CD CR for changes to the `.spec.Notifications.Enabled`
   990  	// field. When a change is detected that results in notifications being disabled, we trigger deletion of notifications resources
   991  	deleteNotificationsPred := predicate.Funcs{
   992  		UpdateFunc: func(e event.UpdateEvent) bool {
   993  			newCR, ok := e.ObjectNew.(*argoproj.ArgoCD)
   994  			if !ok {
   995  				return false
   996  			}
   997  			oldCR, ok := e.ObjectOld.(*argoproj.ArgoCD)
   998  			if !ok {
   999  				return false
  1000  			}
  1001  			if oldCR.Spec.Notifications.Enabled && !newCR.Spec.Notifications.Enabled {
  1002  				err := r.deleteNotificationsResources(newCR)
  1003  				if err != nil {
  1004  					log.Error(err, fmt.Sprintf("Failed to delete notifications controller resources for ArgoCD %s in namespace %s",
  1005  						newCR.Name, newCR.Namespace))
  1006  				}
  1007  			}
  1008  			return true
  1009  		},
  1010  	}
  1011  
  1012  	// Watch for changes to primary resource ArgoCD
  1013  	bldr.For(&argoproj.ArgoCD{}, builder.WithPredicates(deleteSSOPred, deleteNotificationsPred))
  1014  
  1015  	// Watch for changes to ConfigMap sub-resources owned by ArgoCD instances.
  1016  	bldr.Owns(&corev1.ConfigMap{})
  1017  
  1018  	// Watch for changes to Secret sub-resources owned by ArgoCD instances.
  1019  	bldr.Owns(&corev1.Secret{})
  1020  
  1021  	// Watch for changes to Service sub-resources owned by ArgoCD instances.
  1022  	bldr.Owns(&corev1.Service{})
  1023  
  1024  	// Watch for changes to Deployment sub-resources owned by ArgoCD instances.
  1025  	bldr.Owns(&appsv1.Deployment{})
  1026  
  1027  	// Watch for changes to Ingress sub-resources owned by ArgoCD instances.
  1028  	bldr.Owns(&networkingv1.Ingress{})
  1029  
  1030  	bldr.Owns(&v1.Role{})
  1031  
  1032  	bldr.Owns(&v1.RoleBinding{})
  1033  
  1034  	clusterResourceHandler := handler.EnqueueRequestsFromMapFunc(clusterResourceMapper)
  1035  
  1036  	clusterSecretResourceHandler := handler.EnqueueRequestsFromMapFunc(clusterSecretResourceMapper)
  1037  
  1038  	appSetGitlabSCMTLSConfigMapHandler := handler.EnqueueRequestsFromMapFunc(applicationSetGitlabSCMTLSConfigMapMapper)
  1039  
  1040  	tlsSecretHandler := handler.EnqueueRequestsFromMapFunc(tlsSecretMapper)
  1041  
  1042  	bldr.Watches(&v1.ClusterRoleBinding{}, clusterResourceHandler)
  1043  
  1044  	bldr.Watches(&v1.ClusterRole{}, clusterResourceHandler)
  1045  
  1046  	bldr.Watches(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{
  1047  		Name: common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName,
  1048  	}}, appSetGitlabSCMTLSConfigMapHandler)
  1049  
  1050  	// Watch for secrets of type TLS that might be created by external processes
  1051  	bldr.Watches(&corev1.Secret{Type: corev1.SecretTypeTLS}, tlsSecretHandler)
  1052  
  1053  	// Watch for cluster secrets added to the argocd instance
  1054  	bldr.Watches(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{
  1055  		Labels: map[string]string{
  1056  			common.ArgoCDManagedByClusterArgoCDLabel: "cluster",
  1057  		}}}, clusterSecretResourceHandler)
  1058  
  1059  	// Watch for changes to Secret sub-resources owned by ArgoCD instances.
  1060  	bldr.Owns(&appsv1.StatefulSet{})
  1061  
  1062  	// Inspect cluster to verify availability of extra features
  1063  	// This sets the flags that are used in subsequent checks
  1064  	if err := InspectCluster(); err != nil {
  1065  		log.Info("unable to inspect cluster")
  1066  	}
  1067  
  1068  	if IsRouteAPIAvailable() {
  1069  		// Watch OpenShift Route sub-resources owned by ArgoCD instances.
  1070  		bldr.Owns(&routev1.Route{})
  1071  	}
  1072  
  1073  	if IsPrometheusAPIAvailable() {
  1074  		// Watch Prometheus sub-resources owned by ArgoCD instances.
  1075  		bldr.Owns(&monitoringv1.Prometheus{})
  1076  
  1077  		// Watch Prometheus ServiceMonitor sub-resources owned by ArgoCD instances.
  1078  		bldr.Owns(&monitoringv1.ServiceMonitor{})
  1079  	}
  1080  
  1081  	if IsTemplateAPIAvailable() {
  1082  		// Watch for the changes to Deployment Config
  1083  		bldr.Owns(&oappsv1.DeploymentConfig{}, builder.WithPredicates(deploymentConfigPred))
  1084  
  1085  	}
  1086  
  1087  	// Watch for changes to NotificationsConfiguration CR
  1088  	bldr.Owns(&v1alpha1.NotificationsConfiguration{})
  1089  
  1090  	namespaceHandler := handler.EnqueueRequestsFromMapFunc(namespaceResourceMapper)
  1091  
  1092  	bldr.Watches(&corev1.Namespace{}, namespaceHandler, builder.WithPredicates(namespaceFilterPredicate()))
  1093  
  1094  	return bldr
  1095  }
  1096  
  1097  // boolPtr returns a pointer to val
  1098  func boolPtr(val bool) *bool {
  1099  	return &val
  1100  }
  1101  
  1102  func int64Ptr(val int64) *int64 {
  1103  	return &val
  1104  }
  1105  
  1106  // triggerRollout will trigger a rollout of a Kubernetes resource specified as
  1107  // obj. It currently supports Deployment and StatefulSet resources.
  1108  func (r *ReconcileArgoCD) triggerRollout(obj interface{}, key string) error {
  1109  	switch res := obj.(type) {
  1110  	case *appsv1.Deployment:
  1111  		return r.triggerDeploymentRollout(res, key)
  1112  	case *appsv1.StatefulSet:
  1113  		return r.triggerStatefulSetRollout(res, key)
  1114  	default:
  1115  		return fmt.Errorf("resource of unknown type %T, cannot trigger rollout", res)
  1116  	}
  1117  }
  1118  
  1119  func allowedNamespace(current string, namespaces string) bool {
  1120  
  1121  	clusterConfigNamespaces := splitList(namespaces)
  1122  	if len(clusterConfigNamespaces) > 0 {
  1123  		if clusterConfigNamespaces[0] == "*" {
  1124  			return true
  1125  		}
  1126  
  1127  		for _, n := range clusterConfigNamespaces {
  1128  			if n == current {
  1129  				return true
  1130  			}
  1131  		}
  1132  	}
  1133  	return false
  1134  }
  1135  
  1136  func splitList(s string) []string {
  1137  	elems := strings.Split(s, ",")
  1138  	for i := range elems {
  1139  		elems[i] = strings.TrimSpace(elems[i])
  1140  	}
  1141  	return elems
  1142  }
  1143  
  1144  func containsString(arr []string, s string) bool {
  1145  	for _, val := range arr {
  1146  		if strings.TrimSpace(val) == s {
  1147  			return true
  1148  		}
  1149  	}
  1150  	return false
  1151  }
  1152  
  1153  // 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
  1154  // deprecation notice
  1155  type DeprecationEventEmissionStatus struct {
  1156  	SSOSpecDeprecationWarningEmitted    bool
  1157  	DexSpecDeprecationWarningEmitted    bool
  1158  	DisableDexDeprecationWarningEmitted bool
  1159  }
  1160  
  1161  // DeprecationEventEmissionTracker map stores the namespace containing ArgoCD instance as key and DeprecationEventEmissionStatus as value,
  1162  // where DeprecationEventEmissionStatus tracks the events that have been emitted for the instance in the particular namespace.
  1163  // This is temporary and can be removed in v0.0.6 when we remove the deprecated fields.
  1164  var DeprecationEventEmissionTracker = make(map[string]DeprecationEventEmissionStatus)
  1165  
  1166  func namespaceFilterPredicate() predicate.Predicate {
  1167  	return predicate.Funcs{
  1168  		UpdateFunc: func(e event.UpdateEvent) bool {
  1169  			// This checks if ArgoCDManagedByLabel exists in newMeta, if exists then -
  1170  			// 1. Check if oldMeta had the label or not? if no, return true
  1171  			// 2. if yes, check if the old and new values are different, if yes,
  1172  			// first deleteRBACs for the old value & return true.
  1173  			// Event is then handled by the reconciler, which would create appropriate RBACs.
  1174  			if valNew, ok := e.ObjectNew.GetLabels()[common.ArgoCDManagedByLabel]; ok {
  1175  				if valOld, ok := e.ObjectOld.GetLabels()[common.ArgoCDManagedByLabel]; ok && valOld != valNew {
  1176  					k8sClient, err := initK8sClient()
  1177  					if err != nil {
  1178  						return false
  1179  					}
  1180  					if err := deleteRBACsForNamespace(e.ObjectOld.GetName(), k8sClient); err != nil {
  1181  						log.Error(err, fmt.Sprintf("failed to delete RBACs for namespace: %s", e.ObjectOld.GetName()))
  1182  					} else {
  1183  						log.Info(fmt.Sprintf("Successfully removed the RBACs for namespace: %s", e.ObjectOld.GetName()))
  1184  					}
  1185  
  1186  					// Delete namespace from cluster secret of previously managing argocd instance
  1187  					if err = deleteManagedNamespaceFromClusterSecret(valOld, e.ObjectOld.GetName(), k8sClient); err != nil {
  1188  						log.Error(err, fmt.Sprintf("unable to delete namespace %s from cluster secret", e.ObjectOld.GetName()))
  1189  					} else {
  1190  						log.Info(fmt.Sprintf("Successfully deleted namespace %s from cluster secret", e.ObjectOld.GetName()))
  1191  					}
  1192  				}
  1193  				return true
  1194  			}
  1195  			// This checks if the old meta had the label, if it did, delete the RBACs for the namespace
  1196  			// which were created when the label was added to the namespace.
  1197  			if ns, ok := e.ObjectOld.GetLabels()[common.ArgoCDManagedByLabel]; ok && ns != "" {
  1198  				k8sClient, err := initK8sClient()
  1199  				if err != nil {
  1200  					return false
  1201  				}
  1202  				if err := deleteRBACsForNamespace(e.ObjectOld.GetName(), k8sClient); err != nil {
  1203  					log.Error(err, fmt.Sprintf("failed to delete RBACs for namespace: %s", e.ObjectOld.GetName()))
  1204  				} else {
  1205  					log.Info(fmt.Sprintf("Successfully removed the RBACs for namespace: %s", e.ObjectOld.GetName()))
  1206  				}
  1207  
  1208  				// Delete managed namespace from cluster secret
  1209  				if err = deleteManagedNamespaceFromClusterSecret(ns, e.ObjectOld.GetName(), k8sClient); err != nil {
  1210  					log.Error(err, fmt.Sprintf("unable to delete namespace %s from cluster secret", e.ObjectOld.GetName()))
  1211  				} else {
  1212  					log.Info(fmt.Sprintf("Successfully deleted namespace %s from cluster secret", e.ObjectOld.GetName()))
  1213  				}
  1214  
  1215  			}
  1216  			return false
  1217  		},
  1218  		DeleteFunc: func(e event.DeleteEvent) bool {
  1219  			if ns, ok := e.Object.GetLabels()[common.ArgoCDManagedByLabel]; ok && ns != "" {
  1220  				k8sClient, err := initK8sClient()
  1221  
  1222  				if err != nil {
  1223  					return false
  1224  				}
  1225  				// Delete managed namespace from cluster secret
  1226  				err = deleteManagedNamespaceFromClusterSecret(ns, e.Object.GetName(), k8sClient)
  1227  				if err != nil {
  1228  					log.Error(err, fmt.Sprintf("unable to delete namespace %s from cluster secret", e.Object.GetName()))
  1229  				} else {
  1230  					log.Info(fmt.Sprintf("Successfully deleted namespace %s from cluster secret", e.Object.GetName()))
  1231  				}
  1232  			}
  1233  
  1234  			// if a namespace is deleted, remove it from deprecationEventEmissionTracker (if exists) so that if a namespace with the same name
  1235  			// is created in the future and contains an Argo CD instance, it will be tracked appropriately
  1236  			delete(DeprecationEventEmissionTracker, e.Object.GetName())
  1237  			return false
  1238  		},
  1239  	}
  1240  }
  1241  
  1242  // deleteRBACsForNamespace deletes the RBACs when the label from the namespace is removed.
  1243  func deleteRBACsForNamespace(sourceNS string, k8sClient kubernetes.Interface) error {
  1244  	log.Info(fmt.Sprintf("Removing the RBACs created for the namespace: %s", sourceNS))
  1245  
  1246  	// List all the roles created for ArgoCD using the label selector
  1247  	labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{common.ArgoCDKeyPartOf: common.ArgoCDAppName}}
  1248  	roles, err := k8sClient.RbacV1().Roles(sourceNS).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.Set(labelSelector.MatchLabels).String()})
  1249  	if err != nil {
  1250  		log.Error(err, fmt.Sprintf("failed to list roles for namespace: %s", sourceNS))
  1251  		return err
  1252  	}
  1253  
  1254  	// Delete all the retrieved roles
  1255  	for _, role := range roles.Items {
  1256  		err = k8sClient.RbacV1().Roles(sourceNS).Delete(context.TODO(), role.Name, metav1.DeleteOptions{})
  1257  		if err != nil {
  1258  			log.Error(err, fmt.Sprintf("failed to delete roles for namespace: %s", sourceNS))
  1259  		}
  1260  	}
  1261  
  1262  	// List all the roles bindings created for ArgoCD using the label selector
  1263  	roleBindings, err := k8sClient.RbacV1().RoleBindings(sourceNS).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.Set(labelSelector.MatchLabels).String()})
  1264  	if err != nil {
  1265  		log.Error(err, fmt.Sprintf("failed to list role bindings for namespace: %s", sourceNS))
  1266  		return err
  1267  	}
  1268  
  1269  	// Delete all the retrieved role bindings
  1270  	for _, roleBinding := range roleBindings.Items {
  1271  		err = k8sClient.RbacV1().RoleBindings(sourceNS).Delete(context.TODO(), roleBinding.Name, metav1.DeleteOptions{})
  1272  		if err != nil {
  1273  			log.Error(err, fmt.Sprintf("failed to delete role binding for namespace: %s", sourceNS))
  1274  		}
  1275  	}
  1276  
  1277  	return nil
  1278  }
  1279  
  1280  func deleteManagedNamespaceFromClusterSecret(ownerNS, sourceNS string, k8sClient kubernetes.Interface) error {
  1281  
  1282  	// Get the cluster secret used for configuring ArgoCD
  1283  	labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{common.ArgoCDSecretTypeLabel: "cluster"}}
  1284  	secrets, err := k8sClient.CoreV1().Secrets(ownerNS).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.Set(labelSelector.MatchLabels).String()})
  1285  	if err != nil {
  1286  		log.Error(err, fmt.Sprintf("failed to retrieve secrets for namespace: %s", ownerNS))
  1287  		return err
  1288  	}
  1289  	for _, secret := range secrets.Items {
  1290  		if string(secret.Data["server"]) != common.ArgoCDDefaultServer {
  1291  			continue
  1292  		}
  1293  		if namespaces, ok := secret.Data["namespaces"]; ok {
  1294  			namespaceList := strings.Split(string(namespaces), ",")
  1295  			var result []string
  1296  
  1297  			for _, n := range namespaceList {
  1298  				// remove the namespace from the list of namespaces
  1299  				if strings.TrimSpace(n) == sourceNS {
  1300  					continue
  1301  				}
  1302  				result = append(result, strings.TrimSpace(n))
  1303  				sort.Strings(result)
  1304  				secret.Data["namespaces"] = []byte(strings.Join(result, ","))
  1305  			}
  1306  			// Update the secret with the updated list of namespaces
  1307  			if _, err = k8sClient.CoreV1().Secrets(ownerNS).Update(context.TODO(), &secret, metav1.UpdateOptions{}); err != nil {
  1308  				log.Error(err, fmt.Sprintf("failed to update cluster permission secret for namespace: %s", ownerNS))
  1309  				return err
  1310  			}
  1311  		}
  1312  	}
  1313  	return nil
  1314  }
  1315  
  1316  func initK8sClient() (*kubernetes.Clientset, error) {
  1317  	cfg, err := config.GetConfig()
  1318  	if err != nil {
  1319  		log.Error(err, "unable to get k8s config")
  1320  		return nil, err
  1321  	}
  1322  
  1323  	k8sClient, err := kubernetes.NewForConfig(cfg)
  1324  	if err != nil {
  1325  		log.Error(err, "unable to create k8s client")
  1326  		return nil, err
  1327  	}
  1328  
  1329  	return k8sClient, nil
  1330  }
  1331  
  1332  // getLogLevel returns the log level for a specified component if it is set or returns the default log level if it is not set
  1333  func getLogLevel(logField string) string {
  1334  
  1335  	switch strings.ToLower(logField) {
  1336  	case "debug",
  1337  		"info",
  1338  		"warn",
  1339  		"error":
  1340  		return logField
  1341  	}
  1342  	return common.ArgoCDDefaultLogLevel
  1343  }
  1344  
  1345  // getLogFormat returns the log format for a specified component if it is set or returns the default log format if it is not set
  1346  func getLogFormat(logField string) string {
  1347  	switch strings.ToLower(logField) {
  1348  	case "text",
  1349  		"json":
  1350  		return logField
  1351  	}
  1352  	return common.ArgoCDDefaultLogFormat
  1353  }
  1354  
  1355  func (r *ReconcileArgoCD) setManagedNamespaces(cr *argoproj.ArgoCD) error {
  1356  	namespaces := &corev1.NamespaceList{}
  1357  	listOption := client.MatchingLabels{
  1358  		common.ArgoCDManagedByLabel: cr.Namespace,
  1359  	}
  1360  
  1361  	// get the list of namespaces managed by the Argo CD instance
  1362  	if err := r.Client.List(context.TODO(), namespaces, listOption); err != nil {
  1363  		return err
  1364  	}
  1365  
  1366  	namespaces.Items = append(namespaces.Items, corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: cr.Namespace}})
  1367  	r.ManagedNamespaces = namespaces
  1368  	return nil
  1369  }
  1370  
  1371  // getSourceNamespaces retrieves a list of namespaces that match the sourceNamespaces
  1372  // pattern specified in the given ArgoCD
  1373  func (r *ReconcileArgoCD) getSourceNamespaces(cr *argoproj.ArgoCD) ([]string, error) {
  1374  	sourceNamespaces := []string{}
  1375  	namespaces := &corev1.NamespaceList{}
  1376  
  1377  	if err := r.Client.List(context.TODO(), namespaces, &client.ListOptions{}); err != nil {
  1378  		return nil, err
  1379  	}
  1380  
  1381  	for _, namespace := range namespaces.Items {
  1382  		if glob.MatchStringInList(cr.Spec.SourceNamespaces, namespace.Name, false) {
  1383  			sourceNamespaces = append(sourceNamespaces, namespace.Name)
  1384  		}
  1385  	}
  1386  
  1387  	return sourceNamespaces, nil
  1388  }
  1389  
  1390  func (r *ReconcileArgoCD) setManagedSourceNamespaces(cr *argoproj.ArgoCD) error {
  1391  	r.ManagedSourceNamespaces = make(map[string]string)
  1392  	namespaces := &corev1.NamespaceList{}
  1393  	listOption := client.MatchingLabels{
  1394  		common.ArgoCDManagedByClusterArgoCDLabel: cr.Namespace,
  1395  	}
  1396  
  1397  	// get the list of namespaces managed by the Argo CD instance
  1398  	if err := r.Client.List(context.TODO(), namespaces, listOption); err != nil {
  1399  		return err
  1400  	}
  1401  
  1402  	for _, namespace := range namespaces.Items {
  1403  		r.ManagedSourceNamespaces[namespace.Name] = ""
  1404  	}
  1405  
  1406  	return nil
  1407  }
  1408  
  1409  // removeUnmanagedSourceNamespaceResources cleansup resources from SourceNamespaces if namespace is not managed by argocd instance.
  1410  // It also removes the managed-by-cluster-argocd label from the namespace
  1411  func (r *ReconcileArgoCD) removeUnmanagedSourceNamespaceResources(cr *argoproj.ArgoCD) error {
  1412  
  1413  	for ns := range r.ManagedSourceNamespaces {
  1414  		managedNamespace := false
  1415  		if cr.GetDeletionTimestamp() == nil {
  1416  			sourceNamespaces, err := r.getSourceNamespaces(cr)
  1417  			if err != nil {
  1418  				return err
  1419  			}
  1420  			for _, namespace := range sourceNamespaces {
  1421  				if namespace == ns {
  1422  					managedNamespace = true
  1423  					break
  1424  				}
  1425  			}
  1426  		}
  1427  
  1428  		if !managedNamespace {
  1429  			if err := r.cleanupUnmanagedSourceNamespaceResources(cr, ns); err != nil {
  1430  				log.Error(err, fmt.Sprintf("error cleaning up resources for namespace %s", ns))
  1431  				continue
  1432  			}
  1433  			delete(r.ManagedSourceNamespaces, ns)
  1434  		}
  1435  	}
  1436  	return nil
  1437  }
  1438  
  1439  func (r *ReconcileArgoCD) cleanupUnmanagedSourceNamespaceResources(cr *argoproj.ArgoCD, ns string) error {
  1440  	namespace := corev1.Namespace{}
  1441  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: ns}, &namespace); err != nil {
  1442  		if !apierrors.IsNotFound(err) {
  1443  			return err
  1444  		}
  1445  		return nil
  1446  	}
  1447  	// Remove managed-by-cluster-argocd from the namespace
  1448  	delete(namespace.Labels, common.ArgoCDManagedByClusterArgoCDLabel)
  1449  	if err := r.Client.Update(context.TODO(), &namespace); err != nil {
  1450  		log.Error(err, fmt.Sprintf("failed to remove label from namespace [%s]", namespace.Name))
  1451  	}
  1452  
  1453  	// Delete Roles for SourceNamespaces
  1454  	existingRole := v1.Role{}
  1455  	roleName := getRoleNameForApplicationSourceNamespaces(namespace.Name, cr)
  1456  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleName, Namespace: namespace.Name}, &existingRole); err != nil {
  1457  		if !apierrors.IsNotFound(err) {
  1458  			return fmt.Errorf("failed to fetch the role for the service account associated with %s : %s", common.ArgoCDServerComponent, err)
  1459  		}
  1460  	}
  1461  	if existingRole.Name != "" {
  1462  		if err := r.Client.Delete(context.TODO(), &existingRole); err != nil {
  1463  			return err
  1464  		}
  1465  	}
  1466  	// Delete RoleBindings for SourceNamespaces
  1467  	existingRoleBinding := &v1.RoleBinding{}
  1468  	roleBindingName := getRoleBindingNameForSourceNamespaces(cr.Name, namespace.Name)
  1469  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBindingName, Namespace: namespace.Name}, existingRoleBinding); err != nil {
  1470  		if !apierrors.IsNotFound(err) {
  1471  			return fmt.Errorf("failed to get the rolebinding associated with %s : %s", common.ArgoCDServerComponent, err)
  1472  		}
  1473  	}
  1474  	if existingRoleBinding.Name != "" {
  1475  		if err := r.Client.Delete(context.TODO(), existingRoleBinding); err != nil {
  1476  			return err
  1477  		}
  1478  	}
  1479  	return nil
  1480  }
  1481  
  1482  func isProxyCluster() bool {
  1483  	cfg, err := config.GetConfig()
  1484  	if err != nil {
  1485  		log.Error(err, "failed to get k8s config")
  1486  	}
  1487  
  1488  	// Initialize config client.
  1489  	configClient, err := configv1client.NewForConfig(cfg)
  1490  	if err != nil {
  1491  		log.Error(err, "failed to initialize openshift config client")
  1492  		return false
  1493  	}
  1494  
  1495  	proxy, err := configClient.Proxies().Get(context.TODO(), "cluster", metav1.GetOptions{})
  1496  	if err != nil {
  1497  		log.Error(err, "failed to get proxy configuration")
  1498  		return false
  1499  	}
  1500  
  1501  	if proxy.Spec.HTTPSProxy != "" {
  1502  		log.Info("proxy configuration detected")
  1503  		return true
  1504  	}
  1505  
  1506  	return false
  1507  }
  1508  
  1509  func getOpenShiftAPIURL() string {
  1510  	k8s, err := initK8sClient()
  1511  	if err != nil {
  1512  		log.Error(err, "failed to initialize k8s client")
  1513  	}
  1514  
  1515  	cm, err := k8s.CoreV1().ConfigMaps("openshift-console").Get(context.TODO(), "console-config", metav1.GetOptions{})
  1516  	if err != nil {
  1517  		log.Error(err, "")
  1518  	}
  1519  
  1520  	var cf string
  1521  	if v, ok := cm.Data["console-config.yaml"]; ok {
  1522  		cf = v
  1523  	}
  1524  
  1525  	data := make(map[string]interface{})
  1526  	err = yaml.Unmarshal([]byte(cf), data)
  1527  	if err != nil {
  1528  		log.Error(err, "")
  1529  	}
  1530  
  1531  	var apiURL interface{}
  1532  	var out string
  1533  	if c, ok := data["clusterInfo"]; ok {
  1534  		ci, _ := c.(map[interface{}]interface{})
  1535  
  1536  		apiURL = ci["masterPublicURL"]
  1537  		out = fmt.Sprintf("%v", apiURL)
  1538  	}
  1539  
  1540  	return out
  1541  }
  1542  
  1543  func AddSeccompProfileForOpenShift(client client.Client, podspec *corev1.PodSpec) {
  1544  	if !IsVersionAPIAvailable() {
  1545  		return
  1546  	}
  1547  	version, err := getClusterVersion(client)
  1548  	if err != nil {
  1549  		log.Error(err, "couldn't get OpenShift version")
  1550  	}
  1551  	if version == "" || semver.Compare(fmt.Sprintf("v%s", version), "v4.10.999") > 0 {
  1552  		if podspec.SecurityContext == nil {
  1553  			podspec.SecurityContext = &corev1.PodSecurityContext{}
  1554  		}
  1555  		if podspec.SecurityContext.SeccompProfile == nil {
  1556  			podspec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{}
  1557  		}
  1558  		if len(podspec.SecurityContext.SeccompProfile.Type) == 0 {
  1559  			podspec.SecurityContext.SeccompProfile.Type = corev1.SeccompProfileTypeRuntimeDefault
  1560  		}
  1561  	}
  1562  }
  1563  
  1564  // getClusterVersion returns the OpenShift Cluster version in which the operator is installed
  1565  func getClusterVersion(client client.Client) (string, error) {
  1566  	if !IsVersionAPIAvailable() {
  1567  		return "", nil
  1568  	}
  1569  	clusterVersion := &configv1.ClusterVersion{}
  1570  	err := client.Get(context.TODO(), types.NamespacedName{Name: "version"}, clusterVersion)
  1571  	if err != nil {
  1572  		if apierrors.IsNotFound(err) {
  1573  			return "", nil
  1574  		}
  1575  		return "", err
  1576  	}
  1577  	return clusterVersion.Status.Desired.Version, nil
  1578  }
  1579  
  1580  // generateRandomBytes returns a securely generated random bytes.
  1581  func generateRandomBytes(n int) []byte {
  1582  	b := make([]byte, n)
  1583  	_, err := rand.Read(b)
  1584  	if err != nil {
  1585  		log.Error(err, "")
  1586  	}
  1587  	return b
  1588  }
  1589  
  1590  // generateRandomString returns a securely generated random string.
  1591  func generateRandomString(s int) string {
  1592  	b := generateRandomBytes(s)
  1593  	return base64.URLEncoding.EncodeToString(b)
  1594  }
  1595  
  1596  // contains returns true if a string is part of the given slice.
  1597  func contains(s []string, g string) bool {
  1598  	for _, a := range s {
  1599  		if a == g {
  1600  			return true
  1601  		}
  1602  	}
  1603  	return false
  1604  }
  1605  
  1606  // getApplicationSetHTTPServerHost will return the host for the given ArgoCD.
  1607  func getApplicationSetHTTPServerHost(cr *argoproj.ArgoCD) (string, error) {
  1608  	host := cr.Name
  1609  	if len(cr.Spec.ApplicationSet.WebhookServer.Host) > 0 {
  1610  		hostname, err := shortenHostname(cr.Spec.ApplicationSet.WebhookServer.Host)
  1611  		if err != nil {
  1612  			return "", err
  1613  		}
  1614  		host = hostname
  1615  	}
  1616  	return host, nil
  1617  }