fix(mongodb): keep the cleanup escape hatch off legacy operator CRs, and surface a stuck finalizer

The skip-artifact-cleanup annotation reaches releaseMongoDBCleanup before the
ownership check, and the operator-CR delete sat outside the finalizer gate, so
a legacy backup (no delete-backup finalizer) lost its PerconaServerMongoDBBackup
record when a tenant set the annotation to release a stuck Backup or applied it
across a namespace during teardown. That record is what a psmdb restore-by-name
resolves against, and both the RBAC comment and the docs promise it is never
touched. Gate every write in the release on the finalizer being present: an
unowned (legacy) CR or one that cannot be read at all is left intact, and the
Cozystack Backup still releases.

Surface a failed finalizer strip. The release must proceed even against an
unreachable CR, but the swallowed patch error left the operator CR holding
percona.com/delete-backup — pinning its namespace in Terminating, the outcome
the branch exists to prevent — while the Event reported the success case. Emit
a FinalizerNotStripped Warning naming the object so an operator can find it.

Mark the sibling backup fields (schedule, retentionPolicy, destinationPath,
endpointURL, insecureSkipTLSVerify, s3AccessKey, s3SecretKey) as ignored under
useSystemBucket, so a tenant editing one instead of the flag itself is not
misled by a description that describes only the legacy flow; regenerate the
schema, README and mongodb-rd. Pair the insecureSkipTLSVerify rendering
assertion with a case that flips the platform knob on, so the two together
discriminate the rendered value from a hardcoded default.

Assisted-By: LLM
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
This commit is contained in:
Andrey Kolkov committed 2026-09-22 12:03:58 +04:00
1 parent d58322e253
commit cc51e040ae
8 files changed
+141 -50

No files matched your search

+7 -7
View File
@@ -60,28 +60,28 @@ type ConfigSpec struct {
}
type Backup struct {
// Destination path for backups (e.g. s3://bucket/path/).
// Destination path for backups (e.g. s3://bucket/path/). Ignored when useSystemBucket is true (the driver injects the platform bucket).
// +kubebuilder:default:="s3://bucket/path/to/folder/"
DestinationPath string `json:"destinationPath,omitempty"`
// Enable regular backups.
// +kubebuilder:default:=false
Enabled bool `json:"enabled"`
// S3 endpoint URL for uploads.
// S3 endpoint URL for uploads. Ignored when useSystemBucket is true (the strategy supplies the platform endpoint).
// +kubebuilder:default:="http://minio-gateway-service:9000"
EndpointURL string `json:"endpointURL,omitempty"`
// Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs).
// Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs). Ignored when useSystemBucket is true (the platform backupStorage knob applies instead).
// +kubebuilder:default:=false
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
// Retention policy (e.g. "30d").
// Retention policy (e.g. "30d"). Ignored when useSystemBucket is true (no engine-side retention on that flow).
// +kubebuilder:default:="30d"
RetentionPolicy string `json:"retentionPolicy,omitempty"`
// Access key for S3 authentication.
// Access key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).
// +kubebuilder:default:=""
S3AccessKey string `json:"s3AccessKey,omitempty"`
// Secret key for S3 authentication.
// Secret key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).
// +kubebuilder:default:=""
S3SecretKey string `json:"s3SecretKey,omitempty"`
// Cron schedule for automated backups.
// Cron schedule for automated backups. Ignored when useSystemBucket is true (scheduling moves to the BackupClass Plan).
// +kubebuilder:default:="0 2 * * *"
Schedule string `json:"schedule,omitempty"`
// Opt-in: back up to the platform system bucket without supplying S3 credentials. The cozy-default MongoDB BackupClass injects the storage at BackupJob time; tenants do not set s3AccessKey/s3SecretKey/destinationPath/endpointURL, and backups scope to `<namespace>/<application>`. Scheduling and retention move to the BackupClass Plan, so `schedule`, `retentionPolicy` and PITR are ignored while this is on. See docs/operations/backup-classes.md.
@@ -792,28 +792,49 @@ func (r *BackupReconciler) cleanupMongoDBBackup(ctx context.Context, backup *bac
return ctrl.Result{RequeueAfter: psmdbPollInterval}, nil
}
// releaseMongoDBCleanup gives up pruning the archive and lets the Cozystack
// Backup (and any enclosing namespace) finish deleting: best-effort, it strips
// the driver's delete-backup finalizer from the operator CR so the CR no longer
// blocks teardown and deletes that CR, then records a Warning naming the object
// left in the bucket. Every apiserver step is best-effort — a failed read or
// write is swallowed — because the caller reaches here precisely to let a wedged
// Backup go, and must never be blocked by the same unreachable CR. Takes the CR
// name rather than a fetched object so it works on the annotation path, which
// runs before any read. Mirrors the Redis/Rabbitmq release path.
// releaseMongoDBCleanup lets the Cozystack Backup (and any enclosing namespace)
// finish deleting when the archive cannot be pruned in place. It only ever
// touches an operator CR the driver OWNS — one carrying the delete-backup
// finalizer: it strips that finalizer so the CR no longer blocks teardown and
// deletes it. A legacy CR (no finalizer) or one that cannot be read at all is
// left entirely intact, so the never-deleted contract for legacy backups holds
// even though the annotation reaches this path before the ownership check, and
// an unreadable CR still releases. The finalizer strip is best-effort — the
// release must proceed even against an unreachable CR — but a failed strip is
// surfaced (log + Event), because the CR then keeps the finalizer and can hold
// its namespace in Terminating, and the operator needs to know which one. Takes
// the CR name so it works on the annotation path, which runs before any read.
func (r *BackupReconciler) releaseMongoDBCleanup(ctx context.Context, backup *backupsv1alpha1.Backup, sourceBackupName, reason string) (ctrl.Result, error) {
logger := getLogger(ctx)
live := &psmdbtypes.PerconaServerMongoDBBackup{}
if err := r.Get(ctx, types.NamespacedName{Namespace: backup.Namespace, Name: sourceBackupName}, live); err == nil {
if controllerutil.ContainsFinalizer(live, psmdbDeleteBackupFinalizer) {
base := live.DeepCopy()
controllerutil.RemoveFinalizer(live, psmdbDeleteBackupFinalizer)
_ = r.Patch(ctx, live, client.MergeFrom(base))
}
if live.DeletionTimestamp.IsZero() {
_ = r.Delete(ctx, live)
}
err := r.Get(ctx, types.NamespacedName{Namespace: backup.Namespace, Name: sourceBackupName}, live)
if err != nil || !controllerutil.ContainsFinalizer(live, psmdbDeleteBackupFinalizer) {
// Legacy backup (no delete-backup finalizer) or an unreadable CR: nothing
// of the driver's to strip or delete. Leave the operator CR untouched and
// release the Cozystack Backup.
logger.Debug("releasing MongoDB Backup; no driver-owned archive to prune",
"backup", backup.Name, "sourceBackup", sourceBackupName, "reason", reason)
return ctrl.Result{}, nil
}
getLogger(ctx).Info("releasing MongoDB Backup without pruning its object",
base := live.DeepCopy()
controllerutil.RemoveFinalizer(live, psmdbDeleteBackupFinalizer)
if perr := r.Patch(ctx, live, client.MergeFrom(base)); perr != nil {
// The release still proceeds (an unreachable CR must not wedge the
// Backup), but the CR keeps the finalizer and can pin its namespace in
// Terminating — surface which object so an operator can find it.
logger.Info("released MongoDB Backup but could not strip the delete-backup finalizer; the operator CR may hold its namespace in Terminating",
"backup", backup.Name, "sourceBackup", sourceBackupName, "reason", reason, "error", perr)
if r.Recorder != nil {
r.Recorder.Eventf(backup, corev1.EventTypeWarning, "FinalizerNotStripped",
"released Backup but could not strip the delete-backup finalizer from PerconaServerMongoDBBackup %s (%v); it may keep the namespace in Terminating", sourceBackupName, perr)
}
return ctrl.Result{}, nil
}
if live.DeletionTimestamp.IsZero() {
_ = r.Delete(ctx, live)
}
logger.Info("releasing MongoDB Backup, leaving its shared-bucket object for a lifecycle policy",
"backup", backup.Name, "sourceBackup", sourceBackupName, "reason", reason)
if r.Recorder != nil {
r.Recorder.Eventf(backup, corev1.EventTypeWarning, "ArtifactNotDeleted",
@@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
@@ -1507,4 +1508,57 @@ func TestCleanupMongoDBBackup(t *testing.T) {
t.Fatalf("NoMatchError must be treated as gone and release, got res=%+v err=%v", res, err)
}
})
// The annotation reaches releaseMongoDBCleanup before the ownership check, so
// the release must itself refuse to touch a CR the driver does not own — the
// legacy operator-side backup record is what a psmdb restore-by-name resolves
// against, and the RBAC comment and docs both promise it is never deleted.
t.Run("legacy backup with the skip annotation keeps its operator CR", func(t *testing.T) {
c := newMongoDBStrategyTestClient(t, opBackup(false), liveNamespace)
r := &BackupReconciler{Client: c, Recorder: record.NewFakeRecorder(10)}
res, err := r.cleanupMongoDBBackup(context.Background(), newBackup(map[string]string{psmdbSkipArtifactCleanupAnnotation: "true"}))
if err != nil || res.RequeueAfter != 0 {
t.Fatalf("legacy + annotation must release, got res=%+v err=%v", res, err)
}
got := getOp(t, c)
if !got.DeletionTimestamp.IsZero() {
t.Errorf("the escape hatch must not delete a legacy (unowned) operator CR")
}
})
// When the finalizer strip fails, the CR keeps percona.com/delete-backup and
// can pin its namespace in Terminating; the release still proceeds, but it
// must say which object is stuck rather than emit the success-case Event.
t.Run("a failed finalizer strip surfaces the stuck object", func(t *testing.T) {
s := runtime.NewScheme()
_ = scheme.AddToScheme(s)
_ = backupsv1alpha1.AddToScheme(s)
_ = psmdbtypes.AddToScheme(s)
c := clientfake.NewClientBuilder().
WithScheme(s).
WithObjects(opBackup(true)).
WithInterceptorFuncs(interceptor.Funcs{
Patch: func(ctx context.Context, cl client.WithWatch, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
if _, ok := obj.(*psmdbtypes.PerconaServerMongoDBBackup); ok {
return apierrors.NewServiceUnavailable("apiserver down")
}
return cl.Patch(ctx, obj, patch, opts...)
},
}).
Build()
rec := record.NewFakeRecorder(10)
r := &BackupReconciler{Client: c, Recorder: rec}
res, err := r.cleanupMongoDBBackup(context.Background(), newBackup(map[string]string{psmdbSkipArtifactCleanupAnnotation: "true"}))
if err != nil || res.RequeueAfter != 0 {
t.Fatalf("release must proceed even when the strip fails, got res=%+v err=%v", res, err)
}
select {
case ev := <-rec.Events:
if !strings.Contains(ev, "FinalizerNotStripped") || !strings.Contains(ev, "op-bk") {
t.Errorf("expected a FinalizerNotStripped event naming the object, got %q", ev)
}
default:
t.Errorf("expected an event surfacing the stuck finalizer, got none")
}
})
}
+7 -7
View File
@@ -252,13 +252,13 @@ Releases with `external: false`, and sharded releases in any configuration, are
| `backup` | Backup configuration. | `object` | `{}` |
| `backup.enabled` | Enable regular backups. | `bool` | `false` |
| `backup.useSystemBucket` | Opt-in: back up to the platform system bucket without supplying S3 credentials. The cozy-default MongoDB BackupClass injects the storage at BackupJob time; tenants do not set s3AccessKey/s3SecretKey/destinationPath/endpointURL, and backups scope to `<namespace>/<application>`. Scheduling and retention move to the BackupClass Plan, so `schedule`, `retentionPolicy` and PITR are ignored while this is on. See docs/operations/backup-classes.md. | `bool` | `false` |
| `backup.schedule` | Cron schedule for automated backups. | `string` | `0 2 * * *` |
| `backup.retentionPolicy` | Retention policy (e.g. "30d"). | `string` | `30d` |
| `backup.destinationPath` | Destination path for backups (e.g. s3://bucket/path/). | `string` | `s3://bucket/path/to/folder/` |
| `backup.endpointURL` | S3 endpoint URL for uploads. | `string` | `http://minio-gateway-service:9000` |
| `backup.insecureSkipTLSVerify` | Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs). | `bool` | `false` |
| `backup.s3AccessKey` | Access key for S3 authentication. | `string` | `""` |
| `backup.s3SecretKey` | Secret key for S3 authentication. | `string` | `""` |
| `backup.schedule` | Cron schedule for automated backups. Ignored when useSystemBucket is true (scheduling moves to the BackupClass Plan). | `string` | `0 2 * * *` |
| `backup.retentionPolicy` | Retention policy (e.g. "30d"). Ignored when useSystemBucket is true (no engine-side retention on that flow). | `string` | `30d` |
| `backup.destinationPath` | Destination path for backups (e.g. s3://bucket/path/). Ignored when useSystemBucket is true (the driver injects the platform bucket). | `string` | `s3://bucket/path/to/folder/` |
| `backup.endpointURL` | S3 endpoint URL for uploads. Ignored when useSystemBucket is true (the strategy supplies the platform endpoint). | `string` | `http://minio-gateway-service:9000` |
| `backup.insecureSkipTLSVerify` | Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs). Ignored when useSystemBucket is true (the platform backupStorage knob applies instead). | `bool` | `false` |
| `backup.s3AccessKey` | Access key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds). | `string` | `""` |
| `backup.s3SecretKey` | Secret key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds). | `string` | `""` |
### Bootstrap (recovery) parameters
+7 -7
View File
@@ -273,7 +273,7 @@
],
"properties": {
"destinationPath": {
"description": "Destination path for backups (e.g. s3://bucket/path/).",
"description": "Destination path for backups (e.g. s3://bucket/path/). Ignored when useSystemBucket is true (the driver injects the platform bucket).",
"type": "string",
"default": "s3://bucket/path/to/folder/"
},
@@ -283,32 +283,32 @@
"default": false
},
"endpointURL": {
"description": "S3 endpoint URL for uploads.",
"description": "S3 endpoint URL for uploads. Ignored when useSystemBucket is true (the strategy supplies the platform endpoint).",
"type": "string",
"default": "http://minio-gateway-service:9000"
},
"insecureSkipTLSVerify": {
"description": "Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs).",
"description": "Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs). Ignored when useSystemBucket is true (the platform backupStorage knob applies instead).",
"type": "boolean",
"default": false
},
"retentionPolicy": {
"description": "Retention policy (e.g. \"30d\").",
"description": "Retention policy (e.g. \"30d\"). Ignored when useSystemBucket is true (no engine-side retention on that flow).",
"type": "string",
"default": "30d"
},
"s3AccessKey": {
"description": "Access key for S3 authentication.",
"description": "Access key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).",
"type": "string",
"default": ""
},
"s3SecretKey": {
"description": "Secret key for S3 authentication.",
"description": "Secret key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).",
"type": "string",
"default": ""
},
"schedule": {
"description": "Cron schedule for automated backups.",
"description": "Cron schedule for automated backups. Ignored when useSystemBucket is true (scheduling moves to the BackupClass Plan).",
"type": "string",
"default": "0 2 * * *"
},
+7 -7
View File
@@ -156,13 +156,13 @@ databases: {}
## @typedef {struct} Backup - Backup configuration.
## @field {bool} enabled - Enable regular backups.
## @field {bool} [useSystemBucket] - Opt-in: back up to the platform system bucket without supplying S3 credentials. The cozy-default MongoDB BackupClass injects the storage at BackupJob time; tenants do not set s3AccessKey/s3SecretKey/destinationPath/endpointURL, and backups scope to `<namespace>/<application>`. Scheduling and retention move to the BackupClass Plan, so `schedule`, `retentionPolicy` and PITR are ignored while this is on. See docs/operations/backup-classes.md.
## @field {string} [schedule] - Cron schedule for automated backups.
## @field {string} [retentionPolicy] - Retention policy (e.g. "30d").
## @field {string} [destinationPath] - Destination path for backups (e.g. s3://bucket/path/).
## @field {string} [endpointURL] - S3 endpoint URL for uploads.
## @field {bool} [insecureSkipTLSVerify] - Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs).
## @field {string} [s3AccessKey] - Access key for S3 authentication.
## @field {string} [s3SecretKey] - Secret key for S3 authentication.
## @field {string} [schedule] - Cron schedule for automated backups. Ignored when useSystemBucket is true (scheduling moves to the BackupClass Plan).
## @field {string} [retentionPolicy] - Retention policy (e.g. "30d"). Ignored when useSystemBucket is true (no engine-side retention on that flow).
## @field {string} [destinationPath] - Destination path for backups (e.g. s3://bucket/path/). Ignored when useSystemBucket is true (the driver injects the platform bucket).
## @field {string} [endpointURL] - S3 endpoint URL for uploads. Ignored when useSystemBucket is true (the strategy supplies the platform endpoint).
## @field {bool} [insecureSkipTLSVerify] - Skip TLS verification for the S3 endpoint (self-signed, e.g. in-cluster seaweedfs). Ignored when useSystemBucket is true (the platform backupStorage knob applies instead).
## @field {string} [s3AccessKey] - Access key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).
## @field {string} [s3SecretKey] - Secret key for S3 authentication. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).
## @param {Backup} backup - Backup configuration.
backup:
@@ -179,8 +179,10 @@ tests:
value: true
template: templates/strategy-mongodb-default.yaml
# insecureSkipTLSVerify is plumbed end-to-end (strategy -> driver-injected
# storage entry); the template must render it from the platform knob, not
# leave it unset (which pins the injected entry to false regardless).
# storage entry). This case pins the default (knob off -> false); the
# "renders insecureSkipTLSVerify from the platform knob" case below flips
# the knob on, so the pair discriminates the rendered value from a
# hardcoded literal (either would redden one of the two).
- equal:
path: spec.template.s3.insecureSkipTLSVerify
value: false
@@ -240,6 +242,20 @@ tests:
value: "true"
template: templates/velero-bsl.yaml
- it: "the MongoDB strategy renders insecureSkipTLSVerify from the platform knob"
# Pairs with the default (false) assertion above: with the knob flipped on,
# the injected storage entry must carry true. A template that hardcoded either
# literal would redden one of the two cases, so together they prove the value
# tracks backupStorage.insecureSkipTLSVerify rather than a constant.
set:
backupStorage.bucketNameOverride: test-bucket
backupStorage.insecureSkipTLSVerify: true
asserts:
- equal:
path: spec.template.s3.insecureSkipTLSVerify
value: true
template: templates/strategy-mongodb-default.yaml
- it: "external S3 (provisionBucket=false): Strategy CRs render against the configured bucketName, no Bucket CR is created"
set:
backupStorage.provisionBucket: false
+1 -1
View File
@@ -8,7 +8,7 @@ spec:
singular: mongodb
plural: mongodbs
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of MongoDB replicas in replica set.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"t1.small","enum":["t1.nano","t1.micro","t1.small","t1.medium","t1.large","t1.xlarge","t1.2xlarge","t1.4xlarge","c1.nano","c1.micro","c1.small","c1.medium","c1.large","c1.xlarge","c1.2xlarge","c1.4xlarge","s1.nano","s1.micro","s1.small","s1.medium","s1.large","s1.xlarge","s1.2xlarge","s1.4xlarge","u1.nano","u1.micro","u1.small","u1.medium","u1.large","u1.xlarge","u1.2xlarge","u1.4xlarge","m1.nano","m1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m1.2xlarge","m1.4xlarge","nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"","x-kubernetes-validations":[{"rule":"self == oldSelf","message":"storageClass is immutable"}],"x-cozystack-options":{"source":"storageclass"}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"MongoDB major version to deploy.","type":"string","default":"v8","enum":["v8","v7","v6"]},"sharding":{"description":"Enable sharded cluster mode. When disabled, deploys a replica set.","type":"boolean","default":false},"shardingConfig":{"description":"Configuration for sharded cluster mode.","type":"object","default":{},"required":["configServerSize","configServers","mongos"],"properties":{"configServerSize":{"description":"PVC size for config servers.","default":"3Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"configServers":{"description":"Number of config server replicas.","type":"integer","default":3},"mongos":{"description":"Number of mongos router replicas.","type":"integer","default":2},"shards":{"description":"List of shard configurations.","type":"array","default":[{"name":"rs0","replicas":3,"size":"10Gi"}],"items":{"type":"object","required":["name","replicas","size"],"properties":{"name":{"description":"Shard name.","type":"string"},"replicas":{"description":"Number of replicas in this shard.","type":"integer"},"size":{"description":"PVC size for this shard.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"}}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges (readWrite + dbAdmin).","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"insecureSkipTLSVerify":{"description":"Skip TLS verification for the S3 endpoint (self-Line truncated
{"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of MongoDB replicas in replica set.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"t1.small","enum":["t1.nano","t1.micro","t1.small","t1.medium","t1.large","t1.xlarge","t1.2xlarge","t1.4xlarge","c1.nano","c1.micro","c1.small","c1.medium","c1.large","c1.xlarge","c1.2xlarge","c1.4xlarge","s1.nano","s1.micro","s1.small","s1.medium","s1.large","s1.xlarge","s1.2xlarge","s1.4xlarge","u1.nano","u1.micro","u1.small","u1.medium","u1.large","u1.xlarge","u1.2xlarge","u1.4xlarge","m1.nano","m1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m1.2xlarge","m1.4xlarge","nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"","x-kubernetes-validations":[{"rule":"self == oldSelf","message":"storageClass is immutable"}],"x-cozystack-options":{"source":"storageclass"}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"MongoDB major version to deploy.","type":"string","default":"v8","enum":["v8","v7","v6"]},"sharding":{"description":"Enable sharded cluster mode. When disabled, deploys a replica set.","type":"boolean","default":false},"shardingConfig":{"description":"Configuration for sharded cluster mode.","type":"object","default":{},"required":["configServerSize","configServers","mongos"],"properties":{"configServerSize":{"description":"PVC size for config servers.","default":"3Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"configServers":{"description":"Number of config server replicas.","type":"integer","default":3},"mongos":{"description":"Number of mongos router replicas.","type":"integer","default":2},"shards":{"description":"List of shard configurations.","type":"array","default":[{"name":"rs0","replicas":3,"size":"10Gi"}],"items":{"type":"object","required":["name","replicas","size"],"properties":{"name":{"description":"Shard name.","type":"string"},"replicas":{"description":"Number of replicas in this shard.","type":"integer"},"size":{"description":"PVC size for this shard.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"}}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges (readWrite + dbAdmin).","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/). Ignored when useSystemBucket is true (the driver injects the platform bucket).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads. Ignored when useSystemBucket is true (the strategy supplies the platform Line truncated
release:
prefix: mongodb-
labels: