diff --git a/docs/docs/reference/supported-resources.md b/docs/docs/reference/supported-resources.md index 7a0b1ea2..173ef54c 100644 --- a/docs/docs/reference/supported-resources.md +++ b/docs/docs/reference/supported-resources.md @@ -1175,7 +1175,7 @@ resources: admins: [ "vitaly", "dmitriy" ] # Admin user emails developers: [ ] # Developer user emails - # Backup configuration + # Simple backup configuration (backwards compatible) backup: every: 4h # Backup frequency retention: 24h # Retention period @@ -1195,11 +1195,48 @@ resources: cloudProvider: AWS admins: [ "vitaly", "dmitriy" ] developers: [ ] + + # Advanced multi-tier backup configuration backup: - every: 1h # More frequent backups - retention: 168h # Longer retention (1 week) + advanced: + # Hourly snapshots: Every 1 hour (retained 2 days) + hourly: + every: 1 # Every 1 hour + retainFor: 2 # Retain for 2 days (unit: days - default) + + # Daily snapshots: Every day (retained 35 days) + daily: + every: 1 # Every 1 day + retainFor: 35 # Retain for 35 days (unit: days - default) + + # Weekly snapshots: Every week (retained 8 weeks) + weekly: + every: 1 # Every 1 week + retainFor: 8 # Retain for 8 weeks (unit: weeks - default) + + # Monthly snapshots: 1st of month (retained 6 months) + monthly: + every: 1 # Every 1 month + retainFor: 6 # Retain for 6 months (unit: months - default) + + # Point-in-Time Recovery: Continuous oplog streaming + pointInTimeRecovery: + enabled: true # Enable PITR + oplogSizeGB: 2.0 # 2GB oplog size + oplogMinRetentionHours: 24 # Minimum 24 hours retention + ``` +**Backup Security:** + +MongoDB Atlas encrypts **all backups automatically by default** using your cloud provider's encryption: +- **Automatic encryption**: No configuration required - all snapshots are encrypted at rest +- **Cloud provider managed**: Encryption keys managed by AWS/GCP/Azure +- **Universal**: Applies to all cluster tiers and backup types (hourly, daily, weekly, monthly) +- **Secure by design**: Ensures backup data is always protected on disk + +For enterprise customers requiring additional control, MongoDB Atlas also supports Customer Key Management (CKM) using your own KMS keys, but this requires separate configuration outside of Simple Container. + **Client Access:** When this resource is used in a client stack via the `uses` section, Simple Container automatically injects environment variables and template placeholders for MongoDB Atlas database connection. diff --git a/docs/schemas/aws/postgresconfig.json b/docs/schemas/aws/postgresconfig.json index 5060159f..0ec5bbbc 100644 --- a/docs/schemas/aws/postgresconfig.json +++ b/docs/schemas/aws/postgresconfig.json @@ -55,6 +55,9 @@ "engineVersion": { "type": "string" }, + "initSQL": { + "type": "string" + }, "instanceClass": { "type": "string" }, diff --git a/docs/schemas/gcp/redisconfig.json b/docs/schemas/gcp/redisconfig.json index ba84aeda..04361a89 100644 --- a/docs/schemas/gcp/redisconfig.json +++ b/docs/schemas/gcp/redisconfig.json @@ -34,6 +34,9 @@ "adopt": { "type": "boolean" }, + "authorizedNetwork": { + "type": "string" + }, "instanceId": { "type": "string" }, diff --git a/docs/schemas/mongodb/atlasconfig.json b/docs/schemas/mongodb/atlasconfig.json index e2d94473..ea1cc3f7 100644 --- a/docs/schemas/mongodb/atlasconfig.json +++ b/docs/schemas/mongodb/atlasconfig.json @@ -21,6 +21,150 @@ "backup": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { + "advanced": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "daily": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "dayOfMonth": { + "type": "integer" + }, + "dayOfWeek": { + "type": "integer" + }, + "every": { + "type": "integer" + }, + "retainFor": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "every", + "retainFor" + ], + "type": "object" + }, + "export": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "exportBucketId": { + "type": "string" + }, + "exportBucketUrl": { + "type": "string" + }, + "frequencyType": { + "type": "string" + } + }, + "required": [ + "exportBucketId", + "frequencyType" + ], + "type": "object" + }, + "hourly": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "dayOfMonth": { + "type": "integer" + }, + "dayOfWeek": { + "type": "integer" + }, + "every": { + "type": "integer" + }, + "retainFor": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "every", + "retainFor" + ], + "type": "object" + }, + "monthly": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "dayOfMonth": { + "type": "integer" + }, + "dayOfWeek": { + "type": "integer" + }, + "every": { + "type": "integer" + }, + "retainFor": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "every", + "retainFor" + ], + "type": "object" + }, + "pointInTimeRecovery": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "enabled": { + "type": "boolean" + }, + "oplogMinRetentionHours": { + "type": "integer" + }, + "oplogSizeGB": { + "type": "number" + } + }, + "required": [ + "enabled" + ], + "type": "object" + }, + "weekly": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "dayOfMonth": { + "type": "integer" + }, + "dayOfWeek": { + "type": "integer" + }, + "every": { + "type": "integer" + }, + "retainFor": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "every", + "retainFor" + ], + "type": "object" + } + }, + "required": [], + "type": "object" + }, "every": { "type": "string" }, @@ -28,10 +172,7 @@ "type": "string" } }, - "required": [ - "every", - "retention" - ], + "required": [], "type": "object" }, "cloudProvider": { @@ -40,6 +181,9 @@ "clusterName": { "type": "string" }, + "deletionProtection": { + "type": "boolean" + }, "developers": { "items": { "type": "string" @@ -81,6 +225,9 @@ "instanceSize": { "type": "string" }, + "namingStrategyVersion": { + "type": "integer" + }, "networkConfig": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { diff --git a/pkg/clouds/mongodb/mongodb.go b/pkg/clouds/mongodb/mongodb.go index bda1da47..3a640fd2 100644 --- a/pkg/clouds/mongodb/mongodb.go +++ b/pkg/clouds/mongodb/mongodb.go @@ -40,8 +40,62 @@ type PrivateLinkEndpoint struct { } type AtlasBackup struct { - Every string `json:"every" yaml:"every"` // e.g. 2h - Retention string `json:"retention" yaml:"retention"` // e.g. 24h + // Basic configuration (backwards compatible) + Every string `json:"every,omitempty" yaml:"every,omitempty"` // e.g. 2h + Retention string `json:"retention,omitempty" yaml:"retention,omitempty"` // e.g. 24h + + // Advanced multi-tier backup configuration + Advanced *AtlasAdvancedBackup `json:"advanced,omitempty" yaml:"advanced,omitempty"` +} + +// AtlasAdvancedBackup provides sophisticated backup scheduling with multiple retention tiers +type AtlasAdvancedBackup struct { + // Individual schedule policies (can be combined) + Hourly *AtlasBackupPolicy `json:"hourly,omitempty" yaml:"hourly,omitempty"` + Daily *AtlasBackupPolicy `json:"daily,omitempty" yaml:"daily,omitempty"` + Weekly *AtlasBackupPolicy `json:"weekly,omitempty" yaml:"weekly,omitempty"` + Monthly *AtlasBackupPolicy `json:"monthly,omitempty" yaml:"monthly,omitempty"` + + // Point-in-Time Recovery configuration + PointInTimeRecovery *AtlasPointInTimeRecovery `json:"pointInTimeRecovery,omitempty" yaml:"pointInTimeRecovery,omitempty"` + + // Export configuration for cross-region/project backups + Export *AtlasBackupExport `json:"export,omitempty" yaml:"export,omitempty"` +} + +// AtlasBackupPolicy defines a backup schedule with retention +type AtlasBackupPolicy struct { + // Schedule frequency + Every int `json:"every" yaml:"every"` // e.g. 1 for every 1 hour/day/week/month + + // Retention duration (unit inferred from backup type context) + RetainFor int `json:"retainFor" yaml:"retainFor"` // e.g. 2 for retain for 2 days/weeks/months + Unit string `json:"unit,omitempty" yaml:"unit,omitempty"` // "days", "weeks", "months" (optional, defaults based on backup type) + + // Weekly-specific configuration (not supported in current provider) + DayOfWeek *int `json:"dayOfWeek,omitempty" yaml:"dayOfWeek,omitempty"` // 1=Sunday, 7=Saturday + + // Monthly-specific configuration (not supported in current provider) + DayOfMonth *int `json:"dayOfMonth,omitempty" yaml:"dayOfMonth,omitempty"` // 1-31 +} + +// AtlasPointInTimeRecovery configures continuous oplog streaming +type AtlasPointInTimeRecovery struct { + Enabled bool `json:"enabled" yaml:"enabled"` + + // Oplog retention window + OplogSizeGB *float64 `json:"oplogSizeGB,omitempty" yaml:"oplogSizeGB,omitempty"` // GB of oplog to retain + OplogMinRetentionHours *int `json:"oplogMinRetentionHours,omitempty" yaml:"oplogMinRetentionHours,omitempty"` // Minimum oplog retention in hours +} + +// AtlasBackupExport configures cross-region or cross-project backup exports +type AtlasBackupExport struct { + // Export frequency (typically less frequent than main backups) + FrequencyType string `json:"frequencyType" yaml:"frequencyType"` // "daily", "weekly", "monthly" + + // Export destinations + ExportBucketId string `json:"exportBucketId" yaml:"exportBucketId"` // Atlas-managed cloud storage bucket + ExportBucketUrl *string `json:"exportBucketUrl,omitempty" yaml:"exportBucketUrl,omitempty"` // Custom S3/GCS bucket URL } func ReadAtlasConfig(config *api.Config) (api.Config, error) { diff --git a/pkg/clouds/pulumi/mongodb/cluster.go b/pkg/clouds/pulumi/mongodb/cluster.go index 004d3bf4..e07e92a8 100644 --- a/pkg/clouds/pulumi/mongodb/cluster.go +++ b/pkg/clouds/pulumi/mongodb/cluster.go @@ -122,41 +122,31 @@ func Cluster(ctx *sdk.Context, stack api.Stack, input api.ResourceInput, params out.Cluster = cluster if atlasCfg.Backup != nil { - // Configure the backup schedule + // Configure backup schedule - support both basic and advanced configurations backupArgs := &mongodbatlas.CloudBackupScheduleArgs{ ProjectId: projectId, ClusterName: cluster.Name, UpdateSnapshots: sdk.Bool(true), } - every, err := time.ParseDuration(atlasCfg.Backup.Every) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse schedule %q", atlasCfg.Backup.Every) + + // Note: Point-in-Time Recovery is handled at the cluster level via PitEnabled field + // PITR configuration should be set when creating the cluster itself, not in backup schedule + + var err error + if atlasCfg.Backup.Advanced != nil { + // Use advanced backup configuration + err = configureAdvancedBackup(backupArgs, atlasCfg.Backup.Advanced) + } else if atlasCfg.Backup.Every != "" && atlasCfg.Backup.Retention != "" { + // Use legacy basic configuration for backwards compatibility + err = configureBasicBackup(backupArgs, atlasCfg.Backup.Every, atlasCfg.Backup.Retention) + } else { + return nil, errors.New("backup configuration must specify either 'advanced' schedules or basic 'every'/'retention' values") } - retention, err := time.ParseDuration(atlasCfg.Backup.Retention) + if err != nil { - return nil, errors.Wrapf(err, "failed to parse retention %q", atlasCfg.Backup.Retention) - } - if every.Hours() < 24 { - backupArgs.PolicyItemHourly = &mongodbatlas.CloudBackupSchedulePolicyItemHourlyArgs{ - FrequencyInterval: sdk.Int(every.Hours()), - RetentionUnit: sdk.String("days"), - RetentionValue: sdk.Int(retention.Hours() / 24), - } - } else if every.Hours()/24/7 > 0 { - backupArgs.PolicyItemWeeklies = mongodbatlas.CloudBackupSchedulePolicyItemWeeklyArray{ - &mongodbatlas.CloudBackupSchedulePolicyItemWeeklyArgs{ - FrequencyInterval: sdk.Int(every.Hours() / 24 / 7), - RetentionUnit: sdk.String("days"), - RetentionValue: sdk.Int(retention.Hours() / 24), - }, - } - } else if every.Hours() > 24 { - backupArgs.PolicyItemDaily = &mongodbatlas.CloudBackupSchedulePolicyItemDailyArgs{ - FrequencyInterval: sdk.Int(every.Hours() / 24), - RetentionUnit: sdk.String("days"), - RetentionValue: sdk.Int(retention.Hours() / 24), - } + return nil, errors.Wrapf(err, "failed to configure backup schedules") } + _, err = mongodbatlas.NewCloudBackupSchedule(ctx, fmt.Sprintf("%s-backups-schedule", clusterName), backupArgs, opts...) if err != nil { return nil, errors.Wrapf(err, "failed to create mongodb backups schedule for stack %q", stack.Name) @@ -637,3 +627,115 @@ func createDatabaseUsers(ctx *sdk.Context, cluster *mongodbatlas.Cluster, cfg *m })), nil }) } + +// configureBasicBackup handles the legacy simple backup configuration for backwards compatibility +func configureBasicBackup(backupArgs *mongodbatlas.CloudBackupScheduleArgs, every, retention string) error { + everyDuration, err := time.ParseDuration(every) + if err != nil { + return errors.Wrapf(err, "failed to parse schedule %q", every) + } + retentionDuration, err := time.ParseDuration(retention) + if err != nil { + return errors.Wrapf(err, "failed to parse retention %q", retention) + } + + // Convert to the appropriate backup policy based on frequency + if everyDuration.Hours() < 24 { + // Hourly backups + backupArgs.PolicyItemHourly = &mongodbatlas.CloudBackupSchedulePolicyItemHourlyArgs{ + FrequencyInterval: sdk.Int(int(everyDuration.Hours())), + RetentionUnit: sdk.String("days"), + RetentionValue: sdk.Int(int(retentionDuration.Hours() / 24)), + } + } else if everyDuration.Hours() >= 24 && everyDuration.Hours() < 24*7 { + // Daily backups + backupArgs.PolicyItemDaily = &mongodbatlas.CloudBackupSchedulePolicyItemDailyArgs{ + FrequencyInterval: sdk.Int(int(everyDuration.Hours() / 24)), + RetentionUnit: sdk.String("days"), + RetentionValue: sdk.Int(int(retentionDuration.Hours() / 24)), + } + } else { + // Weekly backups + backupArgs.PolicyItemWeeklies = mongodbatlas.CloudBackupSchedulePolicyItemWeeklyArray{ + &mongodbatlas.CloudBackupSchedulePolicyItemWeeklyArgs{ + FrequencyInterval: sdk.Int(int(everyDuration.Hours() / 24 / 7)), + RetentionUnit: sdk.String("days"), + RetentionValue: sdk.Int(int(retentionDuration.Hours() / 24)), + }, + } + } + + return nil +} + +// configureAdvancedBackup handles the new multi-tier backup configuration +func configureAdvancedBackup(backupArgs *mongodbatlas.CloudBackupScheduleArgs, advanced *mongodb.AtlasAdvancedBackup) error { + // Configure hourly backups + if advanced.Hourly != nil { + policy := advanced.Hourly + unit := policy.Unit + if unit == "" { + unit = "days" // Default unit for hourly backups + } + backupArgs.PolicyItemHourly = &mongodbatlas.CloudBackupSchedulePolicyItemHourlyArgs{ + FrequencyInterval: sdk.Int(policy.Every), + RetentionUnit: sdk.String(unit), + RetentionValue: sdk.Int(policy.RetainFor), + } + } + + // Configure daily backups + if advanced.Daily != nil { + policy := advanced.Daily + unit := policy.Unit + if unit == "" { + unit = "days" // Default unit for daily backups + } + backupArgs.PolicyItemDaily = &mongodbatlas.CloudBackupSchedulePolicyItemDailyArgs{ + FrequencyInterval: sdk.Int(policy.Every), + RetentionUnit: sdk.String(unit), + RetentionValue: sdk.Int(policy.RetainFor), + } + } + + // Configure weekly backups + if advanced.Weekly != nil { + policy := advanced.Weekly + unit := policy.Unit + if unit == "" { + unit = "weeks" // Default unit for weekly backups + } + weeklyArgs := &mongodbatlas.CloudBackupSchedulePolicyItemWeeklyArgs{ + FrequencyInterval: sdk.Int(policy.Every), + RetentionUnit: sdk.String(unit), + RetentionValue: sdk.Int(policy.RetainFor), + } + + // Note: DayOfWeek configuration not supported in current MongoDB Atlas provider version + + backupArgs.PolicyItemWeeklies = mongodbatlas.CloudBackupSchedulePolicyItemWeeklyArray{weeklyArgs} + } + + // Configure monthly backups + if advanced.Monthly != nil { + policy := advanced.Monthly + unit := policy.Unit + if unit == "" { + unit = "months" // Default unit for monthly backups + } + monthlyArgs := &mongodbatlas.CloudBackupSchedulePolicyItemMonthlyArgs{ + FrequencyInterval: sdk.Int(policy.Every), + RetentionUnit: sdk.String(unit), + RetentionValue: sdk.Int(policy.RetainFor), + } + + // Note: DayOfMonth configuration not supported in current MongoDB Atlas provider version + + backupArgs.PolicyItemMonthlies = mongodbatlas.CloudBackupSchedulePolicyItemMonthlyArray{monthlyArgs} + } + + // Note: Backup export configuration not supported in current MongoDB Atlas provider version + // Export functionality would need to be configured separately through MongoDB Atlas UI/API + + return nil +}