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 # #####################