@@ -23,12 +23,17 @@ import (
2323	"time" 
2424
2525	appsv1 "k8s.io/api/apps/v1" 
26+ 	"k8s.io/api/certificates/v1beta1" 
2627	corev1 "k8s.io/api/core/v1" 
2728	"k8s.io/apimachinery/pkg/api/errors" 
29+ 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 
30+ 	"k8s.io/apimachinery/pkg/labels" 
2831	"k8s.io/apimachinery/pkg/types" 
2932	"k8s.io/apimachinery/pkg/util/intstr" 
3033	"k8s.io/utils/ptr" 
34+ 	"sigs.k8s.io/controller-runtime/pkg/client" 
3135	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 
36+ 	"sigs.k8s.io/controller-runtime/pkg/reconcile" 
3237
3338	argocdoperatorv1beta1 "github.com/argoproj-labs/argocd-operator/api/v1beta1" 
3439	"github.com/argoproj-labs/argocd-operator/common" 
@@ -350,13 +355,9 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argocdoperatorv1beta1.Argo
350355	if  cr .Spec .Repo .Volumes  !=  nil  {
351356		repoServerVolumes  =  append (repoServerVolumes , cr .Spec .Repo .Volumes ... )
352357	}
358+ 	deploy .Spec .Template .Spec .Volumes  =  repoServerVolumes 
353359
354- 	moreRepoServerVolumes , err  :=  injectCATrustToContainers (cr , deploy )
355- 	if  err  !=  nil  {
356- 		return  err 
357- 	}
358- 
359- 	deploy .Spec .Template .Spec .Volumes  =  append (repoServerVolumes , moreRepoServerVolumes ... )
360+ 	r .injectCATrustToContainers (cr , deploy )
360361
361362	if  replicas  :=  getArgoCDRepoServerReplicas (cr ); replicas  !=  nil  {
362363		deploy .Spec .Replicas  =  replicas 
@@ -588,20 +589,17 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argocdoperatorv1beta1.Argo
588589// 
589590// The production container is then mounted with `/etc/ssl/certs/` (`argocd-ca-trust-target`) and 
590591// `/usr/local/share/ca-certificates/` (`argocd-ca-trust-source`) providing read-only CAs needed. 
591- func  injectCATrustToContainers (cr  * argocdoperatorv1beta1.ArgoCD , deploy  * appsv1.Deployment ) ( repoServerVolumes  []corev1. Volume ,  err   error ) {
592+ func  ( r   * ReconcileArgoCD )  injectCATrustToContainers (cr  * argocdoperatorv1beta1.ArgoCD , deploy  * appsv1.Deployment ) {
592593	if  cr .Spec .Repo .SystemCATrust  ==  nil  {
593- 		return  []corev1. Volume {},  nil 
594+ 		return 
594595	}
595596
596- 	sources , sourceNames , err  :=  caTrustVolumes (cr )
597- 	if  err  !=  nil  {
598- 		return  []corev1.Volume {}, err 
599- 	}
597+ 	sources , sourceNames  :=  r .caTrustVolumes (cr )
600598
601599	volumeSource  :=  "argocd-ca-trust-source" 
602600	volumeTarget  :=  "argocd-ca-trust-target" 
603601
604- 	repoServerVolumes  =  []corev1.Volume {
602+ 	repoServerVolumes  : =Volume {
605603		{
606604			Name : volumeSource ,
607605			VolumeSource : corev1.VolumeSource {
@@ -649,56 +647,53 @@ func injectCATrustToContainers(cr *argocdoperatorv1beta1.ArgoCD, deploy *appsv1.
649647		strings .Join (containerNames , ", " ),
650648	))
651649
652- 	return   repoServerVolumes ,  nil 
650+ 	deploy . Spec . Template . Spec . Volumes   =   append ( deploy . Spec . Template . Spec . Volumes ,  repoServerVolumes ... ) 
653651}
654652
655- func  caTrustVolumes (cr  * argocdoperatorv1beta1.ArgoCD ) ([]corev1.VolumeProjection , []string , error ) {
656- 	checkPath  :=  func (kind  string , path  string ) error  {
657- 		if  ! strings .HasSuffix (path , ".crt" ) {
658- 			return  fmt .Errorf ("invalid %s cert file name suffix '%s' in %s, must be .crt" , kind , path , cr .Name )
653+ func  (r  * ReconcileArgoCD ) caTrustVolumes (cr  * argocdoperatorv1beta1.ArgoCD ) (sources  []corev1.VolumeProjection , sourceNames  []string ) {
654+ 	// The projected file needs to have the `.crt` suffix for the update-ca-certificates to work correctly. Add it if not present. 
655+ 	ensureValidPath  :=  func (path  string ) string  {
656+ 		if  strings .HasSuffix (path , ".crt" ) {
657+ 			return  path 
659658		}
660- 		return  nil 
659+ 		return  path  +  ".crt" 
660+ 	}
661+ 
662+ 	trackSource  :=  func (kind  string , name  string , optional  * bool ) {
663+ 		path  :=  kind  +  ":"  +  name 
664+ 		if  optional  !=  nil  &&  * optional  {
665+ 			path  +=  "(optional)" 
666+ 		}
667+ 		sourceNames  =  append (sourceNames , path )
661668	}
662669
663- 	var  sources  []corev1.VolumeProjection 
664- 	var  sourceNames  []string 
665670	for  _ , bundle  :=  range  cr .Spec .Repo .SystemCATrust .ClusterTrustBundles  {
666671		bundle  =  * bundle .DeepCopy ()
667- 		if  err  :=  checkPath ("ClusterTrustBundle" , bundle .Path ); err  !=  nil  {
668- 			return  nil , nil , err 
669- 		}
672+ 		// Using .Path, because .Name might not be specified 
673+ 		trackSource ("ClusterTrustBundle" , bundle .Path , bundle .Optional )
670674
675+ 		bundle .Path  =  ensureValidPath (bundle .Path )
671676		sources  =  append (sources , corev1.VolumeProjection {ClusterTrustBundle : & bundle })
672- 
673- 		path  :=  "ClusterTrustBundle:"  +  bundle .Path  // Using .Path, because .Name might not be specified 
674- 		if  bundle .Optional  !=  nil  &&  * bundle .Optional  {
675- 			path  +=  "(optional)" 
676- 		}
677- 		sourceNames  =  append (sourceNames , path )
678677	}
679678	for  _ , secret  :=  range  cr .Spec .Repo .SystemCATrust .Secrets  {
680679		secret  =  * secret .DeepCopy ()
681- 		for  _ , item  :=  range  secret .Items  {
682- 			if  err  :=  checkPath ("Secret" , item .Path ); err  !=  nil  {
683- 				return  nil , nil , err 
684- 			}
685- 		}
680+ 		trackSource ("Secret" , secret .Name , secret .Optional )
686681
682+ 		for  i , item  :=  range  secret .Items  {
683+ 			secret .Items [i ].Path  =  ensureValidPath (item .Path )
684+ 		}
687685		sources  =  append (sources , corev1.VolumeProjection {Secret : & secret })
688- 		sourceNames  =  append (sourceNames , fmt .Sprintf ("Secret:%s" , secret .Name ))
689686	}
690687	for  _ , cm  :=  range  cr .Spec .Repo .SystemCATrust .ConfigMaps  {
691688		cm  =  * cm .DeepCopy ()
692- 		for  _ , cmi  :=  range  cm .Items  {
693- 			if  err  :=  checkPath ("ConfigMap" , cmi .Path ); err  !=  nil  {
694- 				return  nil , nil , err 
695- 			}
696- 		}
689+ 		trackSource ("ConfigMap" , cm .Name , cm .Optional )
697690
691+ 		for  i , cmi  :=  range  cm .Items  {
692+ 			cm .Items [i ].Path  =  ensureValidPath (cmi .Path )
693+ 		}
698694		sources  =  append (sources , corev1.VolumeProjection {ConfigMap : & cm })
699- 		sourceNames  =  append (sourceNames , fmt .Sprintf ("ConfigMap:%s" , cm .Name ))
700695	}
701- 	return  sources , sourceNames ,  nil 
696+ 	return  sources , sourceNames 
702697}
703698
704699func  caTrustInitContainer (cr  * argocdoperatorv1beta1.ArgoCD , argoImage  string , volumeSource  string , volumeTarget  string ) corev1.Container  {
@@ -727,6 +722,10 @@ func caTrustInitContainer(cr *argocdoperatorv1beta1.ArgoCD, argoImage string, vo
727722
728723                echo "User defined CA files:" 
729724                ls -l /usr/local/share/ca-certificates/ 
725+ 
726+                 # Make sure the file exist even when the update-ca-certificates produces no pem blocks 
727+                 echo "" > /etc/ssl/certs/ca-certificates.crt 
728+ 
730729                update-ca-certificates --verbose --certsdir "$IMAGE_CERT_PATH" 
731730                echo "Resulting /etc/ssl/certs/" 
732731                ls -l /etc/ssl/certs/ 
@@ -961,3 +960,103 @@ func (r *ReconcileArgoCD) reconcileRepoServerTLSSecret(cr *argocdoperatorv1beta1
961960
962961	return  nil 
963962}
963+ 
964+ // systemCATrustMapper triggers reconciliation of repo-server Deployment if some of the tracked Secrets, ConfigMaps or ClusterTrustBundles have changed 
965+ func  (r  * ReconcileArgoCD ) systemCATrustMapper (ctx  context.Context , o  client.Object ) []reconcile.Request  {
966+ 	// Track Argo CDs whose repo-servers need a rollout, and id of the resource that changed 
967+ 	rolloutBecause  :=  make (map [* argocdoperatorv1beta1.ArgoCD ]string )
968+ 
969+ 	// For cluster-wide resources, it is needed to consult all argos. For cluster-scoped ones, only the argos in the same NS. 
970+ 	argoNamespace  :=  client .InNamespace (o .GetNamespace ())
971+ 	var  argoCDs  argocdoperatorv1beta1.ArgoCDList 
972+ 	if  err  :=  r .Client .List (ctx , & argoCDs , argoNamespace ); err  !=  nil  {
973+ 		log .Error (err , "unable to list ArgoCD instances" )
974+ 		return  []reconcile.Request {}
975+ 	}
976+ 
977+ 	for  _ , argocd  :=  range  argoCDs .Items  {
978+ 		if  argocd .Spec .Repo .SystemCATrust  ==  nil  {
979+ 			continue 
980+ 		}
981+ 
982+ 		switch  obj  :=  o .(type ) {
983+ 		case  * corev1.Secret :
984+ 			for  _ , trustSource  :=  range  argocd .Spec .Repo .SystemCATrust .Secrets  {
985+ 				if  trustSource .Name  ==  obj .Name  {
986+ 					rolloutBecause [& argocd ] =  fmt .Sprintf ("Secret %s/%s" , obj .Namespace , obj .Name )
987+ 					break 
988+ 				}
989+ 			}
990+ 		case  * corev1.ConfigMap :
991+ 			for  _ , trustSource  :=  range  argocd .Spec .Repo .SystemCATrust .ConfigMaps  {
992+ 				if  trustSource .Name  ==  obj .Name  {
993+ 					rolloutBecause [& argocd ] =  fmt .Sprintf ("ConfigMap %s/%s" , obj .Namespace , obj .Name )
994+ 					break 
995+ 				}
996+ 			}
997+ 		case  * v1beta1.ClusterTrustBundle :
998+ 			for  _ , trustSource  :=  range  argocd .Spec .Repo .SystemCATrust .ClusterTrustBundles  {
999+ 				if  isRelevantCtb (trustSource , obj ) {
1000+ 					rolloutBecause [& argocd ] =  fmt .Sprintf ("ClusterTrustBundle %s" , obj .Name )
1001+ 					break 
1002+ 				}
1003+ 			}
1004+ 		default :
1005+ 			panic (fmt .Errorf ("systemCATrustMapper called for unknown type %t" , o ))
1006+ 		}
1007+ 	}
1008+ 
1009+ 	for  argocd , cause  :=  range  rolloutBecause  {
1010+ 		// Instead of triggering rollout, delete the pod to force trust recomputation 
1011+ 		pods  :=  & corev1.PodList {}
1012+ 		err  :=  r .Client .List (context .TODO (), pods ,
1013+ 			client .InNamespace (argocd .Namespace ),
1014+ 			client.MatchingLabelsSelector {Selector : labels .SelectorFromSet (map [string ]string {
1015+ 				"app.kubernetes.io/name" : nameWithSuffix ("repo-server" , argocd ),
1016+ 			})},
1017+ 		)
1018+ 		if  err  !=  nil  {
1019+ 			log .Error (err , "unable to list repo-server pods for argocd" , "ns" , argocd .Namespace , "name" , argocd .Name )
1020+ 		}
1021+ 
1022+ 		// In normal circumstances, there would be 1 pod. There can be multiple during ongoing rollout. None if not yet started, or recovering from an error. 
1023+ 		for  _ , pod  :=  range  pods .Items  {
1024+ 			log .Info (
1025+ 				"restarting repo-server pod after SystemCATrust change in " + cause ,
1026+ 				"pod" , pod .Name , "ns" , pod .Namespace , "phase" , pod .Status .Phase ,
1027+ 			)
1028+ 			if  err  :=  r .Delete (context .TODO (), & pod ); err  !=  nil  {
1029+ 				log .Error (err , "unable to delete repo-server pod 1" , "pod" , pod .Name , "ns" , pod .Namespace , "phase" , pod .Status .Phase )
1030+ 			}
1031+ 		}
1032+ 	}
1033+ 	// No need to reconcile. The pods have been restarted 
1034+ 	return  []reconcile.Request {}
1035+ }
1036+ 
1037+ func  isRelevantCtb (proj  corev1.ClusterTrustBundleProjection , actual  * v1beta1.ClusterTrustBundle ) bool  {
1038+ 	// ClusterTrustBundle uses either .Name or .SignerName plus eventual .LabelSelector to identify the source 
1039+ 	if  proj .Name  !=  nil  &&  * proj .Name  ==  actual .Name  {
1040+ 		return  true 
1041+ 	}
1042+ 
1043+ 	if  proj .SignerName  !=  nil  &&  * proj .SignerName  ==  actual .Spec .SignerName  {
1044+ 		// If unset, interpreted as "match nothing".  If set but empty, interpreted as "match everything". 
1045+ 		if  proj .LabelSelector  ==  nil  {
1046+ 			return  false 
1047+ 		}
1048+ 		if  len (proj .LabelSelector .MatchLabels )+ len (proj .LabelSelector .MatchExpressions ) ==  0  {
1049+ 			return  true 
1050+ 		}
1051+ 
1052+ 		selector , err  :=  metav1 .LabelSelectorAsSelector (proj .LabelSelector )
1053+ 		if  err  !=  nil  {
1054+ 			log .Error (err , "Failed evaluating label selector for System CA trust ClusterTrustBundle" , "selector" , proj .LabelSelector )
1055+ 			return  false 
1056+ 		}
1057+ 
1058+ 		return  selector .Matches (labels .Set (actual .Labels ))
1059+ 	}
1060+ 
1061+ 	return  false 
1062+ }
0 commit comments