Skip to content

Commit 6051879

Browse files
committed
GITOPS-7405: added logic to remove custom labels from live objects if removed from argocd spec
Signed-off-by: Alka Kumari <[email protected]>
1 parent a2925aa commit 6051879

File tree

2 files changed

+290
-1
lines changed

2 files changed

+290
-1
lines changed

controllers/argocd/argocd_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,12 @@ func (r *ReconcileArgoCD) internalReconcile(ctx context.Context, request ctrl.Re
296296
}
297297
}
298298

299+
// Process any stored removed labels for cleanup from managed resources
300+
if err := r.processRemovedLabels(argocd); err != nil {
301+
reqLogger.Error(err, "failed to process removed labels cleanup")
302+
// Don't fail the reconciliation, just log the error and continue
303+
}
304+
299305
if err := r.reconcileResources(argocd); err != nil {
300306
// Error reconciling ArgoCD sub-resources - requeue the request.
301307
return reconcile.Result{}, argocd, err

controllers/argocd/util.go

Lines changed: 284 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
13141597
func deleteRBACsForNamespace(sourceNS string, k8sClient kubernetes.Interface) error {
13151598
log.Info(fmt.Sprintf("Removing the RBACs created for the namespace: %s", sourceNS))

0 commit comments

Comments
 (0)