From 2810545ef7f4629fbeaf26a8ac5e3fcd6d06960c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Wed, 31 Jul 2024 10:55:14 +0100 Subject: [PATCH] Add additional backup configurations to MongoDB Flex instance resource (#486) * Extend resource and datasource * Extend acc test * Extend example * Generate docs * Fix linter * Update stackit/internal/services/mongodbflex/instance/datasource.go Co-authored-by: GokceGK <161626272+GokceGK@users.noreply.github.com> --------- Co-authored-by: GokceGK <161626272+GokceGK@users.noreply.github.com> --- docs/data-sources/mongodbflex_instance.md | 7 +- docs/resources/mongodbflex_instance.md | 15 +- .../stackit_mongodbflex_instance/resource.tf | 5 +- .../mongodbflex/instance/datasource.go | 45 +++- .../services/mongodbflex/instance/resource.go | 239 ++++++++++++++++- .../mongodbflex/instance/resource_test.go | 245 +++++++++++++++++- .../mongodbflex/mongodbflex_acc_test.go | 56 ++-- 7 files changed, 556 insertions(+), 56 deletions(-) diff --git a/docs/data-sources/mongodbflex_instance.md b/docs/data-sources/mongodbflex_instance.md index 7a5419cb..a9f4a74a 100644 --- a/docs/data-sources/mongodbflex_instance.md +++ b/docs/data-sources/mongodbflex_instance.md @@ -55,7 +55,12 @@ Read-Only: Read-Only: -- `type` (String) +- `daily_snapshot_retention_days` (Number) The number of days that daily backups will be retained. +- `monthly_snapshot_retention_months` (Number) The number of months that monthly backups will be retained. +- `point_in_time_window_hours` (Number) The number of hours back in time the point-in-time recovery feature will be able to recover. +- `snapshot_retention_days` (Number) The number of days that continuous backups (controlled via the `backup_schedule`) will be retained. +- `type` (String) Type of the MongoDB Flex instance. +- `weekly_snapshot_retention_weeks` (Number) The number of weeks that weekly backups will be retained. diff --git a/docs/resources/mongodbflex_instance.md b/docs/resources/mongodbflex_instance.md index 0c7438ae..971e7957 100644 --- a/docs/resources/mongodbflex_instance.md +++ b/docs/resources/mongodbflex_instance.md @@ -26,9 +26,10 @@ resource "stackit_mongodbflex_instance" "example" { class = "class" size = 10 } - version = "5.0" + version = "7.0" options = { - type = "Single" + type = "Single" + snapshot_retention_days = 3 } backup_schedule = "0 0 * * *" } @@ -73,7 +74,15 @@ Read-Only: Required: -- `type` (String) +- `type` (String) Type of the MongoDB Flex instance. Supported values are: `Replica`, `Sharded`, `Single`. + +Optional: + +- `daily_snapshot_retention_days` (Number) The number of days that daily backups will be retained. +- `monthly_snapshot_retention_months` (Number) The number of months that monthly backups will be retained. +- `point_in_time_window_hours` (Number) The number of hours back in time the point-in-time recovery feature will be able to recover. +- `snapshot_retention_days` (Number) The number of days that continuous backups (controlled via the `backup_schedule`) will be retained. +- `weekly_snapshot_retention_weeks` (Number) The number of weeks that weekly backups will be retained. diff --git a/examples/resources/stackit_mongodbflex_instance/resource.tf b/examples/resources/stackit_mongodbflex_instance/resource.tf index ef39003e..eaf16e62 100644 --- a/examples/resources/stackit_mongodbflex_instance/resource.tf +++ b/examples/resources/stackit_mongodbflex_instance/resource.tf @@ -11,9 +11,10 @@ resource "stackit_mongodbflex_instance" "example" { class = "class" size = 10 } - version = "5.0" + version = "7.0" options = { - type = "Single" + type = "Single" + snapshot_retention_days = 3 } backup_schedule = "0 0 * * *" } diff --git a/stackit/internal/services/mongodbflex/instance/datasource.go b/stackit/internal/services/mongodbflex/instance/datasource.go index 15ab6072..e16f56b8 100644 --- a/stackit/internal/services/mongodbflex/instance/datasource.go +++ b/stackit/internal/services/mongodbflex/instance/datasource.go @@ -78,14 +78,20 @@ func (r *instanceDataSource) Configure(ctx context.Context, req datasource.Confi // Schema defines the schema for the data source. func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ - "main": "MongoDB Flex instance data source schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal data source. ID. It is structured as \"`project_id`,`instance_id`\".", - "instance_id": "ID of the MongoDB Flex instance.", - "project_id": "STACKIT project ID to which the instance is associated.", - "name": "Instance name.", - "acl": "The Access Control List (ACL) for the MongoDB Flex instance.", - "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").`, - "options": "Custom parameters for the MongoDB Flex instance.", + "main": "MongoDB Flex instance data source schema. Must have a `region` specified in the provider configuration.", + "id": "Terraform's internal data source ID. It is structured as \"`project_id`,`instance_id`\".", + "instance_id": "ID of the MongoDB Flex instance.", + "project_id": "STACKIT project ID to which the instance is associated.", + "name": "Instance name.", + "acl": "The Access Control List (ACL) for the MongoDB Flex instance.", + "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").`, + "options": "Custom parameters for the MongoDB Flex instance.", + "type": "Type of the MongoDB Flex instance.", + "snapshot_retention_days": "The number of days that continuous backups (controlled via the `backup_schedule`) will be retained.", + "daily_snapshot_retention_days": "The number of days that daily backups will be retained.", + "weekly_snapshot_retention_weeks": "The number of weeks that weekly backups will be retained.", + "monthly_snapshot_retention_months": "The number of months that monthly backups will be retained.", + "point_in_time_window_hours": "The number of hours back in time the point-in-time recovery feature will be able to recover.", } resp.Schema = schema.Schema{ @@ -163,7 +169,28 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Computed: true, Attributes: map[string]schema.Attribute{ "type": schema.StringAttribute{ - Computed: true, + Description: descriptions["type"], + Computed: true, + }, + "snapshot_retention_days": schema.Int64Attribute{ + Description: descriptions["snapshot_retention_days"], + Computed: true, + }, + "daily_snapshot_retention_days": schema.Int64Attribute{ + Description: descriptions["daily_snapshot_retention_days"], + Computed: true, + }, + "weekly_snapshot_retention_weeks": schema.Int64Attribute{ + Description: descriptions["weekly_snapshot_retention_weeks"], + Computed: true, + }, + "monthly_snapshot_retention_months": schema.Int64Attribute{ + Description: descriptions["monthly_snapshot_retention_months"], + Computed: true, + }, + "point_in_time_window_hours": schema.Int64Attribute{ + Description: descriptions["point_in_time_window_hours"], + Computed: true, }, }, }, diff --git a/stackit/internal/services/mongodbflex/instance/resource.go b/stackit/internal/services/mongodbflex/instance/resource.go index bf93c15e..dd3e6bf3 100644 --- a/stackit/internal/services/mongodbflex/instance/resource.go +++ b/stackit/internal/services/mongodbflex/instance/resource.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "regexp" + "strconv" "strings" "time" @@ -22,6 +23,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" @@ -86,12 +88,22 @@ var storageTypes = map[string]attr.Type{ // Struct corresponding to Model.Options type optionsModel struct { - Type types.String `tfsdk:"type"` + Type types.String `tfsdk:"type"` + SnapshotRetentionDays types.Int64 `tfsdk:"snapshot_retention_days"` + PointInTimeWindowHours types.Int64 `tfsdk:"point_in_time_window_hours"` + DailySnapshotRetentionDays types.Int64 `tfsdk:"daily_snapshot_retention_days"` + WeeklySnapshotRetentionWeeks types.Int64 `tfsdk:"weekly_snapshot_retention_weeks"` + MonthlySnapshotRetentionMonths types.Int64 `tfsdk:"monthly_snapshot_retention_months"` } // Types corresponding to optionsModel var optionsTypes = map[string]attr.Type{ - "type": basetypes.StringType{}, + "type": basetypes.StringType{}, + "snapshot_retention_days": basetypes.Int64Type{}, + "point_in_time_window_hours": basetypes.Int64Type{}, + "daily_snapshot_retention_days": basetypes.Int64Type{}, + "weekly_snapshot_retention_weeks": basetypes.Int64Type{}, + "monthly_snapshot_retention_months": basetypes.Int64Type{}, } // NewInstanceResource is a helper function to simplify the provider implementation. @@ -147,15 +159,23 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure // Schema defines the schema for the resource. func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + typeOptions := []string{"Replica", "Sharded", "Single"} + descriptions := map[string]string{ - "main": "MongoDB Flex instance resource schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", - "instance_id": "ID of the MongoDB Flex instance.", - "project_id": "STACKIT project ID to which the instance is associated.", - "name": "Instance name.", - "acl": "The Access Control List (ACL) for the MongoDB Flex instance.", - "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").`, - "options": "Custom parameters for the MongoDB Flex instance.", + "main": "MongoDB Flex instance resource schema. Must have a `region` specified in the provider configuration.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "instance_id": "ID of the MongoDB Flex instance.", + "project_id": "STACKIT project ID to which the instance is associated.", + "name": "Instance name.", + "acl": "The Access Control List (ACL) for the MongoDB Flex instance.", + "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").`, + "options": "Custom parameters for the MongoDB Flex instance.", + "type": fmt.Sprintf("Type of the MongoDB Flex instance. %s", utils.SupportedValuesDocumentation(typeOptions)), + "snapshot_retention_days": "The number of days that continuous backups (controlled via the `backup_schedule`) will be retained.", + "daily_snapshot_retention_days": "The number of days that daily backups will be retained.", + "weekly_snapshot_retention_weeks": "The number of weeks that weekly backups will be retained.", + "monthly_snapshot_retention_months": "The number of months that monthly backups will be retained.", + "point_in_time_window_hours": "The number of hours back in time the point-in-time recovery feature will be able to recover.", } resp.Schema = schema.Schema{ @@ -252,11 +272,52 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r Required: true, Attributes: map[string]schema.Attribute{ "type": schema.StringAttribute{ - Required: true, + Description: descriptions["type"], + Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, + "snapshot_retention_days": schema.Int64Attribute{ + Description: descriptions["snapshot_retention_days"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "daily_snapshot_retention_days": schema.Int64Attribute{ + Description: descriptions["daily_snapshot_retention_days"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "weekly_snapshot_retention_weeks": schema.Int64Attribute{ + Description: descriptions["weekly_snapshot_retention_weeks"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "monthly_snapshot_retention_months": schema.Int64Attribute{ + Description: descriptions["monthly_snapshot_retention_months"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "point_in_time_window_hours": schema.Int64Attribute{ + Description: descriptions["point_in_time_window_hours"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, }, }, }, @@ -340,6 +401,24 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err)) return } + + backupScheduleOptionsPayload, err := toUpdateBackupScheduleOptionsPayload(ctx, &model, options) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + backupScheduleOptions, err := r.client.UpdateBackupSchedule(ctx, projectId, instanceId).UpdateBackupSchedulePayload(*backupScheduleOptionsPayload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Updating options: %v", err)) + return + } + + err = mapOptions(&model, options, backupScheduleOptions) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API response: %v", err)) + return + } + // Set state to fully populated data diags = resp.State.Set(ctx, model) resp.Diagnostics.Append(diags...) @@ -491,6 +570,24 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err)) return } + + backupScheduleOptionsPayload, err := toUpdateBackupScheduleOptionsPayload(ctx, &model, options) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + backupScheduleOptions, err := r.client.UpdateBackupSchedule(ctx, projectId, instanceId).UpdateBackupSchedulePayload(*backupScheduleOptionsPayload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Updating options: %v", err)) + return + } + + err = mapOptions(&model, options, backupScheduleOptions) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API response: %v", err)) + return + } + diags = resp.State.Set(ctx, model) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -631,11 +728,47 @@ func mapFields(ctx context.Context, resp *mongodbflex.GetInstanceResponse, model var optionsValues map[string]attr.Value if instance.Options == nil { optionsValues = map[string]attr.Value{ - "type": options.Type, + "type": options.Type, + "snapshot_retention_days": types.Int64Null(), + "daily_snapshot_retention_days": types.Int64Null(), + "weekly_snapshot_retention_weeks": types.Int64Null(), + "monthly_snapshot_retention_months": types.Int64Null(), + "point_in_time_window_hours": types.Int64Null(), } } else { + snapshotRetentionDaysStr := (*instance.Options)["snapshotRetentionDays"] + snapshotRetentionDays, err := strconv.ParseInt(snapshotRetentionDaysStr, 10, 64) + if err != nil { + return fmt.Errorf("parse snapshot retention days: %w", err) + } + dailySnapshotRetentionDaysStr := (*instance.Options)["dailySnapshotRetentionDays"] + dailySnapshotRetentionDays, err := strconv.ParseInt(dailySnapshotRetentionDaysStr, 10, 64) + if err != nil { + return fmt.Errorf("parse daily snapshot retention days: %w", err) + } + weeklySnapshotRetentionWeeksStr := (*instance.Options)["weeklySnapshotRetentionWeeks"] + weeklySnapshotRetentionWeeks, err := strconv.ParseInt(weeklySnapshotRetentionWeeksStr, 10, 64) + if err != nil { + return fmt.Errorf("parse weekly snapshot retention weeks: %w", err) + } + monthlySnapshotRetentionMonthsStr := (*instance.Options)["monthlySnapshotRetentionMonths"] + monthlySnapshotRetentionMonths, err := strconv.ParseInt(monthlySnapshotRetentionMonthsStr, 10, 64) + if err != nil { + return fmt.Errorf("parse monthly snapshot retention months: %w", err) + } + pointInTimeWindowHoursStr := (*instance.Options)["pointInTimeWindowHours"] + pointInTimeWindowHours, err := strconv.ParseInt(pointInTimeWindowHoursStr, 10, 64) + if err != nil { + return fmt.Errorf("parse point in time window hours: %w", err) + } + optionsValues = map[string]attr.Value{ - "type": types.StringValue((*instance.Options)["type"]), + "type": types.StringValue((*instance.Options)["type"]), + "snapshot_retention_days": types.Int64Value(snapshotRetentionDays), + "daily_snapshot_retention_days": types.Int64Value(dailySnapshotRetentionDays), + "weekly_snapshot_retention_weeks": types.Int64Value(weeklySnapshotRetentionWeeks), + "monthly_snapshot_retention_months": types.Int64Value(monthlySnapshotRetentionMonths), + "point_in_time_window_hours": types.Int64Value(pointInTimeWindowHours), } } optionsObject, diags := types.ObjectValue(optionsTypes, optionsValues) @@ -668,6 +801,35 @@ func mapFields(ctx context.Context, resp *mongodbflex.GetInstanceResponse, model return nil } +func mapOptions(model *Model, options *optionsModel, backupScheduleOptions *mongodbflex.BackupSchedule) error { + var optionsValues map[string]attr.Value + if backupScheduleOptions == nil { + optionsValues = map[string]attr.Value{ + "type": options.Type, + "snapshot_retention_days": types.Int64Null(), + "daily_snapshot_retention_days": types.Int64Null(), + "weekly_snapshot_retention_weeks": types.Int64Null(), + "monthly_snapshot_retention_months": types.Int64Null(), + "point_in_time_window_hours": types.Int64Null(), + } + } else { + optionsValues = map[string]attr.Value{ + "type": options.Type, + "snapshot_retention_days": types.Int64Value(*backupScheduleOptions.SnapshotRetentionDays), + "daily_snapshot_retention_days": types.Int64Value(*backupScheduleOptions.DailySnapshotRetentionDays), + "weekly_snapshot_retention_weeks": types.Int64Value(*backupScheduleOptions.WeeklySnapshotRetentionWeeks), + "monthly_snapshot_retention_months": types.Int64Value(*backupScheduleOptions.MonthlySnapshotRetentionMonths), + "point_in_time_window_hours": types.Int64Value(*backupScheduleOptions.PointInTimeWindowHours), + } + } + optionsTF, diags := types.ObjectValue(optionsTypes, optionsValues) + if diags.HasError() { + return fmt.Errorf("creating options: %w", core.DiagsToError(diags)) + } + model.Options = optionsTF + return nil +} + func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *storageModel, options *optionsModel) (*mongodbflex.CreateInstancePayload, error) { if model == nil { return nil, fmt.Errorf("nil model") @@ -746,6 +908,57 @@ func toUpdatePayload(model *Model, acl []string, flavor *flavorModel, storage *s }, nil } +func toUpdateBackupScheduleOptionsPayload(ctx context.Context, model *Model, configuredOptions *optionsModel) (*mongodbflex.UpdateBackupSchedulePayload, error) { + if model == nil || configuredOptions == nil { + return nil, nil + } + + var currOptions = &optionsModel{} + if !(model.Options.IsNull() || model.Options.IsUnknown()) { + diags := model.Options.As(ctx, currOptions, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("map current options: %w", core.DiagsToError(diags)) + } + } + + backupSchedule := conversion.StringValueToPointer(model.BackupSchedule) + + snapshotRetentionDays := conversion.Int64ValueToPointer(configuredOptions.SnapshotRetentionDays) + if snapshotRetentionDays == nil { + snapshotRetentionDays = conversion.Int64ValueToPointer(currOptions.SnapshotRetentionDays) + } + + dailySnapshotRetentionDays := conversion.Int64ValueToPointer(configuredOptions.DailySnapshotRetentionDays) + if dailySnapshotRetentionDays == nil { + dailySnapshotRetentionDays = conversion.Int64ValueToPointer(currOptions.DailySnapshotRetentionDays) + } + + weeklySnapshotRetentionWeeks := conversion.Int64ValueToPointer(configuredOptions.WeeklySnapshotRetentionWeeks) + if weeklySnapshotRetentionWeeks == nil { + weeklySnapshotRetentionWeeks = conversion.Int64ValueToPointer(currOptions.WeeklySnapshotRetentionWeeks) + } + + monthlySnapshotRetentionMonths := conversion.Int64ValueToPointer(configuredOptions.MonthlySnapshotRetentionMonths) + if monthlySnapshotRetentionMonths == nil { + monthlySnapshotRetentionMonths = conversion.Int64ValueToPointer(currOptions.MonthlySnapshotRetentionMonths) + } + + pointInTimeWindowHours := conversion.Int64ValueToPointer(configuredOptions.PointInTimeWindowHours) + if pointInTimeWindowHours == nil { + pointInTimeWindowHours = conversion.Int64ValueToPointer(currOptions.PointInTimeWindowHours) + } + + return &mongodbflex.UpdateBackupSchedulePayload{ + // This is a PUT endpoint and all fields are required + BackupSchedule: backupSchedule, + SnapshotRetentionDays: snapshotRetentionDays, + DailySnapshotRetentionDays: dailySnapshotRetentionDays, + WeeklySnapshotRetentionWeeks: weeklySnapshotRetentionWeeks, + MonthlySnapshotRetentionMonths: monthlySnapshotRetentionMonths, + PointInTimeWindowHours: pointInTimeWindowHours, + }, nil +} + type mongoDBFlexClient interface { ListFlavorsExecute(ctx context.Context, projectId string) (*mongodbflex.ListFlavorsResponse, error) } diff --git a/stackit/internal/services/mongodbflex/instance/resource_test.go b/stackit/internal/services/mongodbflex/instance/resource_test.go index 69710ac0..85041047 100644 --- a/stackit/internal/services/mongodbflex/instance/resource_test.go +++ b/stackit/internal/services/mongodbflex/instance/resource_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" @@ -67,7 +68,12 @@ func TestMapFields(t *testing.T) { "size": types.Int64Null(), }), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ - "type": types.StringNull(), + "type": types.StringNull(), + "snapshot_retention_days": types.Int64Null(), + "daily_snapshot_retention_days": types.Int64Null(), + "weekly_snapshot_retention_weeks": types.Int64Null(), + "monthly_snapshot_retention_months": types.Int64Null(), + "point_in_time_window_hours": types.Int64Null(), }), Version: types.StringNull(), }, @@ -104,7 +110,12 @@ func TestMapFields(t *testing.T) { Size: utils.Ptr(int64(78)), }, Options: &map[string]string{ - "type": "type", + "type": "type", + "snapshotRetentionDays": "5", + "dailySnapshotRetentionDays": "6", + "weeklySnapshotRetentionWeeks": "7", + "monthlySnapshotRetentionMonths": "8", + "pointInTimeWindowHours": "9", }, Version: utils.Ptr("version"), }, @@ -135,7 +146,12 @@ func TestMapFields(t *testing.T) { "size": types.Int64Value(78), }), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ - "type": types.StringValue("type"), + "type": types.StringValue("type"), + "snapshot_retention_days": types.Int64Value(5), + "daily_snapshot_retention_days": types.Int64Value(6), + "weekly_snapshot_retention_weeks": types.Int64Value(7), + "monthly_snapshot_retention_months": types.Int64Value(8), + "point_in_time_window_hours": types.Int64Value(9), }), Version: types.StringValue("version"), }, @@ -164,7 +180,12 @@ func TestMapFields(t *testing.T) { Status: utils.Ptr("status"), Storage: nil, Options: &map[string]string{ - "type": "type", + "type": "type", + "snapshotRetentionDays": "5", + "dailySnapshotRetentionDays": "6", + "weeklySnapshotRetentionWeeks": "7", + "monthlySnapshotRetentionMonths": "8", + "pointInTimeWindowHours": "9", }, Version: utils.Ptr("version"), }, @@ -203,7 +224,12 @@ func TestMapFields(t *testing.T) { "size": types.Int64Value(78), }), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ - "type": types.StringValue("type"), + "type": types.StringValue("type"), + "snapshot_retention_days": types.Int64Value(5), + "daily_snapshot_retention_days": types.Int64Value(6), + "weekly_snapshot_retention_weeks": types.Int64Value(7), + "monthly_snapshot_retention_months": types.Int64Value(8), + "point_in_time_window_hours": types.Int64Value(9), }), Version: types.StringValue("version"), }, @@ -237,7 +263,12 @@ func TestMapFields(t *testing.T) { Status: utils.Ptr("status"), Storage: nil, Options: &map[string]string{ - "type": "type", + "type": "type", + "snapshotRetentionDays": "5", + "dailySnapshotRetentionDays": "6", + "weeklySnapshotRetentionWeeks": "7", + "monthlySnapshotRetentionMonths": "8", + "pointInTimeWindowHours": "9", }, Version: utils.Ptr("version"), }, @@ -276,7 +307,12 @@ func TestMapFields(t *testing.T) { "size": types.Int64Value(78), }), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ - "type": types.StringValue("type"), + "type": types.StringValue("type"), + "snapshot_retention_days": types.Int64Value(5), + "daily_snapshot_retention_days": types.Int64Value(6), + "weekly_snapshot_retention_weeks": types.Int64Value(7), + "monthly_snapshot_retention_months": types.Int64Value(8), + "point_in_time_window_hours": types.Int64Value(9), }), Version: types.StringValue("version"), }, @@ -328,6 +364,77 @@ func TestMapFields(t *testing.T) { } } +func TestMapOptions(t *testing.T) { + tests := []struct { + description string + model *Model + options *optionsModel + backup *mongodbflex.BackupSchedule + expected *Model + isValid bool + }{ + { + "default_values", + &Model{}, + &optionsModel{}, + nil, + &Model{ + Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ + "type": types.StringNull(), + "snapshot_retention_days": types.Int64Null(), + "daily_snapshot_retention_days": types.Int64Null(), + "weekly_snapshot_retention_weeks": types.Int64Null(), + "monthly_snapshot_retention_months": types.Int64Null(), + "point_in_time_window_hours": types.Int64Null(), + }), + }, + true, + }, + { + "simple_values", + &Model{}, + &optionsModel{ + Type: types.StringValue("type"), + }, + &mongodbflex.BackupSchedule{ + SnapshotRetentionDays: utils.Ptr(int64(1)), + DailySnapshotRetentionDays: utils.Ptr(int64(2)), + WeeklySnapshotRetentionWeeks: utils.Ptr(int64(3)), + MonthlySnapshotRetentionMonths: utils.Ptr(int64(4)), + PointInTimeWindowHours: utils.Ptr(int64(5)), + }, + &Model{ + Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ + "type": types.StringValue("type"), + "snapshot_retention_days": types.Int64Value(1), + "daily_snapshot_retention_days": types.Int64Value(2), + "weekly_snapshot_retention_weeks": types.Int64Value(3), + "monthly_snapshot_retention_months": types.Int64Value(4), + "point_in_time_window_hours": types.Int64Value(5), + }), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + err := mapOptions(tt.model, tt.options, tt.backup) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(tt.model, tt.expected, cmpopts.IgnoreFields(Model{}, "ACL", "Flavor", "Replicas", "Storage", "Version")) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + func TestToCreatePayload(t *testing.T) { tests := []struct { description string @@ -686,6 +793,130 @@ func TestToUpdatePayload(t *testing.T) { } } +func TestToUpdateBackupScheduleOptionsPayload(t *testing.T) { + tests := []struct { + description string + model *Model + configuredOptions *optionsModel + expected *mongodbflex.UpdateBackupSchedulePayload + isValid bool + }{ + { + "default_values", + &Model{}, + &optionsModel{}, + &mongodbflex.UpdateBackupSchedulePayload{ + BackupSchedule: nil, + SnapshotRetentionDays: nil, + DailySnapshotRetentionDays: nil, + WeeklySnapshotRetentionWeeks: nil, + MonthlySnapshotRetentionMonths: nil, + PointInTimeWindowHours: nil, + }, + true, + }, + { + "config values override current values in model", + &Model{ + BackupSchedule: types.StringValue("schedule"), + Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ + "type": types.StringValue("type"), + "snapshot_retention_days": types.Int64Value(1), + "daily_snapshot_retention_days": types.Int64Value(2), + "weekly_snapshot_retention_weeks": types.Int64Value(3), + "monthly_snapshot_retention_months": types.Int64Value(4), + "point_in_time_window_hours": types.Int64Value(5), + }), + }, + &optionsModel{ + SnapshotRetentionDays: types.Int64Value(6), + DailySnapshotRetentionDays: types.Int64Value(7), + WeeklySnapshotRetentionWeeks: types.Int64Value(8), + MonthlySnapshotRetentionMonths: types.Int64Value(9), + PointInTimeWindowHours: types.Int64Value(10), + }, + &mongodbflex.UpdateBackupSchedulePayload{ + BackupSchedule: utils.Ptr("schedule"), + SnapshotRetentionDays: utils.Ptr(int64(6)), + DailySnapshotRetentionDays: utils.Ptr(int64(7)), + WeeklySnapshotRetentionWeeks: utils.Ptr(int64(8)), + MonthlySnapshotRetentionMonths: utils.Ptr(int64(9)), + PointInTimeWindowHours: utils.Ptr(int64(10)), + }, + true, + }, + { + "current values in model fill in missing values in config", + &Model{ + BackupSchedule: types.StringValue("schedule"), + Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ + "type": types.StringValue("type"), + "snapshot_retention_days": types.Int64Value(1), + "daily_snapshot_retention_days": types.Int64Value(2), + "weekly_snapshot_retention_weeks": types.Int64Value(3), + "monthly_snapshot_retention_months": types.Int64Value(4), + "point_in_time_window_hours": types.Int64Value(5), + }), + }, + &optionsModel{ + SnapshotRetentionDays: types.Int64Value(6), + DailySnapshotRetentionDays: types.Int64Value(7), + WeeklySnapshotRetentionWeeks: types.Int64Null(), + MonthlySnapshotRetentionMonths: types.Int64Null(), + PointInTimeWindowHours: types.Int64Null(), + }, + &mongodbflex.UpdateBackupSchedulePayload{ + BackupSchedule: utils.Ptr("schedule"), + SnapshotRetentionDays: utils.Ptr(int64(6)), + DailySnapshotRetentionDays: utils.Ptr(int64(7)), + WeeklySnapshotRetentionWeeks: utils.Ptr(int64(3)), + MonthlySnapshotRetentionMonths: utils.Ptr(int64(4)), + PointInTimeWindowHours: utils.Ptr(int64(5)), + }, + true, + }, + { + "null_fields_and_int_conversions", + &Model{ + BackupSchedule: types.StringNull(), + }, + &optionsModel{ + SnapshotRetentionDays: types.Int64Null(), + DailySnapshotRetentionDays: types.Int64Null(), + WeeklySnapshotRetentionWeeks: types.Int64Null(), + MonthlySnapshotRetentionMonths: types.Int64Null(), + PointInTimeWindowHours: types.Int64Null(), + }, + &mongodbflex.UpdateBackupSchedulePayload{ + BackupSchedule: nil, + SnapshotRetentionDays: nil, + DailySnapshotRetentionDays: nil, + WeeklySnapshotRetentionWeeks: nil, + MonthlySnapshotRetentionMonths: nil, + PointInTimeWindowHours: nil, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output, err := toUpdateBackupScheduleOptionsPayload(context.Background(), tt.model, tt.configuredOptions) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + func TestLoadFlavorId(t *testing.T) { tests := []struct { description string diff --git a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go index 33f2c711..50ccda2e 100644 --- a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go +++ b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go @@ -20,22 +20,25 @@ import ( // Instance resource data var instanceResource = map[string]string{ - "project_id": testutil.ProjectId, - "name": fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)), - "acl": "192.168.0.0/16", - "flavor_cpu": "2", - "flavor_ram": "4", - "flavor_description": "Small, Compute optimized", - "replicas": "1", - "storage_class": "premium-perf2-mongodb", - "storage_size": "10", - "version": "5.0", - "version_updated": "6.0", - "options_type": "Single", - "flavor_id": "2.4", - "backup_schedule": "00 6 * * *", - "backup_schedule_updated": "00 12 * * *", - "backup_schedule_read": "0 6 * * *", + "project_id": testutil.ProjectId, + "name": fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)), + "acl": "192.168.0.0/16", + "flavor_cpu": "2", + "flavor_ram": "4", + "flavor_description": "Small, Compute optimized", + "replicas": "1", + "storage_class": "premium-perf2-mongodb", + "storage_size": "10", + "version": "5.0", + "version_updated": "6.0", + "options_type": "Single", + "flavor_id": "2.4", + "backup_schedule": "00 6 * * *", + "backup_schedule_updated": "00 12 * * *", + "backup_schedule_read": "0 6 * * *", + "snapshot_retention_days": "4", + "snapshot_retention_days_updated": "3", + "daily_snapshot_retention_days": "1", } // User resource data @@ -46,7 +49,7 @@ var userResource = map[string]string{ "project_id": instanceResource["project_id"], } -func configResources(version, backupSchedule string) string { +func configResources(version, backupSchedule, snapshotRetentionDays string) string { return fmt.Sprintf(` %s @@ -66,6 +69,8 @@ func configResources(version, backupSchedule string) string { version = "%s" options = { type = "%s" + snapshot_retention_days = %s + daily_snapshot_retention_days = %s } backup_schedule = "%s" } @@ -89,6 +94,8 @@ func configResources(version, backupSchedule string) string { instanceResource["storage_size"], version, instanceResource["options_type"], + snapshotRetentionDays, + instanceResource["daily_snapshot_retention_days"], backupSchedule, userResource["username"], userResource["role"], @@ -103,7 +110,7 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { Steps: []resource.TestStep{ // Creation { - Config: configResources(instanceResource["version"], instanceResource["backup_schedule"]), + Config: configResources(instanceResource["version"], instanceResource["backup_schedule"], instanceResource["snapshot_retention_days"]), Check: resource.ComposeAggregateTestCheckFunc( // Instance resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "project_id", instanceResource["project_id"]), @@ -120,6 +127,8 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "storage.size", instanceResource["storage_size"]), resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "version", instanceResource["version"]), resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "options.type", instanceResource["options_type"]), + resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "options.snapshot_retention_days", instanceResource["snapshot_retention_days"]), + resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "options.daily_snapshot_retention_days", instanceResource["daily_snapshot_retention_days"]), resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "backup_schedule", instanceResource["backup_schedule"]), // User @@ -153,7 +162,7 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { user_id = stackit_mongodbflex_user.user.user_id } `, - configResources(instanceResource["version"], instanceResource["backup_schedule"]), + configResources(instanceResource["version"], instanceResource["backup_schedule"], instanceResource["snapshot_retention_days"]), ), Check: resource.ComposeAggregateTestCheckFunc( // Instance data @@ -180,6 +189,8 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_mongodbflex_instance.instance", "flavor.ram", instanceResource["flavor_ram"]), resource.TestCheckResourceAttr("data.stackit_mongodbflex_instance.instance", "replicas", instanceResource["replicas"]), resource.TestCheckResourceAttr("data.stackit_mongodbflex_instance.instance", "options.type", instanceResource["options_type"]), + resource.TestCheckResourceAttr("data.stackit_mongodbflex_instance.instance", "options.snapshot_retention_days", instanceResource["snapshot_retention_days"]), + resource.TestCheckResourceAttr("data.stackit_mongodbflex_instance.instance", "options.daily_snapshot_retention_days", instanceResource["daily_snapshot_retention_days"]), resource.TestCheckResourceAttr("data.stackit_mongodbflex_instance.instance", "backup_schedule", instanceResource["backup_schedule_read"]), // User data @@ -245,7 +256,7 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { }, // Update { - Config: configResources(instanceResource["version_updated"], instanceResource["backup_schedule_updated"]), + Config: configResources(instanceResource["version_updated"], instanceResource["backup_schedule_updated"], instanceResource["snapshot_retention_days_updated"]), Check: resource.ComposeAggregateTestCheckFunc( // Instance data resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "project_id", instanceResource["project_id"]), @@ -262,6 +273,7 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "storage.size", instanceResource["storage_size"]), resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "version", instanceResource["version_updated"]), resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "options.type", instanceResource["options_type"]), + resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "options.snapshot_retention_days", instanceResource["snapshot_retention_days_updated"]), resource.TestCheckResourceAttr("stackit_mongodbflex_instance.instance", "backup_schedule", instanceResource["backup_schedule_updated"]), ), }, @@ -275,7 +287,9 @@ func testAccCheckMongoDBFlexDestroy(s *terraform.State) error { var client *mongodbflex.APIClient var err error if testutil.MongoDBFlexCustomEndpoint == "" { - client, err = mongodbflex.NewAPIClient() + client, err = mongodbflex.NewAPIClient( + config.WithRegion("eu01"), + ) } else { client, err = mongodbflex.NewAPIClient( config.WithEndpoint(testutil.MongoDBFlexCustomEndpoint),