From a6cf0e5ce46e289b2f358d8ff1c451135754e448 Mon Sep 17 00:00:00 2001 From: Yan Bondarenko <202671653+yankawai@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:14:18 +0300 Subject: [PATCH] fix(mongodb): set the s3 bucket and prefix on the bootstrap restore With bootstrap.enabled the chart rendered a PerconaServerMongoDBRestore without backupSource.s3.bucket. The operator CRD requires that field, so the API server rejected the restore and the HelmRelease failed. The prefix was missing too, and it matters just as much. For a backupSource restore the operator builds the PBM storage from backupSource.s3 alone and takes only the backup name from backupSource.destination, so a restore without the prefix would look for the backup at the bucket root. The restore now takes the bucket and prefix from the same helpers that fill the backup storage, so it reads from where the backups are written. Both places quote the two values, so a numeric bucket or prefix stays a string instead of making the resource invalid. The backup storage still leaves out an empty prefix, so running releases keep the same object. A destinationPath that names no bucket fails at render time instead of reaching the operator with an empty bucket. Assisted-by: LLM Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com> --- packages/apps/mongodb/templates/_backup.tpl | 13 ++ packages/apps/mongodb/templates/mongodb.yaml | 6 +- packages/apps/mongodb/templates/restore.yaml | 5 + .../mongodb/tests/restore-storage_test.yaml | 133 ++++++++++++++++++ packages/apps/mongodb/tests/restore_test.yaml | 117 +++++++++++++++ 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 packages/apps/mongodb/templates/_backup.tpl create mode 100644 packages/apps/mongodb/tests/restore-storage_test.yaml diff --git a/packages/apps/mongodb/templates/_backup.tpl b/packages/apps/mongodb/templates/_backup.tpl new file mode 100644 index 000000000..516cb4b44 --- /dev/null +++ b/packages/apps/mongodb/templates/_backup.tpl @@ -0,0 +1,13 @@ +{{- /* + Bucket and prefix of backup.destinationPath. The bootstrap restore has to + read from the place the backup storage writes to. The operator builds the + restore's storage from backupSource.s3 alone and takes only the backup name + from backupSource.destination, so both templates derive the pair here. +*/}} +{{- define "mongodb.backup.bucket" -}} +{{- .Values.backup.destinationPath | trimPrefix "s3://" | regexFind "^[^/]+" -}} +{{- end -}} + +{{- define "mongodb.backup.prefix" -}} +{{- .Values.backup.destinationPath | trimPrefix "s3://" | splitList "/" | rest | join "/" -}} +{{- end -}} diff --git a/packages/apps/mongodb/templates/mongodb.yaml b/packages/apps/mongodb/templates/mongodb.yaml index 439bb978a..c12dbca07 100644 --- a/packages/apps/mongodb/templates/mongodb.yaml +++ b/packages/apps/mongodb/templates/mongodb.yaml @@ -244,8 +244,10 @@ spec: s3-storage: type: s3 s3: - bucket: {{ .Values.backup.destinationPath | trimPrefix "s3://" | regexFind "^[^/]+" }} - prefix: {{ .Values.backup.destinationPath | trimPrefix "s3://" | splitList "/" | rest | join "/" }} + bucket: {{ include "mongodb.backup.bucket" . | quote }} + {{- with include "mongodb.backup.prefix" . }} + prefix: {{ . | quote }} + {{- end }} endpointUrl: {{ .Values.backup.endpointURL }} credentialsSecret: {{ .Release.Name }}-s3-creds insecureSkipTLSVerify: {{ .Values.backup.insecureSkipTLSVerify | default false }} diff --git a/packages/apps/mongodb/templates/restore.yaml b/packages/apps/mongodb/templates/restore.yaml index 083a72091..2742cc81f 100644 --- a/packages/apps/mongodb/templates/restore.yaml +++ b/packages/apps/mongodb/templates/restore.yaml @@ -8,6 +8,9 @@ {{- if not .Values.backup.destinationPath }} {{- fail "backup.destinationPath is required when bootstrap.enabled is true" }} {{- end }} +{{- if not (include "mongodb.backup.bucket" .) }} +{{- fail "backup.destinationPath must name a bucket (s3://bucket/path/) when bootstrap.enabled is true" }} +{{- end }} {{- if not .Values.backup.endpointURL }} {{- fail "backup.endpointURL is required when bootstrap.enabled is true" }} {{- end }} @@ -33,6 +36,8 @@ spec: type: logical destination: {{ .Values.backup.destinationPath | trimSuffix "/" }}/{{ .Values.bootstrap.backupName }} s3: + bucket: {{ include "mongodb.backup.bucket" . | quote }} + prefix: {{ include "mongodb.backup.prefix" . | quote }} credentialsSecret: {{ .Release.Name }}-s3-creds endpointUrl: {{ .Values.backup.endpointURL }} insecureSkipTLSVerify: {{ .Values.backup.insecureSkipTLSVerify | default false }} diff --git a/packages/apps/mongodb/tests/restore-storage_test.yaml b/packages/apps/mongodb/tests/restore-storage_test.yaml new file mode 100644 index 000000000..1f1245bf8 --- /dev/null +++ b/packages/apps/mongodb/tests/restore-storage_test.yaml @@ -0,0 +1,133 @@ +suite: bootstrap restore reads where the backup storage writes + +templates: + - templates/mongodb.yaml + - templates/restore.yaml + +release: + name: test-mongodb + namespace: tenant-test + +tests: + - it: gives the restore the same S3 location and access as the backup storage + set: + _cluster: + cluster-domain: cozy.local + bootstrap: + enabled: true + backupName: "2026-10-01T02:00:21Z" + backup: + enabled: true + destinationPath: "s3://mybucket/mongodb/prod/" + endpointURL: "https://s3.example.org" + insecureSkipTLSVerify: true + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - equal: + path: spec.backup.storages.s3-storage.s3.bucket + value: mybucket + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.bucket + value: mybucket + template: templates/restore.yaml + - equal: + path: spec.backup.storages.s3-storage.s3.prefix + value: mongodb/prod/ + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.prefix + value: mongodb/prod/ + template: templates/restore.yaml + - equal: + path: spec.backup.storages.s3-storage.s3.endpointUrl + value: https://s3.example.org + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.endpointUrl + value: https://s3.example.org + template: templates/restore.yaml + - equal: + path: spec.backup.storages.s3-storage.s3.credentialsSecret + value: test-mongodb-s3-creds + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.credentialsSecret + value: test-mongodb-s3-creds + template: templates/restore.yaml + - equal: + path: spec.backup.storages.s3-storage.s3.insecureSkipTLSVerify + value: true + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.insecureSkipTLSVerify + value: true + template: templates/restore.yaml + - equal: + path: spec.backup.storages.s3-storage.s3.forcePathStyle + value: true + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.forcePathStyle + value: true + template: templates/restore.yaml + - equal: + path: spec.backupSource.destination + value: s3://mybucket/mongodb/prod/2026-10-01T02:00:21Z + template: templates/restore.yaml + + - it: keeps a numeric bucket and prefix as strings on both sides + set: + _cluster: + cluster-domain: cozy.local + bootstrap: + enabled: true + backupName: "2026-10-01T02:00:21Z" + backup: + enabled: true + destinationPath: "s3://2024/10" + endpointURL: "https://s3.example.org" + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - equal: + path: spec.backup.storages.s3-storage.s3.bucket + value: "2024" + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backup.storages.s3-storage.s3.prefix + value: "10" + template: templates/mongodb.yaml + documentSelector: + path: kind + value: PerconaServerMongoDB + - equal: + path: spec.backupSource.s3.bucket + value: "2024" + template: templates/restore.yaml + - equal: + path: spec.backupSource.s3.prefix + value: "10" + template: templates/restore.yaml diff --git a/packages/apps/mongodb/tests/restore_test.yaml b/packages/apps/mongodb/tests/restore_test.yaml index 8088014e0..8337873b5 100644 --- a/packages/apps/mongodb/tests/restore_test.yaml +++ b/packages/apps/mongodb/tests/restore_test.yaml @@ -281,6 +281,123 @@ tests: path: spec.backupSource.s3.forcePathStyle value: true + - it: sets bucket and prefix from destinationPath + release: + name: test-mongodb + namespace: tenant-test + set: + bootstrap: + enabled: true + backupName: "backup" + backup: + destinationPath: "s3://mybucket/mongodb/prod/" + endpointURL: "http://minio:9000" + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - equal: + path: spec.backupSource.s3.bucket + value: mybucket + - equal: + path: spec.backupSource.s3.prefix + value: mongodb/prod/ + + - it: keeps a prefix without a trailing slash as given + release: + name: test-mongodb + namespace: tenant-test + set: + bootstrap: + enabled: true + backupName: "backup" + backup: + destinationPath: "s3://mybucket/mongodb/prod" + endpointURL: "http://minio:9000" + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - equal: + path: spec.backupSource.s3.prefix + value: mongodb/prod + - equal: + path: spec.backupSource.destination + value: s3://mybucket/mongodb/prod/backup + + - it: sets an empty prefix when destinationPath is the bucket root + release: + name: test-mongodb + namespace: tenant-test + set: + bootstrap: + enabled: true + backupName: "backup" + backup: + destinationPath: "s3://mybucket" + endpointURL: "http://minio:9000" + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - equal: + path: spec.backupSource.s3.bucket + value: mybucket + - equal: + path: spec.backupSource.s3.prefix + value: "" + - equal: + path: spec.backupSource.destination + value: s3://mybucket/backup + + - it: keeps a numeric bucket and prefix as strings + release: + name: test-mongodb + namespace: tenant-test + set: + bootstrap: + enabled: true + backupName: "backup" + backup: + destinationPath: "s3://2024/10" + endpointURL: "http://minio:9000" + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - equal: + path: spec.backupSource.s3.bucket + value: "2024" + - equal: + path: spec.backupSource.s3.prefix + value: "10" + + - it: fails when destinationPath names no bucket + release: + name: test-mongodb + namespace: tenant-test + set: + bootstrap: + enabled: true + backupName: "backup" + backup: + destinationPath: "s3://" + endpointURL: "http://minio:9000" + s3AccessKey: "access" + s3SecretKey: "secret" + asserts: + - failedTemplate: + errorMessage: "backup.destinationPath must name a bucket (s3://bucket/path/) when bootstrap.enabled is true" + + - it: does not check the bucket when bootstrap is disabled + release: + name: test-mongodb + namespace: tenant-test + set: + bootstrap: + enabled: false + backup: + destinationPath: "s3://" + asserts: + - hasDocuments: + count: 0 + ##################### # PITR # #####################