Skip to content

Commit 0614ce5

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 0614ce5

File tree

2 files changed

+291
-1
lines changed

2 files changed

+291
-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: 285 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"
@@ -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.
13141598
func deleteRBACsForNamespace(sourceNS string, k8sClient kubernetes.Interface) error {
13151599
log.Info(fmt.Sprintf("Removing the RBACs created for the namespace: %s", sourceNS))

0 commit comments

Comments
 (0)