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