Repository: gardener/etcd-druid (go)

Issue:
Remove the functionality in druid that deletes old configmaps
**How to categorize this issue?**
<!--
Please select area, kind, and priority for this issue. This helps the community categorizing it.
Replace below TODOs or exchange the existing identifiers with those that fit best in your opinion.
If multiple identifiers make sense you can also state the commands multiple times, e.g.
  /area control-plane
  /area auto-scaling
  ...

"/area" identifiers:     audit-logging|auto-scaling|backup|certification|control-plane-migration|control-plane|cost|delivery|dev-productivity|disaster-recovery|documentation|high-availability|logging|metering|monitoring|networking|open-source|ops-productivity|os|performance|quality|robustness|scalability|security|storage|testing|usability|user-management
"/kind" identifiers:     api-change|bug|cleanup|discussion|enhancement|epic|impediment|poc|post-mortem|question|regression|task|technical-debt|test
-->
/area quality
/kind cleanup

**What would you like to be added**:

The PR #1014 adds functionality to cleanup the old configmaps with naming format `etcd-bootstrap-UID[:6]`. However, as it's only a cleanup logic this needs to be removed from druid code after 3 releases i.e in `v0.31.0` ensuring sufficient time for users to upgrade to versions that has this cleanup logic. After that, it is unnecessary to maintain this piece of code. 

**Why is this needed**:

Remove cleanup logic for older configmaps in `v0.31.0`


Pull request:
Remove functionality that deletes old ConfigMaps
**How to categorize this PR?**
<!--
Please select area, kind, and priority for this pull request. This helps the community categorizing it.
Replace below TODOs or exchange the existing identifiers with those that fit best in your opinion.
If multiple identifiers make sense you can also state the commands multiple times, e.g.
  /area control-plane
  /area auto-scaling
  ...

"/area" identifiers:     audit-logging|auto-scaling|backup|compliance|control-plane-migration|control-plane|cost|delivery|dev-productivity|disaster-recovery|documentation|high-availability|logging|metering|monitoring|networking|open-source|ops-productivity|os|performance|quality|robustness|scalability|security|storage|testing|usability|user-management
"/kind" identifiers:     api-change|bug|cleanup|discussion|enhancement|epic|flake|impediment|poc|post-mortem|question|regression|task|technical-debt|test

For Druid Enhancement Proposals (DEPs), please ensure that the proposal is written in the following [format](https://github.com/gardener/etcd-druid/blob/master/docs/proposals/00-template.md) before submitting this pull request.
-->
/area quality
/kind cleanup

**What this PR does / why we need it**:

This PR removes the code functionality that was introduced in #1014 to remove older ConfigMaps with the naming format `etcd-bootstrap-UID[:6]`. The functionality was only intended to be there for 3 releases starting from `v0.28.0` till `v0.30.0`. Now it's no longer needed to be present, hence removing the functionality.

**Which issue(s) this PR fixes**:
Fixes #1015 

**Special notes for your reviewer**:


**Release note**:
<!--
Write your release note:
1. Enter your release note in the below block.
4. If no release note is required, just write "NONE" within the block.

Format of block header: <category> <target_group>
Possible values:
- category:       breaking|noteworthy|feature|bugfix|doc|other
- target_group:   user|operator|developer|dependency
-->
```other developer
Remove the functionality that deletes old ConfigMaps from the cluster
```


Patch:
diff --git a/api/core/v1alpha1/helper.go b/api/core/v1alpha1/helper.go
index be28f103..8b9c7dab 100644
--- a/api/core/v1alpha1/helper.go
+++ b/api/core/v1alpha1/helper.go
@@ -29,12 +29,6 @@ func GetServiceAccountName(etcdObjMeta metav1.ObjectMeta) string {
 	return etcdObjMeta.Name
 }
 
-// GetOldConfigMapName returns the name of the old configmap for the Etcd.
-// TODO: @anveshreddy18: Remove this function after 3 releases, i.e in v0.31.0
-func GetOldConfigMapName(etcdObjMeta metav1.ObjectMeta) string {
-	return fmt.Sprintf("etcd-bootstrap-%s", string(etcdObjMeta.UID[:6]))
-}
-
 // GetConfigMapName returns the name of the configmap for the Etcd.
 func GetConfigMapName(etcdObjMeta metav1.ObjectMeta) string {
 	return fmt.Sprintf("%s-config", etcdObjMeta.Name)
diff --git a/internal/component/configmap/configmap.go b/internal/component/configmap/configmap.go
index 09fd1e8a..dfaa3aaa 100644
--- a/internal/component/configmap/configmap.go
+++ b/internal/component/configmap/configmap.go
@@ -64,24 +64,7 @@ func (r _resource) GetExistingResourceNames(ctx component.OperatorContext, etcdO
 }
 
 // PreSync is a no-op for the configmap component.
-// TODO: @anveshreddy18: Remove the functionality in the below PreSync method after 3 releases i.e in v0.31.0
-// This is a temporary functionality to remove the old configmaps from the cluster that are created by the etcd-druid.
-func (r _resource) PreSync(ctx component.OperatorContext, etcd *druidv1alpha1.Etcd) error {
-	oldConfigMap := emptyConfigMap(getOldObjectKey(etcd.ObjectMeta))
-	ctx.Logger.Info("PreSync: Deleting old configmap", "name", oldConfigMap.Name)
-	if err := r.client.Delete(ctx, oldConfigMap); err != nil {
-		if errors.IsNotFound(err) {
-			ctx.Logger.Info("No old configmap found, ConfigMap PreSync is a no-op", "name", oldConfigMap.Name)
-			return nil
-		}
-		return druiderr.WrapError(
-			err,
-			ErrDeleteConfigMap,
-			component.OperationPreSync,
-			fmt.Sprintf("Failed to delete old configmap %s", oldConfigMap.Name),
-		)
-	}
-	ctx.Logger.Info("deleted", "component", "configmap", "name", oldConfigMap.Name)
+func (r _resource) PreSync(_ component.OperatorContext, _ *druidv1alpha1.Etcd) error {
 	return nil
 }
 
@@ -159,13 +142,6 @@ func getObjectKey(obj metav1.ObjectMeta) client.ObjectKey {
 	}
 }
 
-func getOldObjectKey(obj metav1.ObjectMeta) client.ObjectKey {
-	return client.ObjectKey{
-		Name:      druidv1alpha1.GetOldConfigMapName(obj),
-		Namespace: obj.Namespace,
-	}
-}
-
 func emptyConfigMap(objectKey client.ObjectKey) *corev1.ConfigMap {
 	return &corev1.ConfigMap{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/internal/component/statefulset/builder.go b/internal/component/statefulset/builder.go
index 3c29fd12..ce0be500 100644
--- a/internal/component/statefulset/builder.go
+++ b/internal/component/statefulset/builder.go
@@ -86,7 +86,7 @@ func newStsBuilder(client client.Client,
 	if err != nil {
 		return nil, err
 	}
-	provider, err := getBackupStoreProvider(etcd)
+	provider, err := kubernetes.GetBackupStoreProvider(etcd)
 	if err != nil {
 		return nil, err
 	}
@@ -246,29 +246,29 @@ func (b *stsBuilder) getVolumeClaimTemplates() []corev1.PersistentVolumeClaim {
 }
 
 func (b *stsBuilder) getPodInitContainers() []corev1.Container {
-	initContainers := make([]corev1.Container, 0, 1)
-	if b.etcd.IsBackupStoreEnabled() {
-		if b.provider != nil && *b.provider == druidstore.Local {
-			etcdBackupVolumeMount := b.getEtcdBackupVolumeMount()
-			if etcdBackupVolumeMount != nil {
-				initContainers = append(initContainers, corev1.Container{
-					Name:            common.InitContainerNameChangeBackupBucketPermissions,
-					Image:           b.initContainerImage,
-					ImagePullPolicy: corev1.PullIfNotPresent,
-					Command:         []string{"sh", "-c", "--"},
-					Args:            []string{fmt.Sprintf("chown -R %d:%d /home/nonroot/%s", nonRootUser, nonRootUser, *b.etcd.Spec.Backup.Store.Container)},
-					VolumeMounts:    []corev1.VolumeMount{*etcdBackupVolumeMount},
-					SecurityContext: &corev1.SecurityContext{
-						AllowPrivilegeEscalation: ptr.To(false),
-						RunAsGroup:               ptr.To[int64](0),
-						RunAsNonRoot:             ptr.To(false),
-						RunAsUser:                ptr.To[int64](0),
-					},
-				})
-			}
-		}
+	if !b.etcd.IsBackupStoreEnabled() || b.provider == nil || *b.provider != druidstore.Local || ptr.Deref(b.etcd.Spec.RunAsRoot, false) || b.getEtcdBackupVolumeMount() == nil {
+		return nil
 	}
-	return initContainers
+
+	etcdBackupVolumeMount := b.getEtcdBackupVolumeMount()
+	if etcdBackupVolumeMount == nil {
+		return nil
+	}
+
+	return []corev1.Container{{
+		Name:            common.InitContainerNameChangeBackupBucketPermissions,
+		Image:           b.initContainerImage,
+		ImagePullPolicy: corev1.PullIfNotPresent,
+		Command:         []string{"sh", "-c", "--"},
+		Args:            []string{fmt.Sprintf("chown -R %d:%d %s", nonRootUser, nonRootUser, kubernetes.MountPathLocalStore(b.etcd, b.provider))},
+		VolumeMounts:    []corev1.VolumeMount{*etcdBackupVolumeMount},
+		SecurityContext: &corev1.SecurityContext{
+			AllowPrivilegeEscalation: ptr.To(false),
+			RunAsGroup:               ptr.To[int64](0),
+			RunAsNonRoot:             ptr.To(false),
+			RunAsUser:                ptr.To[int64](0),
+		},
+	}}
 }
 
 func (b *stsBuilder) getEtcdContainerVolumeMounts() []corev1.VolumeMount {
@@ -330,7 +330,7 @@ func (b *stsBuilder) getEtcdBackupVolumeMount() *corev1.VolumeMount {
 		if b.etcd.Spec.Backup.Store.Container != nil {
 			return &corev1.VolumeMount{
 				Name:      common.VolumeNameLocalBackup,
-				MountPath: fmt.Sprintf("/home/nonroot/%s", ptr.Deref(b.etcd.Spec.Backup.Store.Container, "")),
+				MountPath: kubernetes.MountPathLocalStore(b.etcd, b.provider),
 			}
 		}
 	case druidstore.GCS:
@@ -860,14 +860,3 @@ func (b *stsBuilder) getBackupVolume(ctx component.OperatorContext) (*corev1.Vol
 	}
 	return nil, nil
 }
-
-func getBackupStoreProvider(etcd *druidv1alpha1.Etcd) (*string, error) {
-	if !etcd.IsBackupStoreEnabled() {
-		return nil, nil
-	}
-	provider, err := druidstore.StorageProviderFromInfraProvider(etcd.Spec.Backup.Store.Provider)
-	if err != nil {
-		return nil, err
-	}
-	return &provider, nil
-}
diff --git a/internal/component/statefulset/stsmatcher.go b/internal/component/statefulset/stsmatcher.go
index 70f60d3b..f9e86c5e 100644
--- a/internal/component/statefulset/stsmatcher.go
+++ b/internal/component/statefulset/stsmatcher.go
@@ -163,7 +163,7 @@ func (s StatefulSetMatcher) matchPodHostAliases() gomegatypes.GomegaMatcher {
 
 func (s StatefulSetMatcher) matchPodInitContainers() gomegatypes.GomegaMatcher {
 	initContainerMatcherElements := make(map[string]gomegatypes.GomegaMatcher)
-	if s.etcd.IsBackupStoreEnabled() && s.provider != nil && *s.provider == druidstore.Local {
+	if s.etcd.IsBackupStoreEnabled() && s.provider != nil && *s.provider == druidstore.Local && !ptr.Deref(s.etcd.Spec.RunAsRoot, false) {
 		changeBackupBucketPermissionsMatcher := MatchFields(IgnoreExtras, Fields{
 			"Name":            Equal(common.InitContainerNameChangeBackupBucketPermissions),
 			"Image":           Equal(s.initContainerImage),
@@ -345,7 +345,10 @@ func (s StatefulSetMatcher) getEtcdBackupVolumeMountMatcher() gomegatypes.Gomega
 	switch *s.provider {
 	case druidstore.Local:
 		if s.etcd.Spec.Backup.Store.Container != nil {
-			return matchVolMount(common.VolumeNameLocalBackup, fmt.Sprintf("/home/nonroot/%s", ptr.Deref(s.etcd.Spec.Backup.Store.Container, "")))
+			if ptr.Deref(s.etcd.Spec.RunAsRoot, false) {
+				return matchVolMount(common.VolumeNameLocalBackup, fmt.Sprintf("/root/%s", *s.etcd.Spec.Backup.Store.Container))
+			}
+			return matchVolMount(common.VolumeNameLocalBackup, fmt.Sprintf("/home/nonroot/%s", *s.etcd.Spec.Backup.Store.Container))
 		}
 	case druidstore.GCS:
 		return matchVolMount(common.VolumeNameProviderBackupSecret, common.VolumeMountPathGCSBackupSecret)
diff --git a/internal/controller/compaction/reconciler.go b/internal/controller/compaction/reconciler.go
index 189176a6..daacc1cd 100644
--- a/internal/controller/compaction/reconciler.go
+++ b/internal/controller/compaction/reconciler.go
@@ -17,6 +17,7 @@ import (
 	druidstore "github.com/gardener/etcd-druid/internal/store"
 	"github.com/gardener/etcd-druid/internal/utils"
 	"github.com/gardener/etcd-druid/internal/utils/imagevector"
+	"github.com/gardener/etcd-druid/internal/utils/kubernetes"
 
 	"github.com/go-logr/logr"
 	"github.com/prometheus/client_golang/prometheus"
@@ -380,7 +381,7 @@ func (r *Reconciler) createCompactionJob(ctx context.Context, logger logr.Logger
 		return nil, err
 	}
 
-	//TODO (abdasgupta): Evaluate necessity of claiming object here after creation
+	// TODO (abdasgupta): Evaluate necessity of claiming object here after creation
 	return job, nil
 }
 
@@ -551,7 +552,7 @@ func getCompactionJobVolumeMounts(etcd *druidv1alpha1.Etcd) ([]v1.VolumeMount, e
 	case druidstore.Local:
 		vms = append(vms, v1.VolumeMount{
 			Name:      "host-storage",
-			MountPath: "/home/nonroot/" + ptr.Deref(etcd.Spec.Backup.Store.Container, ""),
+			MountPath: kubernetes.MountPathLocalStore(etcd, &provider),
 		})
 	case druidstore.GCS:
 		vms = append(vms, v1.VolumeMount{
diff --git a/internal/controller/etcd/reconcile_spec.go b/internal/controller/etcd/reconcile_spec.go
index aeb9419b..dca65645 100644
--- a/internal/controller/etcd/reconcile_spec.go
+++ b/internal/controller/etcd/reconcile_spec.go
@@ -169,9 +169,7 @@ func (r *Reconciler) recordEtcdSpecReconcileSuspension(etcd *druidv1alpha1.Etcd,
 }
 
 func (r *Reconciler) getOrderedOperatorsForPreSync() []component.Kind {
-	return []component.Kind{
-		component.ConfigMapKind,
-	}
+	return []component.Kind{}
 }
 
 func (r *Reconciler) getOrderedOperatorsForSync() []component.Kind {
diff --git a/internal/utils/kubernetes/store.go b/internal/utils/kubernetes/store.go
new file mode 100644
index 00000000..435bdfc7
--- /dev/null
+++ b/internal/utils/kubernetes/store.go
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package kubernetes
+
+import (
+	"k8s.io/utils/ptr"
+
+	druidv1alpha1 "github.com/gardener/etcd-druid/api/core/v1alpha1"
+	druidstore "github.com/gardener/etcd-druid/internal/store"
+)
+
+// GetBackupStoreProvider returns the provider name for the backup store. If the provider is not known, an error is
+// returned.
+func GetBackupStoreProvider(etcd *druidv1alpha1.Etcd) (*string, error) {
+	if !etcd.IsBackupStoreEnabled() {
+		return nil, nil
+	}
+	provider, err := druidstore.StorageProviderFromInfraProvider(etcd.Spec.Backup.Store.Provider)
+	if err != nil {
+		return nil, err
+	}
+	return &provider, nil
+}
+
+// MountPathLocalStore returns the mount path for the local store if the provider matches.
+func MountPathLocalStore(etcd *druidv1alpha1.Etcd, provider *string) string {
+	if !etcd.IsBackupStoreEnabled() || provider == nil || *provider != druidstore.Local {
+		return ""
+	}
+
+	homeDir := "/home/nonroot"
+	if ptr.Deref(etcd.Spec.RunAsRoot, false) {
+		homeDir = "/root"
+	}
+
+	path := ""
+	if container := etcd.Spec.Backup.Store.Container; container != nil {
+		path = "/" + *container
+	}
+
+	return homeDir + path
+}


Test patch:
diff --git a/internal/utils/kubernetes/store_test.go b/internal/utils/kubernetes/store_test.go
new file mode 100644
index 00000000..2899bfe8
--- /dev/null
+++ b/internal/utils/kubernetes/store_test.go
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package kubernetes
+
+import (
+	"testing"
+
+	"k8s.io/utils/ptr"
+
+	druidv1alpha1 "github.com/gardener/etcd-druid/api/core/v1alpha1"
+	testutils "github.com/gardener/etcd-druid/test/utils"
+
+	. "github.com/onsi/gomega"
+)
+
+func TestMountPathLocalStore(t *testing.T) {
+	newEtcdBuilder := func() *testutils.EtcdBuilder {
+		return testutils.EtcdBuilderWithDefaults(testutils.TestEtcdName, testutils.TestNamespace)
+	}
+	testCases := []struct {
+		name           string
+		etcd           *druidv1alpha1.Etcd
+		provider       *string
+		expectedResult string
+	}{
+		{
+			name:           "should return empty string if backup store is not enabled",
+			etcd:           newEtcdBuilder().Build(),
+			expectedResult: "",
+		},
+		{
+			name:           "should return empty string if provider is nil",
+			etcd:           newEtcdBuilder().WithProviderLocal().Build(),
+			expectedResult: "",
+		},
+		{
+			name:           "should return empty string if provider is not 'Local'",
+			etcd:           newEtcdBuilder().WithProviderS3().Build(),
+			provider:       ptr.To("S3"),
+			expectedResult: "",
+		},
+		{
+			name:           "should return non-root homedir with path if container is set",
+			etcd:           newEtcdBuilder().WithProviderLocal().Build(),
+			provider:       ptr.To("Local"),
+			expectedResult: "/home/nonroot/default.bkp",
+		},
+		{
+			name:           "should return root homedir with path if container is set",
+			etcd:           newEtcdBuilder().WithProviderLocal().WithRunAsRoot(ptr.To(true)).Build(),
+			provider:       ptr.To("Local"),
+			expectedResult: "/root/default.bkp",
+		},
+		{
+			name: "should return non-root homedir without path if container is nil",
+			etcd: func() *druidv1alpha1.Etcd {
+				etcd := newEtcdBuilder().WithProviderLocal().Build()
+				etcd.Spec.Backup.Store.Container = nil
+				return etcd
+			}(),
+			provider:       ptr.To("Local"),
+			expectedResult: "/home/nonroot",
+		},
+		{
+			name: "should return root homedir without path if container is nil",
+			etcd: func() *druidv1alpha1.Etcd {
+				etcd := newEtcdBuilder().WithProviderLocal().WithRunAsRoot(ptr.To(true)).Build()
+				etcd.Spec.Backup.Store.Container = nil
+				return etcd
+			}(),
+			provider:       ptr.To("Local"),
+			expectedResult: "/root",
+		},
+	}
+
+	g := NewWithT(t)
+	t.Parallel()
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			g.Expect(MountPathLocalStore(tc.etcd, tc.provider)).To(Equal(tc.expectedResult))
+		})
+	}
+}
diff --git a/test/utils/etcd.go b/test/utils/etcd.go
index 909ba58a..86e610b7 100644
--- a/test/utils/etcd.go
+++ b/test/utils/etcd.go
@@ -121,6 +121,14 @@ func (eb *EtcdBuilder) WithPeerTLS() *EtcdBuilder {
 	return eb
 }
 
+func (eb *EtcdBuilder) WithRunAsRoot(runAsRoot *bool) *EtcdBuilder {
+	if eb == nil || eb.etcd == nil {
+		return nil
+	}
+	eb.etcd.Spec.RunAsRoot = runAsRoot
+	return eb
+}
+
 func (eb *EtcdBuilder) WithReadyStatus() *EtcdBuilder {
 	if eb == nil || eb.etcd == nil {
 		return nil