diff --git a/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml b/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml index b31dda364..c6207c514 100644 --- a/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml +++ b/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml @@ -5870,7 +5870,6 @@ spec: required: - name type: object - minItems: 1 type: array x-kubernetes-list-map-keys: - name @@ -7091,6 +7090,11 @@ spec: description: Enable tracking latest restorable time type: boolean type: object + x-kubernetes-validations: + - message: At least one repository must be configured when backups + are enabled + rule: (has(self.enabled) && self.enabled == false) || size(self.pgbackrest.repos) + > 0 crVersion: description: |- Version of the operator. Update this to new version after operator diff --git a/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml b/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml index 8b38ce51f..1dc1ef246 100644 --- a/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml +++ b/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml @@ -6275,7 +6275,6 @@ spec: required: - name type: object - minItems: 1 type: array x-kubernetes-list-map-keys: - name @@ -7496,6 +7495,11 @@ spec: description: Enable tracking latest restorable time type: boolean type: object + x-kubernetes-validations: + - message: At least one repository must be configured when backups + are enabled + rule: (has(self.enabled) && self.enabled == false) || size(self.pgbackrest.repos) + > 0 crVersion: description: |- Version of the operator. Update this to new version after operator diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 97b02f85f..2f326a7c7 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -6572,7 +6572,6 @@ spec: required: - name type: object - minItems: 1 type: array x-kubernetes-list-map-keys: - name @@ -7793,6 +7792,11 @@ spec: description: Enable tracking latest restorable time type: boolean type: object + x-kubernetes-validations: + - message: At least one repository must be configured when backups + are enabled + rule: (has(self.enabled) && self.enabled == false) || size(self.pgbackrest.repos) + > 0 crVersion: description: |- Version of the operator. Update this to new version after operator diff --git a/deploy/crd.yaml b/deploy/crd.yaml index 9124e3457..2e75d79bb 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -6572,7 +6572,6 @@ spec: required: - name type: object - minItems: 1 type: array x-kubernetes-list-map-keys: - name @@ -7793,6 +7792,11 @@ spec: description: Enable tracking latest restorable time type: boolean type: object + x-kubernetes-validations: + - message: At least one repository must be configured when backups + are enabled + rule: (has(self.enabled) && self.enabled == false) || size(self.pgbackrest.repos) + > 0 crVersion: description: |- Version of the operator. Update this to new version after operator diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 8d766bd59..19bf06c04 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -6572,7 +6572,6 @@ spec: required: - name type: object - minItems: 1 type: array x-kubernetes-list-map-keys: - name @@ -7793,6 +7792,11 @@ spec: description: Enable tracking latest restorable time type: boolean type: object + x-kubernetes-validations: + - message: At least one repository must be configured when backups + are enabled + rule: (has(self.enabled) && self.enabled == false) || size(self.pgbackrest.repos) + > 0 crVersion: description: |- Version of the operator. Update this to new version after operator diff --git a/percona/controller/pgcluster/controller_test.go b/percona/controller/pgcluster/controller_test.go index 7c5d3926c..548ef3a0c 100644 --- a/percona/controller/pgcluster/controller_test.go +++ b/percona/controller/pgcluster/controller_test.go @@ -2353,6 +2353,86 @@ var _ = Describe("CR Validations", Ordered, func() { }) }) }) + + Context("Backup repository validations", Ordered, func() { + When("creating a CR with valid backup configurations", func() { + It("should accept backups disabled with no repositories", func() { + cr, err := readDefaultCR("cr-validation-backup-1", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.Backups.Enabled = &f + cr.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{} + + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + }) + + It("should accept backups enabled with at least one repository", func() { + cr, err := readDefaultCR("cr-validation-backup-2", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.Backups.Enabled = &t + cr.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{ + {Name: "repo1"}, + } + + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + }) + + It("should accept backups disabled with at least one repository", func() { + cr, err := readDefaultCR("cr-validation-backup-3", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.Backups.Enabled = &f + cr.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{ + {Name: "repo1"}, + } + + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + }) + + It("should accept backups enabled (nil - default true) with repositories", func() { + cr, err := readDefaultCR("cr-validation-backup-4", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.Backups.Enabled = nil // defaults to enabled + cr.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{ + {Name: "repo1"}, + } + + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + }) + }) + + When("creating a CR with invalid backup configurations", func() { + It("should reject backups enabled with no repositories", func() { + cr, err := readDefaultCR("cr-validation-backup-5", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.Backups.Enabled = &t + cr.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{} + + err = k8sClient.Create(ctx, cr) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "At least one repository must be configured when backups are enabled", + )) + }) + + It("should reject backups enabled (nil - default true) with no repositories", func() { + cr, err := readDefaultCR("cr-validation-backup-6", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.Backups.Enabled = nil // defaults to enabled + cr.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{} + + err = k8sClient.Create(ctx, cr) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring( + "At least one repository must be configured when backups are enabled", + )) + }) + }) + }) }) var _ = Describe("Init Container", Ordered, func() { diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go index 8b0fb316e..ffa75d94e 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go @@ -476,6 +476,8 @@ type Patroni struct { Version string `json:"version"` } +// Backups struct. +// +kubebuilder:validation:XValidation:rule="(has(self.enabled) && self.enabled == false) || size(self.pgbackrest.repos) > 0",message="At least one repository must be configured when backups are enabled" type Backups struct { Enabled *bool `json:"enabled,omitempty"` @@ -562,7 +564,6 @@ type PGBackRestArchive struct { Jobs *crunchyv1beta1.BackupJobs `json:"jobs,omitempty"` // Defines a pgBackRest repository - // +kubebuilder:validation:MinItems=1 // +listType=map // +listMapKey=name Repos []crunchyv1beta1.PGBackRestRepo `json:"repos"`