@@ -19,6 +19,7 @@ import (
1919	"context" 
2020	"crypto/rand" 
2121	"encoding/base64" 
22+ 	"encoding/json" 
2223	"fmt" 
2324	"os" 
2425	"reflect" 
@@ -1089,7 +1090,7 @@ func (r *ReconcileArgoCD) setResourceWatches(bldr *builder.Builder, clusterResou
10891090	}
10901091
10911092	// Watch for changes to primary resource ArgoCD 
1092- 	bldr .For (& argoproj.ArgoCD {}, builder .WithPredicates (deleteSSOPred , deleteNotificationsPred , r .argoCDNamespaceManagementFilterPredicate ()))
1093+ 	bldr .For (& argoproj.ArgoCD {}, builder .WithPredicates (deleteSSOPred , deleteNotificationsPred , r .argoCDNamespaceManagementFilterPredicate (),  r . argoCDSpecLabelCleanupPredicate () ))
10931094
10941095	// Watch for changes to ConfigMap sub-resources owned by ArgoCD instances. 
10951096	bldr .Owns (& corev1.ConfigMap {})
@@ -1310,6 +1311,288 @@ func (r *ReconcileArgoCD) namespaceFilterPredicate() predicate.Predicate {
13101311	}
13111312}
13121313
1314+ // argoCDSpecLabelCleanupPredicate tracks label changes in ArgoCD CR specs and cleans up removed labels from managed resources 
1315+ func  (r  * ReconcileArgoCD ) argoCDSpecLabelCleanupPredicate () predicate.Predicate  {
1316+ 	return  predicate.Funcs {
1317+ 		UpdateFunc : func (e  event.UpdateEvent ) bool  {
1318+ 			newCR , ok  :=  e .ObjectNew .(* argoproj.ArgoCD )
1319+ 			if  ! ok  {
1320+ 				return  false 
1321+ 			}
1322+ 			oldCR , ok  :=  e .ObjectOld .(* argoproj.ArgoCD )
1323+ 			if  ! ok  {
1324+ 				return  false 
1325+ 			}
1326+ 
1327+ 			// Track removed labels from each component spec 
1328+ 			removedLabels  :=  r .calculateRemovedSpecLabels (oldCR , newCR )
1329+ 
1330+ 			if  len (removedLabels ) >  0  {
1331+ 				log .Info ("Detected removed labels from ArgoCD spec" ,
1332+ 					"argocd" , fmt .Sprintf ("%s/%s" , newCR .Namespace , newCR .Name ),
1333+ 					"removedLabels" , removedLabels )
1334+ 
1335+ 				// Store removed labels for cleanup during reconciliation 
1336+ 				if  err  :=  r .storeRemovedLabelsForCleanup (newCR , removedLabels ); err  !=  nil  {
1337+ 					log .Error (err , "failed to store removed labels for cleanup" )
1338+ 				}
1339+ 			}
1340+ 
1341+ 			return  true  // Trigger reconciliation for ArgoCD updates 
1342+ 		},
1343+ 	}
1344+ }
1345+ 
1346+ const  RemovedLabelsAnnotation  =  "argocd.argoproj.io/removed-labels" 
1347+ 
1348+ // calculateRemovedSpecLabels compares old and new ArgoCD specs and returns labels that were removed 
1349+ func  (r  * ReconcileArgoCD ) calculateRemovedSpecLabels (oldCR , newCR  * argoproj.ArgoCD ) map [string ]string  {
1350+ 	// Add nil checks to prevent panic 
1351+ 	if  r  ==  nil  ||  oldCR  ==  nil  ||  newCR  ==  nil  {
1352+ 		return  map [string ]string {}
1353+ 	}
1354+ 	removedLabels  :=  make (map [string ]string )
1355+ 
1356+ 	// Check Server labels 
1357+ 	oldServerLabels  :=  oldCR .Spec .Server .Labels 
1358+ 	newServerLabels  :=  newCR .Spec .Server .Labels 
1359+ 	for  key , value  :=  range  oldServerLabels  {
1360+ 		if  newServerLabels  ==  nil  ||  newServerLabels [key ] ==  ""  {
1361+ 			removedLabels [key ] =  value 
1362+ 		}
1363+ 	}
1364+ 
1365+ 	// Check Repo Server labels 
1366+ 	oldRepoLabels  :=  oldCR .Spec .Repo .Labels 
1367+ 	newRepoLabels  :=  newCR .Spec .Repo .Labels 
1368+ 	for  key , value  :=  range  oldRepoLabels  {
1369+ 		if  newRepoLabels  ==  nil  ||  newRepoLabels [key ] ==  ""  {
1370+ 			removedLabels [key ] =  value 
1371+ 		}
1372+ 	}
1373+ 
1374+ 	// Check Controller labels 
1375+ 	oldControllerLabels  :=  oldCR .Spec .Controller .Labels 
1376+ 	newControllerLabels  :=  newCR .Spec .Controller .Labels 
1377+ 	for  key , value  :=  range  oldControllerLabels  {
1378+ 		if  newControllerLabels  ==  nil  ||  newControllerLabels [key ] ==  ""  {
1379+ 			removedLabels [key ] =  value 
1380+ 		}
1381+ 	}
1382+ 
1383+ 	// Check ApplicationSet labels 
1384+ 	if  oldCR .Spec .ApplicationSet  !=  nil  &&  newCR .Spec .ApplicationSet  !=  nil  {
1385+ 		oldAppSetLabels  :=  oldCR .Spec .ApplicationSet .Labels 
1386+ 		newAppSetLabels  :=  newCR .Spec .ApplicationSet .Labels 
1387+ 		for  key , value  :=  range  oldAppSetLabels  {
1388+ 			if  newAppSetLabels  ==  nil  ||  newAppSetLabels [key ] ==  ""  {
1389+ 				removedLabels [key ] =  value 
1390+ 			}
1391+ 		}
1392+ 	}
1393+ 
1394+ 	// Note: Notifications and Prometheus specs don't have Labels fields currently 
1395+ 	// If they are added in the future, the logic can be uncommented and updated 
1396+ 
1397+ 	return  removedLabels 
1398+ }
1399+ 
1400+ func  (r  * ReconcileArgoCD ) storeRemovedLabelsForCleanup (obj  client.Object , removedLabels  map [string ]string ) error  {
1401+ 	if  len (removedLabels ) ==  0  {
1402+ 		return  nil 
1403+ 	}
1404+ 
1405+ 	// Serialize removed labels to JSON 
1406+ 	removedLabelsJSON , err  :=  json .Marshal (removedLabels )
1407+ 	if  err  !=  nil  {
1408+ 		return  fmt .Errorf ("failed to marshal removed labels: %w" , err )
1409+ 	}
1410+ 
1411+ 	// Add annotation with removed labels 
1412+ 	annotations  :=  obj .GetAnnotations ()
1413+ 	if  annotations  ==  nil  {
1414+ 		annotations  =  make (map [string ]string )
1415+ 	}
1416+ 	annotations [RemovedLabelsAnnotation ] =  string (removedLabelsJSON )
1417+ 	obj .SetAnnotations (annotations )
1418+ 
1419+ 	// Update the object 
1420+ 	return  r .Client .Update (context .TODO (), obj )
1421+ }
1422+ 
1423+ func  (r  * ReconcileArgoCD ) processRemovedLabels (argocd  * argoproj.ArgoCD ) error  {
1424+ 	annotations  :=  argocd .GetAnnotations ()
1425+ 	if  annotations  ==  nil  {
1426+ 		return  nil 
1427+ 	}
1428+ 
1429+ 	removedLabelsJSON , exists  :=  annotations [RemovedLabelsAnnotation ]
1430+ 	if  ! exists  {
1431+ 		return  nil 
1432+ 	}
1433+ 
1434+ 	var  removedLabels  map [string ]string 
1435+ 	if  err  :=  json .Unmarshal ([]byte (removedLabelsJSON ), & removedLabels ); err  !=  nil  {
1436+ 		return  fmt .Errorf ("failed to unmarshal removed labels: %w" , err )
1437+ 	}
1438+ 
1439+ 	if  len (removedLabels ) >  0  {
1440+ 		// Clean up from managed resources 
1441+ 		if  err  :=  r .cleanupLabelsFromManagedResources (argocd , removedLabels ); err  !=  nil  {
1442+ 			return  err 
1443+ 		}
1444+ 
1445+ 		// Clear the annotation 
1446+ 		delete (annotations , RemovedLabelsAnnotation )
1447+ 		argocd .SetAnnotations (annotations )
1448+ 		return  r .Client .Update (context .TODO (), argocd )
1449+ 	}
1450+ 
1451+ 	return  nil 
1452+ }
1453+ 
1454+ func  (r  * ReconcileArgoCD ) cleanupLabelsFromManagedResources (argocd  * argoproj.ArgoCD , labelsToRemove  map [string ]string ) error  {
1455+ 	log .Info ("Cleaning up removed labels from managed resources" ,
1456+ 		"argocd" , fmt .Sprintf ("%s/%s" , argocd .Namespace , argocd .Name ),
1457+ 		"labelsToRemove" , labelsToRemove )
1458+ 
1459+ 	// Clean up labels from ArgoCD Server Deployment 
1460+ 	if  err  :=  r .cleanupLabelsFromComponent (argocd , labelsToRemove , "server" , & appsv1.Deployment {}); err  !=  nil  {
1461+ 		log .Error (err , "failed to cleanup labels from server deployment" )
1462+ 	}
1463+ 
1464+ 	// Clean up labels from ArgoCD Repo Server Deployment 
1465+ 	if  err  :=  r .cleanupLabelsFromComponent (argocd , labelsToRemove , "repo-server" , & appsv1.Deployment {}); err  !=  nil  {
1466+ 		log .Error (err , "failed to cleanup labels from repo-server deployment" )
1467+ 	}
1468+ 
1469+ 	// Clean up labels from ArgoCD Application Controller StatefulSet 
1470+ 	if  err  :=  r .cleanupLabelsFromComponent (argocd , labelsToRemove , "application-controller" , & appsv1.StatefulSet {}); err  !=  nil  {
1471+ 		log .Error (err , "failed to cleanup labels from application-controller statefulset" )
1472+ 	}
1473+ 
1474+ 	// Clean up labels from ArgoCD ApplicationSet Controller Deployment 
1475+ 	if  err  :=  r .cleanupLabelsFromComponent (argocd , labelsToRemove , "applicationset-controller" , & appsv1.Deployment {}); err  !=  nil  {
1476+ 		log .Error (err , "failed to cleanup labels from applicationset-controller deployment" )
1477+ 	}
1478+ 
1479+ 	// Clean up labels from ArgoCD Notifications Controller Deployment (if enabled) 
1480+ 	if  argocd .Spec .Notifications .Enabled  {
1481+ 		if  err  :=  r .cleanupLabelsFromComponent (argocd , labelsToRemove , "notifications-controller" , & appsv1.Deployment {}); err  !=  nil  {
1482+ 			log .Error (err , "failed to cleanup labels from notifications-controller deployment" )
1483+ 		}
1484+ 	}
1485+ 
1486+ 	// Use existing pattern for other managed resources 
1487+ 	selector , err  :=  argocdInstanceSelector (argocd .Name )
1488+ 	if  err  !=  nil  {
1489+ 		return  err 
1490+ 	}
1491+ 
1492+ 	// Clean up ConfigMaps 
1493+ 	configMapList  :=  & corev1.ConfigMapList {}
1494+ 	if  err  :=  filterObjectsBySelector (r .Client , configMapList , selector ); err  ==  nil  {
1495+ 		for  _ , cm  :=  range  configMapList .Items  {
1496+ 			if  err  :=  r .removeLabelsFromObject (& cm , labelsToRemove ); err  !=  nil  {
1497+ 				log .Error (err , "failed to remove labels from ConfigMap" , "name" , cm .Name )
1498+ 			}
1499+ 		}
1500+ 	}
1501+ 
1502+ 	// Add other resource types as needed... 
1503+ 	return  nil 
1504+ }
1505+ 
1506+ // cleanupLabelsFromComponent removes specified labels from a specific ArgoCD component 
1507+ func  (r  * ReconcileArgoCD ) cleanupLabelsFromComponent (argocd  * argoproj.ArgoCD , labelsToRemove  map [string ]string , componentName  string , obj  client.Object ) error  {
1508+ 	resourceName  :=  fmt .Sprintf ("%s-%s" , argocd .Name , componentName )
1509+ 	key  :=  types.NamespacedName {
1510+ 		Namespace : argocd .Namespace ,
1511+ 		Name :      resourceName ,
1512+ 	}
1513+ 
1514+ 	if  err  :=  r .Client .Get (context .TODO (), key , obj ); err  !=  nil  {
1515+ 		if  apierrors .IsNotFound (err ) {
1516+ 			// Component doesn't exist, nothing to clean up 
1517+ 			return  nil 
1518+ 		}
1519+ 		return  fmt .Errorf ("failed to get %s: %w" , componentName , err )
1520+ 	}
1521+ 
1522+ 	// Remove labels from the resource itself 
1523+ 	if  err  :=  r .removeLabelsFromObject (obj , labelsToRemove ); err  !=  nil  {
1524+ 		return  fmt .Errorf ("failed to remove labels from %s: %w" , componentName , err )
1525+ 	}
1526+ 
1527+ 	// For Deployments and StatefulSets, also clean labels from pod template 
1528+ 	switch  resource  :=  obj .(type ) {
1529+ 	case  * appsv1.Deployment :
1530+ 		podTemplateLabels  :=  resource .Spec .Template .Labels 
1531+ 		if  podTemplateLabels  !=  nil  {
1532+ 			modified  :=  false 
1533+ 			for  labelKey  :=  range  labelsToRemove  {
1534+ 				if  _ , exists  :=  podTemplateLabels [labelKey ]; exists  {
1535+ 					delete (podTemplateLabels , labelKey )
1536+ 					modified  =  true 
1537+ 				}
1538+ 			}
1539+ 			if  modified  {
1540+ 				resource .Spec .Template .Labels  =  podTemplateLabels 
1541+ 				if  err  :=  r .Client .Update (context .TODO (), resource ); err  !=  nil  {
1542+ 					return  fmt .Errorf ("failed to update pod template labels for %s deployment: %w" , componentName , err )
1543+ 				}
1544+ 				log .Info ("Removed labels from pod template" ,
1545+ 					"component" , componentName ,
1546+ 					"removedLabels" , labelsToRemove )
1547+ 			}
1548+ 		}
1549+ 	case  * appsv1.StatefulSet :
1550+ 		podTemplateLabels  :=  resource .Spec .Template .Labels 
1551+ 		if  podTemplateLabels  !=  nil  {
1552+ 			modified  :=  false 
1553+ 			for  labelKey  :=  range  labelsToRemove  {
1554+ 				if  _ , exists  :=  podTemplateLabels [labelKey ]; exists  {
1555+ 					delete (podTemplateLabels , labelKey )
1556+ 					modified  =  true 
1557+ 				}
1558+ 			}
1559+ 			if  modified  {
1560+ 				resource .Spec .Template .Labels  =  podTemplateLabels 
1561+ 				if  err  :=  r .Client .Update (context .TODO (), resource ); err  !=  nil  {
1562+ 					return  fmt .Errorf ("failed to update pod template labels for %s statefulset: %w" , componentName , err )
1563+ 				}
1564+ 				log .Info ("Removed labels from pod template" ,
1565+ 					"component" , componentName ,
1566+ 					"removedLabels" , labelsToRemove )
1567+ 			}
1568+ 		}
1569+ 	}
1570+ 
1571+ 	return  nil 
1572+ }
1573+ 
1574+ func  (r  * ReconcileArgoCD ) removeLabelsFromObject (obj  client.Object , labelsToRemove  map [string ]string ) error  {
1575+ 	currentLabels  :=  obj .GetLabels ()
1576+ 	if  currentLabels  ==  nil  {
1577+ 		return  nil 
1578+ 	}
1579+ 
1580+ 	modified  :=  false 
1581+ 	for  labelKey  :=  range  labelsToRemove  {
1582+ 		if  _ , exists  :=  currentLabels [labelKey ]; exists  {
1583+ 			delete (currentLabels , labelKey )
1584+ 			modified  =  true 
1585+ 		}
1586+ 	}
1587+ 
1588+ 	if  modified  {
1589+ 		obj .SetLabels (currentLabels )
1590+ 		return  r .Client .Update (context .TODO (), obj )
1591+ 	}
1592+ 
1593+ 	return  nil 
1594+ }
1595+ 
13131596// deleteRBACsForNamespace deletes the RBACs when the label from the namespace is removed. 
13141597func  deleteRBACsForNamespace (sourceNS  string , k8sClient  kubernetes.Interface ) error  {
13151598	log .Info (fmt .Sprintf ("Removing the RBACs created for the namespace: %s" , sourceNS ))
0 commit comments