Repository: cloudnative-pg/cloudnative-pg

Issue:
[Feature]: Deprecate `.spec.backup.retentionPolicy`
### Is there an existing issue already for this feature request/idea?

- [x] I have searched for an existing issue, and could not find anything. I believe this is a new feature request to be evaluated.

### What problem is this feature going to solve? Why should it be added?

For historical reasons, retention policies are currently included in the `.spec.backup` section. This approach was necessary because object stores were the only supported method at the time, and we had not yet considered the use of plugins.

With the introduction of plugins, we need to establish a consistent way to define retention policies. This may involve allowing each plugin to manage its own retention policies.


### Describe the solution you'd like

- Implement the retention policies specification in the plugin
- Document that this feature will become obsolete and provide steps on how to move it in the plugin
- Log at cluster start that this feature is deprecated


### Describe alternatives you've considered

N/A

### Additional context

_No response_

### Backport?

No

### Are you willing to actively contribute to this feature?

Yes

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Hints from issue comments:

Solution patch:
diff --git a/internal/webhook/v1/cluster_webhook.go b/internal/webhook/v1/cluster_webhook.go
index c52d5cb5..453de809 100644
--- a/internal/webhook/v1/cluster_webhook.go
+++ b/internal/webhook/v1/cluster_webhook.go
@@ -2335,9 +2335,55 @@ func (v *ClusterCustomValidator) validatePgFailoverSlots(r *apiv1.Cluster) field
 
 func (v *ClusterCustomValidator) getAdmissionWarnings(r *apiv1.Cluster) admission.Warnings {
 	list := getMaintenanceWindowsAdmissionWarnings(r)
+	list = append(list, getInTreeBarmanWarnings(r)...)
+	list = append(list, getRetentionPolicyWarnings(r)...)
 	return append(list, getSharedBuffersWarnings(r)...)
 }
 
+func getInTreeBarmanWarnings(r *apiv1.Cluster) admission.Warnings {
+	var result admission.Warnings
+
+	var paths []string
+
+	if r.Spec.Backup != nil && r.Spec.Backup.BarmanObjectStore != nil {
+		paths = append(paths, field.NewPath("spec", "backup", "barmanObjectStore").String())
+	}
+
+	for idx, externalCluster := range r.Spec.ExternalClusters {
+		if externalCluster.BarmanObjectStore != nil {
+			paths = append(paths, field.NewPath("spec", "externalClusters", fmt.Sprintf("%d", idx),
+				"barmanObjectStore").String())
+		}
+	}
+
+	if len(paths) > 0 {
+		pathsStr := strings.Join(paths, ", ")
+		result = append(
+			result,
+			fmt.Sprintf("Native support for Barman Cloud backups and recovery is deprecated and will be "+
+				"completely removed in CloudNativePG 1.28.0. Found usage in: %s. "+
+				"Please migrate existing clusters to the new Barman Cloud Plugin to ensure a smooth transition.",
+				pathsStr),
+		)
+	}
+	return result
+}
+
+func getRetentionPolicyWarnings(r *apiv1.Cluster) admission.Warnings {
+	var result admission.Warnings
+
+	if r.Spec.Backup != nil && r.Spec.Backup.RetentionPolicy != "" && r.Spec.Backup.BarmanObjectStore == nil {
+		result = append(
+			result,
+			"Retention policies specified in .spec.backup.retentionPolicy are only used by the "+
+				"in-tree barman-cloud support, which is not being used in this cluster. "+
+				"Please use a backup plugin and migrate this configuration to the plugin configuration",
+		)
+	}
+
+	return result
+}
+
 func getSharedBuffersWarnings(r *apiv1.Cluster) admission.Warnings {
 	var result admission.Warnings
 



Test patch:
diff --git a/internal/webhook/v1/cluster_webhook_test.go b/internal/webhook/v1/cluster_webhook_test.go
index d92b6a90..2ab0e37a 100644
--- a/internal/webhook/v1/cluster_webhook_test.go
+++ b/internal/webhook/v1/cluster_webhook_test.go
@@ -5129,3 +5129,89 @@ var _ = Describe("", func() {
 		Expect(errs[0].Error()).To(ContainSubstring("error decoding liveness pinger config"))
 	})
 })
+
+var _ = Describe("getInTreeBarmanWarnings", func() {
+	It("returns no warnings when BarmanObjectStore is not configured", func() {
+		cluster := &apiv1.Cluster{
+			Spec: apiv1.ClusterSpec{
+				Backup:           nil,
+				ExternalClusters: nil,
+			},
+		}
+		Expect(getInTreeBarmanWarnings(cluster)).To(BeEmpty())
+	})
+
+	It("returns a warning when BarmanObjectStore is configured in backup", func() {
+		cluster := &apiv1.Cluster{
+			Spec: apiv1.ClusterSpec{
+				Backup: &apiv1.BackupConfiguration{
+					BarmanObjectStore: &apiv1.BarmanObjectStoreConfiguration{},
+				},
+			},
+		}
+		warnings := getInTreeBarmanWarnings(cluster)
+		Expect(warnings).To(HaveLen(1))
+		Expect(warnings[0]).To(ContainSubstring("spec.backup.barmanObjectStore"))
+	})
+
+	It("returns warnings for multiple external clusters with BarmanObjectStore", func() {
+		cluster := &apiv1.Cluster{
+			Spec: apiv1.ClusterSpec{
+				ExternalClusters: []apiv1.ExternalCluster{
+					{BarmanObjectStore: &apiv1.BarmanObjectStoreConfiguration{}},
+					{BarmanObjectStore: &apiv1.BarmanObjectStoreConfiguration{}},
+				},
+			},
+		}
+		warnings := getInTreeBarmanWarnings(cluster)
+		Expect(warnings).To(HaveLen(1))
+		Expect(warnings[0]).To(ContainSubstring("spec.externalClusters.0.barmanObjectStore"))
+		Expect(warnings[0]).To(ContainSubstring("spec.externalClusters.1.barmanObjectStore"))
+	})
+
+	It("returns warnings for both backup and external clusters with BarmanObjectStore", func() {
+		cluster := &apiv1.Cluster{
+			Spec: apiv1.ClusterSpec{
+				Backup: &apiv1.BackupConfiguration{
+					BarmanObjectStore: &apiv1.BarmanObjectStoreConfiguration{},
+				},
+				ExternalClusters: []apiv1.ExternalCluster{
+					{BarmanObjectStore: &apiv1.BarmanObjectStoreConfiguration{}},
+				},
+			},
+		}
+		warnings := getInTreeBarmanWarnings(cluster)
+		Expect(warnings).To(HaveLen(1))
+		Expect(warnings[0]).To(ContainSubstring("spec.backup.barmanObjectStore"))
+		Expect(warnings[0]).To(ContainSubstring("spec.externalClusters.0.barmanObjectStore"))
+	})
+})
+
+var _ = Describe("getRetentionPolicyWarnings", func() {
+	It("returns no warnings if the retention policy is used with the in-tree backup support", func() {
+		cluster := &apiv1.Cluster{
+			Spec: apiv1.ClusterSpec{
+				Backup: &apiv1.BackupConfiguration{
+					RetentionPolicy:   "this retention policy",
+					BarmanObjectStore: &apiv1.BarmanObjectStoreConfiguration{},
+				},
+			},
+		}
+
+		warnings := getRetentionPolicyWarnings(cluster)
+		Expect(warnings).To(BeEmpty())
+	})
+
+	It("return a warning when retention policies are declared and not used", func() {
+		cluster := &apiv1.Cluster{
+			Spec: apiv1.ClusterSpec{
+				Backup: &apiv1.BackupConfiguration{
+					RetentionPolicy: "this retention policy",
+				},
+			},
+		}
+
+		warnings := getRetentionPolicyWarnings(cluster)
+		Expect(warnings).To(HaveLen(1))
+	})
+})