From 717ba684f800bdc895cd2f2cf18e4f984d27709c Mon Sep 17 00:00:00 2001 From: maastha <122359335+maastha@users.noreply.github.com> Date: Tue, 26 Mar 2024 14:41:47 +0000 Subject: [PATCH] fix: Uses `overwriteBackupPolicies` in `mongodbatlas_backup_compliance_policy` to avoid overwriting non complying backup policies in updates (#2054) --- .../resource_backup_compliance_policy.go | 36 ++-- .../resource_backup_compliance_policy_test.go | 159 +++++++++++++++++- 2 files changed, 182 insertions(+), 13 deletions(-) diff --git a/internal/service/backupcompliancepolicy/resource_backup_compliance_policy.go b/internal/service/backupcompliancepolicy/resource_backup_compliance_policy.go index 8604ee659f..4acc92778f 100644 --- a/internal/service/backupcompliancepolicy/resource_backup_compliance_policy.go +++ b/internal/service/backupcompliancepolicy/resource_backup_compliance_policy.go @@ -8,13 +8,15 @@ import ( "net/http" "strings" + "go.mongodb.org/atlas-sdk/v20231115008/admin" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/spf13/cast" + "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/cloudbackupschedule" - "github.com/spf13/cast" - "go.mongodb.org/atlas-sdk/v20231115008/admin" ) const ( @@ -233,7 +235,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag. connV2 := meta.(*config.MongoDBClient).AtlasV2 projectID := d.Get("project_id").(string) - params := &admin.DataProtectionSettings20231001{ + dataProtectionSettings := &admin.DataProtectionSettings20231001{ ProjectId: conversion.StringPtr(projectID), AuthorizedEmail: d.Get("authorized_email").(string), AuthorizedUserFirstName: d.Get("authorized_user_first_name").(string), @@ -291,10 +293,15 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag. } } if len(backupPoliciesItem) > 0 { - params.ScheduledPolicyItems = &backupPoliciesItem + dataProtectionSettings.ScheduledPolicyItems = &backupPoliciesItem } - _, _, err := connV2.CloudBackupsApi.UpdateDataProtectionSettings(ctx, projectID, params).Execute() + params := admin.UpdateDataProtectionSettingsApiParams{ + GroupId: projectID, + DataProtectionSettings20231001: dataProtectionSettings, + OverwriteBackupPolicies: conversion.Pointer(false), + } + _, _, err := connV2.CloudBackupsApi.UpdateDataProtectionSettingsWithParams(ctx, ¶ms).Execute() if err != nil { return diag.FromErr(fmt.Errorf(errorBackupPolicyUpdate, projectID, err)) } @@ -393,7 +400,7 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag. ids := conversion.DecodeStateID(d.Id()) projectID := ids["project_id"] - params := &admin.DataProtectionSettings20231001{ + dataProtectionSettings := &admin.DataProtectionSettings20231001{ ProjectId: conversion.StringPtr(projectID), AuthorizedEmail: d.Get("authorized_email").(string), AuthorizedUserFirstName: d.Get("authorized_user_first_name").(string), @@ -402,19 +409,19 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag. } if d.HasChange("copy_protection_enabled") { - params.CopyProtectionEnabled = conversion.Pointer(d.Get("copy_protection_enabled").(bool)) + dataProtectionSettings.CopyProtectionEnabled = conversion.Pointer(d.Get("copy_protection_enabled").(bool)) } if d.HasChange("encryption_at_rest_enabled") { - params.EncryptionAtRestEnabled = conversion.Pointer(d.Get("encryption_at_rest_enabled").(bool)) + dataProtectionSettings.EncryptionAtRestEnabled = conversion.Pointer(d.Get("encryption_at_rest_enabled").(bool)) } if d.HasChange("pit_enabled") { - params.PitEnabled = conversion.Pointer(d.Get("pit_enabled").(bool)) + dataProtectionSettings.PitEnabled = conversion.Pointer(d.Get("pit_enabled").(bool)) } if d.HasChange("restore_window_days") { - params.RestoreWindowDays = conversion.Pointer(cast.ToInt(d.Get("restore_window_days"))) + dataProtectionSettings.RestoreWindowDays = conversion.Pointer(cast.ToInt(d.Get("restore_window_days"))) } var backupPoliciesItem []admin.BackupComplianceScheduledPolicyItem @@ -463,10 +470,15 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag. } } if len(backupPoliciesItem) > 0 { - params.ScheduledPolicyItems = &backupPoliciesItem + dataProtectionSettings.ScheduledPolicyItems = &backupPoliciesItem } - _, _, err := connV2.CloudBackupsApi.UpdateDataProtectionSettings(ctx, projectID, params).Execute() + params := admin.UpdateDataProtectionSettingsApiParams{ + GroupId: projectID, + DataProtectionSettings20231001: dataProtectionSettings, + OverwriteBackupPolicies: conversion.Pointer(false), + } + _, _, err := connV2.CloudBackupsApi.UpdateDataProtectionSettingsWithParams(ctx, ¶ms).Execute() if err != nil { return diag.FromErr(fmt.Errorf(errorBackupPolicyUpdate, projectID, err)) } diff --git a/internal/service/backupcompliancepolicy/resource_backup_compliance_policy_test.go b/internal/service/backupcompliancepolicy/resource_backup_compliance_policy_test.go index c963205936..1eb7feac64 100644 --- a/internal/service/backupcompliancepolicy/resource_backup_compliance_policy_test.go +++ b/internal/service/backupcompliancepolicy/resource_backup_compliance_policy_test.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "os" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc" ) @@ -41,7 +43,7 @@ func TestAccGenericBackupRSBackupCompliancePolicy_basic(t *testing.T) { }) } -func TestAccGenericBackupRSBackupCompliancePolicy_withFirstLastName(t *testing.T) { +func TestAccGenericBackupRSBackupCompliancePolicy_update(t *testing.T) { var ( orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") projectOwnerID = os.Getenv("MONGODB_ATLAS_PROJECT_OWNER_ID") @@ -53,6 +55,17 @@ func TestAccGenericBackupRSBackupCompliancePolicy_withFirstLastName(t *testing.T ProtoV6ProviderFactories: acc.TestAccProviderV6Factories, CheckDestroy: checkDestroy, Steps: []resource.TestStep{ + { + Config: configWithoutOptionals(projectName, orgID, projectOwnerID), + Check: resource.ComposeTestCheckFunc( + checkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "authorized_user_first_name", "First"), + resource.TestCheckResourceAttr(resourceName, "authorized_user_last_name", "Last"), + resource.TestCheckResourceAttr(resourceName, "pit_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "encryption_at_rest_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "copy_protection_enabled", "false"), + ), + }, { Config: configBasic(projectName, orgID, projectOwnerID), Check: resource.ComposeTestCheckFunc( @@ -65,6 +78,150 @@ func TestAccGenericBackupRSBackupCompliancePolicy_withFirstLastName(t *testing.T }) } +func TestAccGenericBackupRSBackupCompliancePolicy_overwriteBackupPolicies(t *testing.T) { + var ( + orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") + projectOwnerID = os.Getenv("MONGODB_ATLAS_PROJECT_OWNER_ID") + projectName = acc.RandomProjectName() + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.PreCheckBasic(t) }, + ProtoV6ProviderFactories: acc.TestAccProviderV6Factories, + CheckDestroy: checkDestroy, + Steps: []resource.TestStep{ + { + Config: configClusterWithBackupSchedule(projectName, orgID, projectOwnerID), + }, + { + Config: configOverwriteIncompatibleBackupPoliciesError(projectName, orgID, projectOwnerID), + ExpectError: regexp.MustCompile(`BACKUP_POLICIES_NOT_MEETING_BACKUP_COMPLIANCE_POLICY_REQUIREMENTS`), + }, + }, + }) +} + +func configOverwriteIncompatibleBackupPoliciesError(projectName, orgID, projectOwnerID string) string { + return acc.ConfigProjectWithSettings(projectName, orgID, projectOwnerID, false) + ` + resource "mongodbatlas_cluster" "test" { + project_id = mongodbatlas_project.test.id + name = "test1" + provider_name = "AWS" + cluster_type = "REPLICASET" + mongo_db_major_version = "6.0" + provider_instance_size_name = "M10" + auto_scaling_compute_enabled = false + cloud_backup = true + auto_scaling_disk_gb_enabled = true + disk_size_gb = 12 + provider_volume_type = "STANDARD" + retain_backups_enabled = true + + advanced_configuration { + oplog_min_retention_hours = 8 + } + + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + electable_nodes = 3 + priority = 7 + read_only_nodes = 0 + } + } + } + + resource "mongodbatlas_cloud_backup_schedule" "test" { + cluster_name = mongodbatlas_cluster.test.name + project_id = mongodbatlas_project.test.id + + reference_hour_of_day = 3 + reference_minute_of_hour = 45 + restore_window_days = 2 + + copy_settings { + cloud_provider = "AWS" + frequencies = ["DAILY"] + region_name = "US_WEST_1" + replication_spec_id = one(mongodbatlas_cluster.test.replication_specs).id + should_copy_oplogs = false + } + } + + resource "mongodbatlas_backup_compliance_policy" "test" { + project_id = mongodbatlas_project.test.id + authorized_email = "test@example.com" + authorized_user_first_name = "First" + authorized_user_last_name = "Last" + copy_protection_enabled = true + pit_enabled = false + encryption_at_rest_enabled = false + + on_demand_policy_item { + frequency_interval = 1 + retention_unit = "days" + retention_value = 1 + } + + policy_item_daily { + frequency_interval = 1 + retention_unit = "days" + retention_value = 1 + } + } + ` +} + +func configClusterWithBackupSchedule(projectName, orgID, projectOwnerID string) string { + return acc.ConfigProjectWithSettings(projectName, orgID, projectOwnerID, false) + ` + resource "mongodbatlas_cluster" "test" { + project_id = mongodbatlas_project.test.id + name = "test1" + provider_name = "AWS" + cluster_type = "REPLICASET" + mongo_db_major_version = "6.0" + provider_instance_size_name = "M10" + auto_scaling_compute_enabled = false + cloud_backup = true + auto_scaling_disk_gb_enabled = true + disk_size_gb = 12 + provider_volume_type = "STANDARD" + retain_backups_enabled = true + + advanced_configuration { + oplog_min_retention_hours = 8 + } + + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + electable_nodes = 3 + priority = 7 + read_only_nodes = 0 + } + } + } + + resource "mongodbatlas_cloud_backup_schedule" "test" { + cluster_name = mongodbatlas_cluster.test.name + project_id = mongodbatlas_project.test.id + + reference_hour_of_day = 3 + reference_minute_of_hour = 45 + restore_window_days = 2 + + copy_settings { + cloud_provider = "AWS" + frequencies = ["DAILY"] + region_name = "US_WEST_1" + replication_spec_id = one(mongodbatlas_cluster.test.replication_specs).id + should_copy_oplogs = false + } + } + ` +} func TestAccGenericBackupRSBackupCompliancePolicy_withoutOptionals(t *testing.T) { var ( orgID = os.Getenv("MONGODB_ATLAS_ORG_ID")