diff --git a/api/apps/v1alpha1/mongodb/types.go b/api/apps/v1alpha1/mongodb/types.go index 7c0f6f33f..89ebef1f7 100644 --- a/api/apps/v1alpha1/mongodb/types.go +++ b/api/apps/v1alpha1/mongodb/types.go @@ -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 `/`. 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. diff --git a/internal/backupcontroller/psmdbstrategy_controller.go b/internal/backupcontroller/psmdbstrategy_controller.go index 5cb894049..cd17e61f0 100644 --- a/internal/backupcontroller/psmdbstrategy_controller.go +++ b/internal/backupcontroller/psmdbstrategy_controller.go @@ -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", diff --git a/internal/backupcontroller/psmdbstrategy_controller_test.go b/internal/backupcontroller/psmdbstrategy_controller_test.go index c53afea3b..203177650 100644 --- a/internal/backupcontroller/psmdbstrategy_controller_test.go +++ b/internal/backupcontroller/psmdbstrategy_controller_test.go @@ -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") + } + }) } diff --git a/packages/apps/mongodb/README.md b/packages/apps/mongodb/README.md index af033c2b8..e779a7353 100644 --- a/packages/apps/mongodb/README.md +++ b/packages/apps/mongodb/README.md @@ -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 `/`. 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 diff --git a/packages/apps/mongodb/values.schema.json b/packages/apps/mongodb/values.schema.json index a6fdd3f46..5680ea65c 100644 --- a/packages/apps/mongodb/values.schema.json +++ b/packages/apps/mongodb/values.schema.json @@ -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 * * *" }, diff --git a/packages/apps/mongodb/values.yaml b/packages/apps/mongodb/values.yaml index 660332029..10dd250e3 100644 --- a/packages/apps/mongodb/values.yaml +++ b/packages/apps/mongodb/values.yaml @@ -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 `/`. 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: diff --git a/packages/system/backupstrategy-controller/tests/rendering_test.yaml b/packages/system/backupstrategy-controller/tests/rendering_test.yaml index 695f82792..5bb5cc1d7 100644 --- a/packages/system/backupstrategy-controller/tests/rendering_test.yaml +++ b/packages/system/backupstrategy-controller/tests/rendering_test.yaml @@ -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 diff --git a/packages/system/mongodb-rd/cozyrds/mongodb.yaml b/packages/system/mongodb-rd/cozyrds/mongodb.yaml index 1e8b69f48..0b0ee8863 100644 --- a/packages/system/mongodb-rd/cozyrds/mongodb.yaml +++ b/packages/system/mongodb-rd/cozyrds/mongodb.yaml @@ -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-signed, e.g. in-cluster seaweedfs).","type":"boolean","default":false},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"},"useSystemBucket":{"description":"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 `/`. 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.","type":"boolean","default":false}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["backupName","enabled"],"properties":{"backupName":{"description":"Name of backup to restore from.","type":"string","default":""},"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"recoveryTime":{"description":"Timestamp for point-in-time recovery; empty means latest.","type":"string","default":""}}}}} + {"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 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). Ignored when useSystemBucket is true (the platform backupStorage knob applies instead).","type":"boolean","default":false},"retentionPolicy":{"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. Ignored when useSystemBucket is true (credentials come from the projected cozy-backups-creds).","type":"string","default":""},"s3SecretKey":{"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. Ignored when useSystemBucket is true (scheduling moves to the BackupClass Plan).","type":"string","default":"0 2 * * *"},"useSystemBucket":{"description":"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 `/`. 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.","type":"boolean","default":false}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["backupName","enabled"],"properties":{"backupName":{"description":"Name of backup to restore from.","type":"string","default":""},"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"recoveryTime":{"description":"Timestamp for point-in-time recovery; empty means latest.","type":"string","default":""}}}}} release: prefix: mongodb- labels: