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

     1  // Copyright 2021 ArgoCD Operator Developers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // 	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package argocd
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"reflect"
    23  	"strings"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	v1 "k8s.io/api/rbac/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	amerr "k8s.io/apimachinery/pkg/util/errors"
    31  	"k8s.io/apimachinery/pkg/util/intstr"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    34  
    35  	argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
    36  	"github.com/argoproj-labs/argocd-operator/common"
    37  	"github.com/argoproj-labs/argocd-operator/controllers/argoutil"
    38  )
    39  
    40  const (
    41  	ApplicationSetGitlabSCMTlsCertPath = "/app/tls/scm/cert"
    42  )
    43  
    44  // getArgoApplicationSetCommand will return the command for the ArgoCD ApplicationSet component.
    45  func (r *ReconcileArgoCD) getArgoApplicationSetCommand(cr *argoproj.ArgoCD) []string {
    46  	cmd := make([]string, 0)
    47  
    48  	cmd = append(cmd, "entrypoint.sh")
    49  	cmd = append(cmd, "argocd-applicationset-controller")
    50  
    51  	if cr.Spec.Repo.IsEnabled() {
    52  		cmd = append(cmd, "--argocd-repo-server", getRepoServerAddress(cr))
    53  	} else {
    54  		log.Info("Repo Server is disabled. This would affect the functioning of ApplicationSet Controller.")
    55  	}
    56  
    57  	cmd = append(cmd, "--loglevel")
    58  	cmd = append(cmd, getLogLevel(cr.Spec.ApplicationSet.LogLevel))
    59  
    60  	if cr.Spec.ApplicationSet.SCMRootCAConfigMap != "" {
    61  		cmd = append(cmd, "--scm-root-ca-path")
    62  		cmd = append(cmd, ApplicationSetGitlabSCMTlsCertPath)
    63  	}
    64  
    65  	// appset source namespaces should be subset of apps source namespaces
    66  	appsetsSourceNamespaces := []string{}
    67  	appsNamespaces, err := r.getSourceNamespaces(cr)
    68  	if err == nil {
    69  		for _, ns := range cr.Spec.ApplicationSet.SourceNamespaces {
    70  			if contains(appsNamespaces, ns) {
    71  				appsetsSourceNamespaces = append(appsetsSourceNamespaces, ns)
    72  			} else {
    73  				log.V(1).Info(fmt.Sprintf("Apps in target sourceNamespace %s is not enabled, thus skipping the namespace in deployment command.", ns))
    74  			}
    75  		}
    76  	}
    77  
    78  	if len(appsetsSourceNamespaces) > 0 {
    79  		cmd = append(cmd, "--applicationset-namespaces", fmt.Sprint(strings.Join(appsetsSourceNamespaces, ",")))
    80  	}
    81  
    82  	if len(cr.Spec.ApplicationSet.SCMProviders) > 0 {
    83  		cmd = append(cmd, "--allowed-scm-providers", fmt.Sprint(strings.Join(cr.Spec.ApplicationSet.SCMProviders, ",")))
    84  	}
    85  
    86  	// appset in any ns is enabled and no scmProviders allow list is specified,
    87  	// disables scm & PR generators to prevent potential security issues
    88  	// https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Appset-Any-Namespace/#scm-providers-secrets-consideration
    89  	if len(appsetsSourceNamespaces) > 0 && !(len(cr.Spec.ApplicationSet.SCMProviders) > 0) {
    90  		cmd = append(cmd, "--enable-scm-providers=false")
    91  	}
    92  
    93  	// ApplicationSet command arguments provided by the user
    94  	extraArgs := cr.Spec.ApplicationSet.ExtraCommandArgs
    95  	err = isMergable(extraArgs, cmd)
    96  	if err != nil {
    97  		return cmd
    98  	}
    99  
   100  	cmd = append(cmd, extraArgs...)
   101  
   102  	return cmd
   103  }
   104  
   105  func (r *ReconcileArgoCD) reconcileApplicationSetController(cr *argoproj.ArgoCD) error {
   106  
   107  	log.Info("reconciling applicationset serviceaccounts")
   108  	sa, err := r.reconcileApplicationSetServiceAccount(cr)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	log.Info("reconciling applicationset roles")
   114  	role, err := r.reconcileApplicationSetRole(cr)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	log.Info("reconciling applicationset role bindings")
   120  	if err := r.reconcileApplicationSetRoleBinding(cr, role, sa); err != nil {
   121  		return err
   122  	}
   123  
   124  	log.Info("reconciling applicationset deployments")
   125  	if err := r.reconcileApplicationSetDeployment(cr, sa); err != nil {
   126  		return err
   127  	}
   128  
   129  	log.Info("reconciling applicationset service")
   130  	if err := r.reconcileApplicationSetService(cr); err != nil {
   131  		return err
   132  	}
   133  
   134  	// create clusterrole & clusterrolebinding if cluster-scoped ArgoCD
   135  	log.Info("reconciling applicationset clusterroles")
   136  	clusterrole, err := r.reconcileApplicationSetClusterRole(cr)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	log.Info("reconciling applicationset clusterrolebindings")
   142  	if err := r.reconcileApplicationSetClusterRoleBinding(cr, clusterrole, sa); err != nil {
   143  		return err
   144  	}
   145  
   146  	// reconcile source namespace roles & rolebindings
   147  	log.Info("reconciling applicationset roles & rolebindings in source namespaces")
   148  	if err := r.reconcileApplicationSetSourceNamespacesResources(cr); err != nil {
   149  		return err
   150  	}
   151  
   152  	// remove resources for namespaces not part of SourceNamespaces
   153  	log.Info("performing cleanup for applicationset source namespaces")
   154  	if err := r.removeUnmanagedApplicationSetSourceNamespaceResources(cr); err != nil {
   155  		return err
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // reconcileApplicationControllerDeployment will ensure the Deployment resource is present for the ArgoCD Application Controller component.
   162  func (r *ReconcileArgoCD) reconcileApplicationSetDeployment(cr *argoproj.ArgoCD, sa *corev1.ServiceAccount) error {
   163  
   164  	exists := false
   165  	existing := newDeploymentWithSuffix("applicationset-controller", "controller", cr)
   166  	if argoutil.IsObjectFound(r.Client, cr.Namespace, existing.Name, existing) {
   167  		exists = true
   168  	}
   169  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   170  		if exists {
   171  			return r.Client.Delete(context.TODO(), existing)
   172  		}
   173  		return nil
   174  	}
   175  
   176  	deploy := newDeploymentWithSuffix("applicationset-controller", "controller", cr)
   177  
   178  	setAppSetLabels(&deploy.ObjectMeta)
   179  
   180  	podSpec := &deploy.Spec.Template.Spec
   181  
   182  	// sa would be nil when spec.applicationset.enabled = false
   183  	if sa != nil {
   184  		podSpec.ServiceAccountName = sa.ObjectMeta.Name
   185  	}
   186  	podSpec.Volumes = []corev1.Volume{
   187  		{
   188  			Name: "ssh-known-hosts",
   189  			VolumeSource: corev1.VolumeSource{
   190  				ConfigMap: &corev1.ConfigMapVolumeSource{
   191  					LocalObjectReference: corev1.LocalObjectReference{
   192  						Name: common.ArgoCDKnownHostsConfigMapName,
   193  					},
   194  				},
   195  			},
   196  		},
   197  		{
   198  			Name: "tls-certs",
   199  			VolumeSource: corev1.VolumeSource{
   200  				ConfigMap: &corev1.ConfigMapVolumeSource{
   201  					LocalObjectReference: corev1.LocalObjectReference{
   202  						Name: common.ArgoCDTLSCertsConfigMapName,
   203  					},
   204  				},
   205  			},
   206  		},
   207  		{
   208  			Name: "gpg-keys",
   209  			VolumeSource: corev1.VolumeSource{
   210  				ConfigMap: &corev1.ConfigMapVolumeSource{
   211  					LocalObjectReference: corev1.LocalObjectReference{
   212  						Name: common.ArgoCDGPGKeysConfigMapName,
   213  					},
   214  				},
   215  			},
   216  		},
   217  		{
   218  			Name: "gpg-keyring",
   219  			VolumeSource: corev1.VolumeSource{
   220  				EmptyDir: &corev1.EmptyDirVolumeSource{},
   221  			},
   222  		},
   223  		{
   224  			Name: "tmp",
   225  			VolumeSource: corev1.VolumeSource{
   226  				EmptyDir: &corev1.EmptyDirVolumeSource{},
   227  			},
   228  		},
   229  	}
   230  	addSCMGitlabVolumeMount := false
   231  	if scmRootCAConfigMapName := getSCMRootCAConfigMapName(cr); scmRootCAConfigMapName != "" {
   232  		cm := newConfigMapWithName(scmRootCAConfigMapName, cr)
   233  		if argoutil.IsObjectFound(r.Client, cr.Namespace, cr.Spec.ApplicationSet.SCMRootCAConfigMap, cm) {
   234  			addSCMGitlabVolumeMount = true
   235  			podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{
   236  				Name: "appset-gitlab-scm-tls-cert",
   237  				VolumeSource: corev1.VolumeSource{
   238  					ConfigMap: &corev1.ConfigMapVolumeSource{
   239  						LocalObjectReference: corev1.LocalObjectReference{
   240  							Name: common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName,
   241  						},
   242  					},
   243  				},
   244  			})
   245  		}
   246  	}
   247  
   248  	podSpec.Containers = []corev1.Container{
   249  		r.applicationSetContainer(cr, addSCMGitlabVolumeMount),
   250  	}
   251  	AddSeccompProfileForOpenShift(r.Client, podSpec)
   252  
   253  	if exists {
   254  
   255  		existingSpec := existing.Spec.Template.Spec
   256  
   257  		deploymentsDifferent := !reflect.DeepEqual(existingSpec.Containers[0], podSpec.Containers) ||
   258  			!reflect.DeepEqual(existingSpec.Volumes, podSpec.Volumes) ||
   259  			existingSpec.ServiceAccountName != podSpec.ServiceAccountName ||
   260  			!reflect.DeepEqual(existing.Labels, deploy.Labels) ||
   261  			!reflect.DeepEqual(existing.Spec.Template.Labels, deploy.Spec.Template.Labels) ||
   262  			!reflect.DeepEqual(existing.Spec.Selector, deploy.Spec.Selector) ||
   263  			!reflect.DeepEqual(existing.Spec.Template.Spec.NodeSelector, deploy.Spec.Template.Spec.NodeSelector) ||
   264  			!reflect.DeepEqual(existing.Spec.Template.Spec.Tolerations, deploy.Spec.Template.Spec.Tolerations)
   265  
   266  		// If the Deployment already exists, make sure the values we care about are up-to-date
   267  		if deploymentsDifferent {
   268  			existing.Spec.Template.Spec.Containers = podSpec.Containers
   269  			existing.Spec.Template.Spec.Volumes = podSpec.Volumes
   270  			existing.Spec.Template.Spec.ServiceAccountName = podSpec.ServiceAccountName
   271  			existing.Labels = deploy.Labels
   272  			existing.Spec.Template.Labels = deploy.Spec.Template.Labels
   273  			existing.Spec.Selector = deploy.Spec.Selector
   274  			existing.Spec.Template.Spec.NodeSelector = deploy.Spec.Template.Spec.NodeSelector
   275  			existing.Spec.Template.Spec.Tolerations = deploy.Spec.Template.Spec.Tolerations
   276  			return r.Client.Update(context.TODO(), existing)
   277  		}
   278  		return nil // Deployment found with nothing to do, move along...
   279  	}
   280  
   281  	if !cr.Spec.ApplicationSet.IsEnabled() {
   282  		return nil
   283  	}
   284  
   285  	if err := controllerutil.SetControllerReference(cr, deploy, r.Scheme); err != nil {
   286  		return err
   287  	}
   288  	return r.Client.Create(context.TODO(), deploy)
   289  
   290  }
   291  
   292  func (r *ReconcileArgoCD) applicationSetContainer(cr *argoproj.ArgoCD, addSCMGitlabVolumeMount bool) corev1.Container {
   293  	// Global proxy env vars go first
   294  	appSetEnv := []corev1.EnvVar{{
   295  		Name: "NAMESPACE",
   296  		ValueFrom: &corev1.EnvVarSource{
   297  			FieldRef: &corev1.ObjectFieldSelector{
   298  				FieldPath: "metadata.namespace",
   299  			},
   300  		},
   301  	}}
   302  
   303  	// Merge ApplicationSet env vars provided by the user
   304  	// User should be able to override the default NAMESPACE environmental variable
   305  	appSetEnv = argoutil.EnvMerge(cr.Spec.ApplicationSet.Env, appSetEnv, true)
   306  	// Environment specified in the CR take precedence over everything else
   307  	appSetEnv = argoutil.EnvMerge(appSetEnv, proxyEnvVars(), false)
   308  
   309  	container := corev1.Container{
   310  		Command:         r.getArgoApplicationSetCommand(cr),
   311  		Env:             appSetEnv,
   312  		Image:           getApplicationSetContainerImage(cr),
   313  		ImagePullPolicy: corev1.PullAlways,
   314  		Name:            "argocd-applicationset-controller",
   315  		Resources:       getApplicationSetResources(cr),
   316  		VolumeMounts: []corev1.VolumeMount{
   317  			{
   318  				Name:      "ssh-known-hosts",
   319  				MountPath: "/app/config/ssh",
   320  			},
   321  			{
   322  				Name:      "tls-certs",
   323  				MountPath: "/app/config/tls",
   324  			},
   325  			{
   326  				Name:      "gpg-keys",
   327  				MountPath: "/app/config/gpg/source",
   328  			},
   329  			{
   330  				Name:      "gpg-keyring",
   331  				MountPath: "/app/config/gpg/keys",
   332  			},
   333  			{
   334  				Name:      "tmp",
   335  				MountPath: "/tmp",
   336  			},
   337  		},
   338  		Ports: []corev1.ContainerPort{
   339  			{
   340  				ContainerPort: 7000,
   341  				Name:          "webhook",
   342  			},
   343  			{
   344  				ContainerPort: 8080,
   345  				Name:          "metrics",
   346  			},
   347  		},
   348  		SecurityContext: &corev1.SecurityContext{
   349  			Capabilities: &corev1.Capabilities{
   350  				Drop: []corev1.Capability{
   351  					"ALL",
   352  				},
   353  			},
   354  			AllowPrivilegeEscalation: boolPtr(false),
   355  			ReadOnlyRootFilesystem:   boolPtr(true),
   356  			RunAsNonRoot:             boolPtr(true),
   357  		},
   358  	}
   359  	if addSCMGitlabVolumeMount {
   360  		container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
   361  			Name:      "appset-gitlab-scm-tls-cert",
   362  			MountPath: ApplicationSetGitlabSCMTlsCertPath,
   363  		})
   364  	}
   365  	return container
   366  }
   367  
   368  func (r *ReconcileArgoCD) reconcileApplicationSetServiceAccount(cr *argoproj.ArgoCD) (*corev1.ServiceAccount, error) {
   369  
   370  	sa := newServiceAccountWithName("applicationset-controller", cr)
   371  	setAppSetLabels(&sa.ObjectMeta)
   372  
   373  	exists := true
   374  	if err := argoutil.FetchObject(r.Client, cr.Namespace, sa.Name, sa); err != nil {
   375  		if !apierrors.IsNotFound(err) {
   376  			return sa, err
   377  		}
   378  		exists = false
   379  	}
   380  
   381  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   382  		if exists {
   383  			err := r.Client.Delete(context.TODO(), sa)
   384  			if err != nil {
   385  				if !apierrors.IsNotFound(err) {
   386  					return sa, err
   387  				}
   388  			}
   389  		}
   390  		return sa, nil
   391  	}
   392  
   393  	if !exists {
   394  		if err := controllerutil.SetControllerReference(cr, sa, r.Scheme); err != nil {
   395  			return sa, err
   396  		}
   397  
   398  		err := r.Client.Create(context.TODO(), sa)
   399  		if err != nil {
   400  			return sa, err
   401  		}
   402  	}
   403  
   404  	return sa, nil
   405  }
   406  
   407  // reconcileApplicationSetClusterRoleBinding reconciles required clusterrole for appset controller when ArgoCD is cluster-scoped
   408  func (r *ReconcileArgoCD) reconcileApplicationSetClusterRole(cr *argoproj.ArgoCD) (*v1.ClusterRole, error) {
   409  
   410  	allowed := false
   411  	if allowedNamespace(cr.Namespace, os.Getenv("ARGOCD_CLUSTER_CONFIG_NAMESPACES")) {
   412  		allowed = true
   413  	}
   414  
   415  	// controller disabled, don't create resources
   416  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   417  		allowed = false
   418  	}
   419  
   420  	policyRules := []v1.PolicyRule{
   421  		// ApplicationSet
   422  		{
   423  			APIGroups: []string{"argoproj.io"},
   424  			Resources: []string{
   425  				"applications",
   426  				"applicationsets",
   427  			},
   428  			Verbs: []string{
   429  				"list",
   430  				"watch",
   431  			},
   432  		},
   433  		// Secrets
   434  		{
   435  			APIGroups: []string{""},
   436  			Resources: []string{
   437  				"secrets",
   438  			},
   439  			Verbs: []string{
   440  				"list",
   441  				"watch",
   442  			},
   443  		},
   444  	}
   445  
   446  	clusterRole := newClusterRole(common.ArgoCDApplicationSetControllerComponent, policyRules, cr)
   447  	if err := applyReconcilerHook(cr, clusterRole, ""); err != nil {
   448  		return nil, err
   449  	}
   450  
   451  	existingClusterRole := &v1.ClusterRole{}
   452  	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: clusterRole.Name}, existingClusterRole)
   453  	if err != nil {
   454  		if !apierrors.IsNotFound(err) {
   455  			return nil, fmt.Errorf("failed to reconcile the cluster role for the service account associated with %s : %s", clusterRole.Name, err)
   456  		}
   457  		if !allowed {
   458  			// Do Nothing
   459  			return clusterRole, nil
   460  		}
   461  		return clusterRole, r.Client.Create(context.TODO(), clusterRole)
   462  	}
   463  
   464  	// ArgoCD not cluster scoped, cleanup any existing resource and exit
   465  	if !allowed {
   466  		err := r.Client.Delete(context.TODO(), existingClusterRole)
   467  		if err != nil {
   468  			if !apierrors.IsNotFound(err) {
   469  				return existingClusterRole, err
   470  			}
   471  		}
   472  		return existingClusterRole, nil
   473  	}
   474  
   475  	// if the Rules differ, update the Role
   476  	if !reflect.DeepEqual(existingClusterRole.Rules, clusterRole.Rules) {
   477  		existingClusterRole.Rules = clusterRole.Rules
   478  		if err := r.Client.Update(context.TODO(), existingClusterRole); err != nil {
   479  			return nil, err
   480  		}
   481  	}
   482  	return existingClusterRole, nil
   483  }
   484  
   485  // reconcileApplicationSetClusterRoleBinding reconciles required clusterrolebinding for appset controller when ArgoCD is cluster-scoped
   486  func (r *ReconcileArgoCD) reconcileApplicationSetClusterRoleBinding(cr *argoproj.ArgoCD, role *v1.ClusterRole, sa *corev1.ServiceAccount) error {
   487  
   488  	allowed := false
   489  	if allowedNamespace(cr.Namespace, os.Getenv("ARGOCD_CLUSTER_CONFIG_NAMESPACES")) {
   490  		allowed = true
   491  	}
   492  
   493  	// controller disabled, don't create resources
   494  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   495  		allowed = false
   496  	}
   497  
   498  	clusterRB := newClusterRoleBindingWithname(common.ArgoCDApplicationSetControllerComponent, cr)
   499  	clusterRB.Subjects = []v1.Subject{
   500  		{
   501  			Kind:      v1.ServiceAccountKind,
   502  			Name:      sa.Name,
   503  			Namespace: cr.Namespace,
   504  		},
   505  	}
   506  	clusterRB.RoleRef = v1.RoleRef{
   507  		APIGroup: v1.GroupName,
   508  		Kind:     "ClusterRole",
   509  		Name:     role.Name,
   510  	}
   511  	if err := applyReconcilerHook(cr, clusterRB, ""); err != nil {
   512  		return err
   513  	}
   514  
   515  	existingClusterRB := &v1.ClusterRoleBinding{}
   516  	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: clusterRB.Name}, existingClusterRB)
   517  	if err != nil {
   518  		if !apierrors.IsNotFound(err) {
   519  			return fmt.Errorf("failed to reconcile the cluster rolebinding for the service account associated with %s : %s", clusterRB.Name, err)
   520  		}
   521  		if !allowed {
   522  			// Do Nothing
   523  			return nil
   524  		}
   525  		return r.Client.Create(context.TODO(), clusterRB)
   526  	}
   527  
   528  	// ArgoCD not cluster scoped, cleanup any existing resource and exit
   529  	if !allowed {
   530  		err := r.Client.Delete(context.TODO(), existingClusterRB)
   531  		if err != nil {
   532  			if !apierrors.IsNotFound(err) {
   533  				return err
   534  			}
   535  		}
   536  		return nil
   537  	}
   538  
   539  	// if subj differ, update the rolebinding
   540  	if !reflect.DeepEqual(existingClusterRB.Subjects, clusterRB.Subjects) {
   541  		existingClusterRB.Subjects = clusterRB.Subjects
   542  		if err := r.Client.Update(context.TODO(), existingClusterRB); err != nil {
   543  			return err
   544  		}
   545  	} else if !reflect.DeepEqual(existingClusterRB.RoleRef, clusterRB.RoleRef) {
   546  		// RoleRef can't be updated, delete the rolebinding so that it gets recreated
   547  		_ = r.Client.Delete(context.TODO(), existingClusterRB)
   548  		return fmt.Errorf("change detected in roleRef for rolebinding %s of Argo CD instance %s in namespace %s", existingClusterRB.Name, cr.Name, existingClusterRB.Namespace)
   549  	}
   550  	return nil
   551  }
   552  
   553  // reconcileApplicationSetSourceNamespacesResources creates role & rolebinding in target source namespaces for appset controller
   554  // Appset resources are only created if target source ns is subset of apps source namespaces
   555  func (r *ReconcileArgoCD) reconcileApplicationSetSourceNamespacesResources(cr *argoproj.ArgoCD) error {
   556  
   557  	var reconciliationErrors []error
   558  
   559  	// controller disabled, nothing to do. cleanup handled by removeUnmanagedApplicationSetSourceNamespaceResources()
   560  	if cr.Spec.ApplicationSet == nil {
   561  		return nil
   562  	}
   563  
   564  	// create resources for each appset source namespace
   565  	for _, sourceNamespace := range cr.Spec.ApplicationSet.SourceNamespaces {
   566  
   567  		// source ns should be part of app-in-any-ns
   568  		appsNamespaces, err := r.getSourceNamespaces(cr)
   569  		if err != nil {
   570  			reconciliationErrors = append(reconciliationErrors, err)
   571  			continue
   572  		}
   573  		if !contains(appsNamespaces, sourceNamespace) {
   574  			log.Error(fmt.Errorf("skipping reconciliation of resources for sourceNamespace %s as Apps in target sourceNamespace is not enabled", sourceNamespace), "Warning")
   575  			continue
   576  		}
   577  
   578  		// skip source ns if doesn't exist
   579  		namespace := &corev1.Namespace{}
   580  		if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: sourceNamespace}, namespace); err != nil {
   581  			errMsg := fmt.Errorf("failed to retrieve namespace %s", sourceNamespace)
   582  			reconciliationErrors = append(reconciliationErrors, errors.Join(errMsg, err))
   583  			continue
   584  		}
   585  
   586  		// No namespace can be managed by multiple argo-cd instances (cluster scoped or namespace scoped)
   587  		// i.e, only one of either managed-by or applicationset-managed-by-cluster-argocd labels can be applied to a given namespace.
   588  		// Since appset-in-any-ns is in beta, we prioritize managed-by label in case of a conflict.
   589  		if value, ok := namespace.Labels[common.ArgoCDManagedByLabel]; ok && value != "" {
   590  			log.Info(fmt.Sprintf("Skipping reconciling resources for namespace %s as it is already managed-by namespace %s.", namespace.Name, value))
   591  			// remove any source namespace resources
   592  			if val, ok1 := namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel]; ok1 && val != cr.Namespace {
   593  				delete(r.ManagedApplicationSetSourceNamespaces, namespace.Name)
   594  				if err := r.cleanupUnmanagedApplicationSetSourceNamespaceResources(cr, namespace.Name); err != nil {
   595  					log.Error(err, fmt.Sprintf("error cleaning up resources for namespace %s", namespace.Name))
   596  				}
   597  			}
   598  			continue
   599  		}
   600  
   601  		log.Info(fmt.Sprintf("Reconciling applicationset resources for %s", namespace.Name))
   602  		// add applicationset-managed-by-cluster-argocd label on namespace
   603  		if _, ok := namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel]; !ok {
   604  			// Get the latest value of namespace before updating it
   605  			if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: namespace.Name}, namespace); err != nil {
   606  				return err
   607  			}
   608  			// Update namespace with applicationset-managed-by-cluster-argocd label
   609  			if namespace.Labels == nil {
   610  				namespace.Labels = make(map[string]string)
   611  			}
   612  			namespace.Labels[common.ArgoCDApplicationSetManagedByClusterArgoCDLabel] = cr.Namespace
   613  			if err := r.Client.Update(context.TODO(), namespace); err != nil {
   614  				log.Error(err, fmt.Sprintf("failed to add label from namespace [%s]", namespace.Name))
   615  			}
   616  		}
   617  
   618  		// role & rolebinding for applicationset controller in source namespace
   619  		role := v1.Role{
   620  			ObjectMeta: metav1.ObjectMeta{
   621  				Name:      getResourceNameForApplicationSetSourceNamespaces(cr),
   622  				Namespace: sourceNamespace,
   623  				Labels:    argoutil.LabelsForCluster(cr),
   624  			},
   625  			Rules: policyRuleForApplicationSetController(),
   626  		}
   627  		err = r.reconcileSourceNamespaceRole(role, cr)
   628  		if err != nil {
   629  			reconciliationErrors = append(reconciliationErrors, err)
   630  		}
   631  
   632  		roleBinding := v1.RoleBinding{
   633  			ObjectMeta: metav1.ObjectMeta{
   634  				Name:        getResourceNameForApplicationSetSourceNamespaces(cr),
   635  				Labels:      argoutil.LabelsForCluster(cr),
   636  				Annotations: argoutil.AnnotationsForCluster(cr),
   637  				Namespace:   sourceNamespace,
   638  			},
   639  			RoleRef: v1.RoleRef{
   640  				APIGroup: v1.GroupName,
   641  				Kind:     "Role",
   642  				Name:     getResourceNameForApplicationSetSourceNamespaces(cr),
   643  			},
   644  			Subjects: []v1.Subject{
   645  				{
   646  					Kind:      v1.ServiceAccountKind,
   647  					Name:      getServiceAccountName(cr.Name, "applicationset-controller"),
   648  					Namespace: cr.Namespace,
   649  				},
   650  			},
   651  		}
   652  		err = r.reconcileSourceNamespaceRoleBinding(roleBinding, cr)
   653  		if err != nil {
   654  			reconciliationErrors = append(reconciliationErrors, err)
   655  		}
   656  
   657  		// appset permissions for argocd server in source namespaces are handled by apps-in-any-ns code
   658  
   659  		if _, ok := r.ManagedApplicationSetSourceNamespaces[sourceNamespace]; !ok {
   660  			if r.ManagedApplicationSetSourceNamespaces == nil {
   661  				r.ManagedApplicationSetSourceNamespaces = make(map[string]string)
   662  			}
   663  			r.ManagedApplicationSetSourceNamespaces[sourceNamespace] = ""
   664  		}
   665  	}
   666  
   667  	return amerr.NewAggregate(reconciliationErrors)
   668  }
   669  
   670  func (r *ReconcileArgoCD) reconcileApplicationSetRole(cr *argoproj.ArgoCD) (*v1.Role, error) {
   671  
   672  	policyRules := policyRuleForApplicationSetController()
   673  
   674  	role := newRole("applicationset-controller", policyRules, cr)
   675  	setAppSetLabels(&role.ObjectMeta)
   676  
   677  	exists := true
   678  	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: cr.Namespace}, role)
   679  	if err != nil {
   680  		if !apierrors.IsNotFound(err) {
   681  			return role, err
   682  		}
   683  		exists = false
   684  	}
   685  
   686  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   687  		if exists {
   688  			if err := r.Client.Delete(context.TODO(), role); err != nil {
   689  				if !apierrors.IsNotFound(err) {
   690  					return role, err
   691  				}
   692  			}
   693  		}
   694  		return role, nil
   695  	}
   696  
   697  	role.Rules = policyRules
   698  	if err = controllerutil.SetControllerReference(cr, role, r.Scheme); err != nil {
   699  		return role, err
   700  	}
   701  	if exists {
   702  		return role, r.Client.Update(context.TODO(), role)
   703  	} else {
   704  		return role, r.Client.Create(context.TODO(), role)
   705  	}
   706  
   707  }
   708  
   709  func (r *ReconcileArgoCD) reconcileApplicationSetRoleBinding(cr *argoproj.ArgoCD, role *v1.Role, sa *corev1.ServiceAccount) error {
   710  
   711  	name := "applicationset-controller"
   712  
   713  	// get expected name
   714  	roleBinding := newRoleBindingWithname(name, cr)
   715  
   716  	// fetch existing rolebinding by name
   717  	roleBindingExists := true
   718  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: cr.Namespace}, roleBinding); err != nil {
   719  		if !apierrors.IsNotFound(err) {
   720  			return fmt.Errorf("failed to get the rolebinding associated with %s : %s", name, err)
   721  		}
   722  		roleBindingExists = false
   723  	}
   724  
   725  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   726  		if roleBindingExists {
   727  			return r.Client.Delete(context.TODO(), roleBinding)
   728  		}
   729  		return nil
   730  	}
   731  
   732  	setAppSetLabels(&roleBinding.ObjectMeta)
   733  
   734  	roleBinding.RoleRef = v1.RoleRef{
   735  		APIGroup: v1.GroupName,
   736  		Kind:     "Role",
   737  		Name:     role.Name,
   738  	}
   739  
   740  	roleBinding.Subjects = []v1.Subject{
   741  		{
   742  			Kind:      v1.ServiceAccountKind,
   743  			Name:      sa.Name,
   744  			Namespace: sa.Namespace,
   745  		},
   746  	}
   747  
   748  	if err := controllerutil.SetControllerReference(cr, roleBinding, r.Scheme); err != nil {
   749  		return err
   750  	}
   751  
   752  	if roleBindingExists {
   753  		return r.Client.Update(context.TODO(), roleBinding)
   754  	}
   755  
   756  	return r.Client.Create(context.TODO(), roleBinding)
   757  }
   758  
   759  func getApplicationSetContainerImage(cr *argoproj.ArgoCD) string {
   760  	defaultImg, defaultTag := false, false
   761  
   762  	img := ""
   763  	tag := ""
   764  
   765  	// First pull from spec, if it exists
   766  	if cr.Spec.ApplicationSet != nil {
   767  		img = cr.Spec.ApplicationSet.Image
   768  		tag = cr.Spec.ApplicationSet.Version
   769  	}
   770  
   771  	// If spec is empty, use the defaults
   772  	if img == "" {
   773  		img = common.ArgoCDDefaultArgoImage
   774  		defaultImg = true
   775  	}
   776  	if tag == "" {
   777  		tag = common.ArgoCDDefaultArgoVersion
   778  		defaultTag = true
   779  	}
   780  
   781  	// If an env var is specified then use that, but don't override the spec values (if they are present)
   782  	if e := os.Getenv(common.ArgoCDImageEnvName); e != "" && (defaultTag && defaultImg) {
   783  		return e
   784  	}
   785  	return argoutil.CombineImageTag(img, tag)
   786  }
   787  
   788  // getApplicationSetResources will return the ResourceRequirements for the Application Sets container.
   789  func getApplicationSetResources(cr *argoproj.ArgoCD) corev1.ResourceRequirements {
   790  	resources := corev1.ResourceRequirements{}
   791  
   792  	// Allow override of resource requirements from CR
   793  	if cr.Spec.ApplicationSet.Resources != nil {
   794  		resources = *cr.Spec.ApplicationSet.Resources
   795  	}
   796  
   797  	return resources
   798  }
   799  
   800  func setAppSetLabels(obj *metav1.ObjectMeta) {
   801  	obj.Labels["app.kubernetes.io/name"] = "argocd-applicationset-controller"
   802  	obj.Labels["app.kubernetes.io/part-of"] = "argocd-applicationset"
   803  	obj.Labels["app.kubernetes.io/component"] = "controller"
   804  }
   805  
   806  // reconcileApplicationSetService will ensure that the Service is present for the ApplicationSet webhook and metrics component.
   807  func (r *ReconcileArgoCD) reconcileApplicationSetService(cr *argoproj.ArgoCD) error {
   808  	log.Info("reconciling applicationset service")
   809  
   810  	svc := newServiceWithSuffix(common.ApplicationSetServiceNameSuffix, common.ApplicationSetServiceNameSuffix, cr)
   811  	if cr.Spec.ApplicationSet == nil || !cr.Spec.ApplicationSet.IsEnabled() {
   812  
   813  		if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) {
   814  			err := argoutil.FetchObject(r.Client, cr.Namespace, svc.Name, svc)
   815  			if err != nil {
   816  				return err
   817  			}
   818  			log.Info(fmt.Sprintf("Deleting applicationset controller service %s as applicationset is disabled", svc.Name))
   819  			err = r.Delete(context.TODO(), svc)
   820  			if err != nil {
   821  				return err
   822  			}
   823  		}
   824  		return nil
   825  	} else {
   826  		if argoutil.IsObjectFound(r.Client, cr.Namespace, svc.Name, svc) {
   827  			return nil // Service found, do nothing
   828  		}
   829  	}
   830  	svc.Spec.Ports = []corev1.ServicePort{
   831  		{
   832  			Name:       "webhook",
   833  			Port:       7000,
   834  			Protocol:   corev1.ProtocolTCP,
   835  			TargetPort: intstr.FromInt(7000),
   836  		}, {
   837  			Name:       "metrics",
   838  			Port:       8080,
   839  			Protocol:   corev1.ProtocolTCP,
   840  			TargetPort: intstr.FromInt(8080),
   841  		},
   842  	}
   843  
   844  	svc.Spec.Selector = map[string]string{
   845  		common.ArgoCDKeyName: nameWithSuffix(common.ApplicationSetServiceNameSuffix, cr),
   846  	}
   847  
   848  	if err := controllerutil.SetControllerReference(cr, svc, r.Scheme); err != nil {
   849  		return err
   850  	}
   851  	return r.Client.Create(context.TODO(), svc)
   852  }
   853  
   854  // Returns the name of the role/rolebinding for the source namespaces for applicationset-controller in the format of "argocdName-argocdNamespace-applicationset"
   855  func getResourceNameForApplicationSetSourceNamespaces(cr *argoproj.ArgoCD) string {
   856  	return fmt.Sprintf("%s-%s-applicationset", cr.Name, cr.Namespace)
   857  }
   858  
   859  // removeUnmanagedApplicationSetSourceNamespaceResources cleansup resources from ApplicationSetSourceNamespaces if namespace is not managed by argocd instance.
   860  // ManagedApplicationSetSourceNamespaces var keeps track of namespaces with appset resources.
   861  func (r *ReconcileArgoCD) removeUnmanagedApplicationSetSourceNamespaceResources(cr *argoproj.ArgoCD) error {
   862  
   863  	for ns := range r.ManagedApplicationSetSourceNamespaces {
   864  		managedNamespace := false
   865  		if cr.Spec.ApplicationSet != nil && cr.GetDeletionTimestamp() == nil {
   866  			appsNamespaces, err := r.getSourceNamespaces(cr)
   867  			if err != nil {
   868  				return err
   869  			}
   870  			for _, namespace := range cr.Spec.ApplicationSet.SourceNamespaces {
   871  				// appset ns should be part of apps ns
   872  				if namespace == ns && contains(appsNamespaces, namespace) {
   873  					managedNamespace = true
   874  					break
   875  				}
   876  			}
   877  		}
   878  
   879  		if !managedNamespace {
   880  			if err := r.cleanupUnmanagedApplicationSetSourceNamespaceResources(cr, ns); err != nil {
   881  				log.Error(err, fmt.Sprintf("error cleaning up applicationset resources for namespace %s", ns))
   882  				continue
   883  			}
   884  			delete(r.ManagedApplicationSetSourceNamespaces, ns)
   885  		}
   886  	}
   887  	return nil
   888  }
   889  
   890  // cleanupUnmanagedApplicationSetSourceNamespaceResources removes the application set resources from target namespace
   891  func (r *ReconcileArgoCD) cleanupUnmanagedApplicationSetSourceNamespaceResources(cr *argoproj.ArgoCD, ns string) error {
   892  	namespace := corev1.Namespace{}
   893  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: ns}, &namespace); err != nil {
   894  		if !apierrors.IsNotFound(err) {
   895  			return err
   896  		}
   897  		return nil
   898  	}
   899  
   900  	// Delete applicationset role & rolebinding
   901  	existingRole := v1.Role{}
   902  	roleName := getResourceNameForApplicationSetSourceNamespaces(cr)
   903  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleName, Namespace: namespace.Name}, &existingRole); err != nil {
   904  		if !apierrors.IsNotFound(err) {
   905  			return fmt.Errorf("failed to fetch the role for the service account associated with %s : %s", common.ArgoCDApplicationSetControllerComponent, err)
   906  		}
   907  	}
   908  	if existingRole.Name != "" {
   909  		err := r.Client.Delete(context.TODO(), &existingRole)
   910  		if err != nil {
   911  			return err
   912  		}
   913  	}
   914  
   915  	existingRoleBinding := &v1.RoleBinding{}
   916  	roleBindingName := getResourceNameForApplicationSetSourceNamespaces(cr)
   917  	if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBindingName, Namespace: namespace.Name}, existingRoleBinding); err != nil {
   918  		if !apierrors.IsNotFound(err) {
   919  			return fmt.Errorf("failed to get the rolebinding associated with %s : %s", common.ArgoCDApplicationSetControllerComponent, err)
   920  		}
   921  	}
   922  	if existingRoleBinding.Name != "" {
   923  		if err := r.Client.Delete(context.TODO(), existingRoleBinding); err != nil {
   924  			return err
   925  		}
   926  	}
   927  
   928  	// app-in-any-ns code will handle removal of appsets permissions for argocd-server in target namespace
   929  
   930  	// Remove applicationset-managed-by-cluster-argocd label from the namespace
   931  	delete(namespace.Labels, common.ArgoCDApplicationSetManagedByClusterArgoCDLabel)
   932  	if err := r.Client.Update(context.TODO(), &namespace); err != nil {
   933  		return fmt.Errorf("failed to remove applicationset label from namespace %s : %s", namespace.Name, err)
   934  	}
   935  
   936  	return nil
   937  }
   938  
   939  // setManagedApplicationSetSourceNamespaces populates ManagedApplicationSetSourceNamespaces var with namespaces
   940  // with "argocd.argoproj.io/applicationset-managed-by-cluster-argocd" label.
   941  func (r *ReconcileArgoCD) setManagedApplicationSetSourceNamespaces(cr *argoproj.ArgoCD) error {
   942  	if r.ManagedApplicationSetSourceNamespaces == nil {
   943  		r.ManagedApplicationSetSourceNamespaces = make(map[string]string)
   944  	}
   945  	namespaces := &corev1.NamespaceList{}
   946  	listOption := client.MatchingLabels{
   947  		common.ArgoCDApplicationSetManagedByClusterArgoCDLabel: cr.Namespace,
   948  	}
   949  
   950  	// get the list of namespaces managed with "argocd.argoproj.io/applicationset-managed-by-cluster-argocd" label
   951  	if err := r.Client.List(context.TODO(), namespaces, listOption); err != nil {
   952  		return err
   953  	}
   954  
   955  	for _, namespace := range namespaces.Items {
   956  		r.ManagedApplicationSetSourceNamespaces[namespace.Name] = ""
   957  	}
   958  
   959  	return nil
   960  }
   961  
   962  // reconcileSourceNamespaceRole creates/updates role
   963  func (r *ReconcileArgoCD) reconcileSourceNamespaceRole(role v1.Role, cr *argoproj.ArgoCD) error {
   964  
   965  	if err := applyReconcilerHook(cr, role, ""); err != nil {
   966  		return err
   967  	}
   968  
   969  	existingRole := v1.Role{}
   970  	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, &existingRole)
   971  	if err != nil {
   972  		if !apierrors.IsNotFound(err) {
   973  			errMsg := fmt.Errorf("failed to retrieve role %s in namespace %s", role.Name, role.Namespace)
   974  			return errors.Join(errMsg, err)
   975  		}
   976  
   977  		if err := r.Client.Create(context.TODO(), &role); err != nil {
   978  			errMsg := fmt.Errorf("failed to create role %s in namespace %s", role.Name, role.Namespace)
   979  			return errors.Join(errMsg, err)
   980  		}
   981  
   982  		log.Info(fmt.Sprintf("role %s created successfully for Argo CD instance %s in namespace %s", role.Name, cr.Name, role.Namespace))
   983  		return nil
   984  	}
   985  
   986  	// if the Rules differ, update the Role, ignore if role is just created.
   987  	if !reflect.DeepEqual(existingRole.Rules, role.Rules) {
   988  		existingRole.Rules = role.Rules
   989  		if err := r.Client.Update(context.TODO(), &existingRole); err != nil {
   990  			errMsg := fmt.Errorf("failed to update role %s in namespace %s", role.Name, role.Namespace)
   991  			return errors.Join(errMsg, err)
   992  		}
   993  		log.Info(fmt.Sprintf("role %s update successfully for Argo CD instance %s in namespace %s", role.Name, cr.Name, role.Namespace))
   994  	}
   995  
   996  	return nil
   997  }
   998  
   999  // reconcileSourceNamespaceRole creates/updates rolebinding
  1000  func (r *ReconcileArgoCD) reconcileSourceNamespaceRoleBinding(roleBinding v1.RoleBinding, cr *argoproj.ArgoCD) error {
  1001  
  1002  	if err := applyReconcilerHook(cr, roleBinding, ""); err != nil {
  1003  		return err
  1004  	}
  1005  
  1006  	existingRoleBinding := v1.RoleBinding{}
  1007  	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, &existingRoleBinding)
  1008  	if err != nil {
  1009  		if !apierrors.IsNotFound(err) {
  1010  			errMsg := fmt.Errorf("failed to retrieve rolebinding %s in namespace %s", roleBinding.Name, roleBinding.Namespace)
  1011  			return errors.Join(errMsg, err)
  1012  		}
  1013  
  1014  		if err := r.Client.Create(context.TODO(), &roleBinding); err != nil {
  1015  			errMsg := fmt.Errorf("failed to create rolebinding %s in namespace %s", roleBinding.Name, roleBinding.Namespace)
  1016  			return errors.Join(errMsg, err)
  1017  		}
  1018  
  1019  		log.Info(fmt.Sprintf("rolebinding %s created successfully for Argo CD instance %s in namespace %s", roleBinding.Name, cr.Name, roleBinding.Namespace))
  1020  		return nil
  1021  	}
  1022  
  1023  	// if the RoleRef changes, delete the existing role binding and create a new one
  1024  	if !reflect.DeepEqual(roleBinding.RoleRef, existingRoleBinding.RoleRef) {
  1025  		if err = r.Client.Delete(context.TODO(), &existingRoleBinding); err != nil {
  1026  			return err
  1027  		}
  1028  	} else {
  1029  		// if the Subjects differ, update the role bindings
  1030  		if !reflect.DeepEqual(roleBinding.Subjects, existingRoleBinding.Subjects) {
  1031  			existingRoleBinding.Subjects = roleBinding.Subjects
  1032  			if err = r.Client.Update(context.TODO(), &existingRoleBinding); err != nil {
  1033  				return err
  1034  			}
  1035  			log.Info(fmt.Sprintf("rolebinding %s update successfully for Argo CD instance %s in namespace %s", roleBinding.Name, cr.Name, roleBinding.Namespace))
  1036  		}
  1037  	}
  1038  
  1039  	return nil
  1040  }
  1041  
  1042  // getApplicationSetSourceNamespaces return list of namespaces from .spec.ApplicationSet.SourceNamespaces
  1043  func (r *ReconcileArgoCD) getApplicationSetSourceNamespaces(cr *argoproj.ArgoCD) []string {
  1044  	if cr.Spec.ApplicationSet != nil {
  1045  		return cr.Spec.ApplicationSet.SourceNamespaces
  1046  	}
  1047  	return []string(nil)
  1048  }