From 83f52a450e715c49a0cf16f1ecb8926b383889a6 Mon Sep 17 00:00:00 2001 From: Brian Adams Date: Wed, 27 Sep 2023 13:20:17 -0400 Subject: [PATCH 01/14] Add timezone argument to aws_backup_plan --- internal/service/backup/plan.go | 11 ++++++++ internal/service/backup/plan_test.go | 32 ++++++++++++++++++++++-- website/docs/r/backup_plan.html.markdown | 2 ++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 9093e0f7a4b..c0300883dd7 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -71,6 +71,10 @@ func ResourcePlan() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "schedule_timezone": { + Type: schema.TypeString, + Optional: true, + }, "enable_continuous_backup": { Type: schema.TypeBool, Optional: true, @@ -310,6 +314,9 @@ func expandPlanRules(ctx context.Context, vRules *schema.Set) []*backup.RuleInpu if vSchedule, ok := mRule["schedule"].(string); ok && vSchedule != "" { rule.ScheduleExpression = aws.String(vSchedule) } + if vScheduleTZ, ok := mRule["schedule_timezone"].(string); ok && vScheduleTZ != "" { + rule.ScheduleExpressionTimezone = aws.String(vScheduleTZ) + } if vEnableContinuousBackup, ok := mRule["enable_continuous_backup"].(bool); ok { rule.EnableContinuousBackup = aws.Bool(vEnableContinuousBackup) } @@ -408,6 +415,7 @@ func flattenPlanRules(ctx context.Context, rules []*backup.Rule) *schema.Set { "rule_name": aws.StringValue(rule.RuleName), "target_vault_name": aws.StringValue(rule.TargetBackupVaultName), "schedule": aws.StringValue(rule.ScheduleExpression), + "schedule_timezone": aws.StringValue(rule.ScheduleExpressionTimezone), "enable_continuous_backup": aws.BoolValue(rule.EnableContinuousBackup), "start_window": int(aws.Int64Value(rule.StartWindowMinutes)), "completion_window": int(aws.Int64Value(rule.CompletionWindowMinutes)), @@ -494,6 +502,9 @@ func planHash(vRule interface{}) int { if v, ok := mRule["schedule"].(string); ok { buf.WriteString(fmt.Sprintf("%s-", v)) } + if v, ok := mRule["schedule_timezone"].(string); ok { + buf.WriteString(fmt.Sprintf("%s-", v)) + } if v, ok := mRule["enable_continuous_backup"].(bool); ok { buf.WriteString(fmt.Sprintf("%t-", v)) } diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 410815a0135..2612eff47d3 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -42,6 +42,7 @@ func TestAccBackupPlan_basic(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -136,12 +137,14 @@ func TestAccBackupPlan_withRules(t *testing.T) { "rule_name": rule1Name, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "rule_name": rule2Name, "target_vault_name": rName, "schedule": "cron(0 6 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -162,18 +165,21 @@ func TestAccBackupPlan_withRules(t *testing.T) { "rule_name": rule1Name, "target_vault_name": rName, "schedule": "cron(0 6 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "rule_name": rule2Name, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "rule_name": rule3Name, "target_vault_name": rName, "schedule": "cron(0 18 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -189,6 +195,7 @@ func TestAccBackupPlan_withRules(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -293,6 +300,7 @@ func TestAccBackupPlan_withRecoveryPointTags(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", "recovery_point_tags.%": "3", "recovery_point_tags.Name": rName, @@ -336,6 +344,7 @@ func TestAccBackupPlan_withRecoveryPointTags(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -618,6 +627,7 @@ func TestAccBackupPlan_enableContinuousBackup(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", + "schedule_timezone": "Pacific/Fiji", "enable_continuous_backup": "true", "lifecycle.#": "1", "lifecycle.0.delete_after": "35", @@ -722,6 +732,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" } } `, rName) @@ -740,6 +751,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" } tags = { @@ -764,6 +776,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" } tags = { @@ -788,11 +801,13 @@ resource "aws_backup_plan" "test" { rule_name = "%[1]s_1" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" } rule { rule_name = "%[1]s_2" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 6 * * ? *)" + schedule_timezone = "Pacific/Fiji" } } `, rName) @@ -811,16 +826,19 @@ resource "aws_backup_plan" "test" { rule_name = "%[1]s_1" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 6 * * ? *)" + schedule_timezone = "Pacific/Fiji" } rule { rule_name = "%[1]s_2" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" } rule { rule_name = "%[1]s_3" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 18 * * ? *)" + schedule_timezone = "Pacific/Fiji" } } `, rName) @@ -839,6 +857,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 7 @@ -861,6 +880,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { delete_after = 120 @@ -883,6 +903,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -906,6 +927,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" recovery_point_tags = { Name = %[1]q @@ -930,7 +952,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - + schedule_timezone = "Pacific/Fiji" recovery_point_tags = { Name = %[1]q Key2 = "Value2b" @@ -958,6 +980,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -998,6 +1021,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1044,6 +1068,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1080,7 +1105,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - + schedule_timezone = "Pacific/Fiji" copy_action { destination_vault_arn = aws_backup_vault.test2.arn } @@ -1102,6 +1127,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1133,6 +1159,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1164,6 +1191,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" enable_continuous_backup = true lifecycle { diff --git a/website/docs/r/backup_plan.html.markdown b/website/docs/r/backup_plan.html.markdown index 5c733efb8a5..6f5d82ba7f3 100644 --- a/website/docs/r/backup_plan.html.markdown +++ b/website/docs/r/backup_plan.html.markdown @@ -20,6 +20,7 @@ resource "aws_backup_plan" "example" { rule_name = "tf_example_backup_rule" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" + schedule_timezone = "Pacific/Fiji" lifecycle { delete_after = 14 @@ -51,6 +52,7 @@ This resource supports the following arguments: * `rule_name` - (Required) An display name for a backup rule. * `target_vault_name` - (Required) The name of a logical container where backups are stored. * `schedule` - (Optional) A CRON expression specifying when AWS Backup initiates a backup job. +* `schedule_timezone` - (Optional) A time zone in "Pacific/Fiji" format. * `enable_continuous_backup` - (Optional) Enable continuous backups for supported resources. * `start_window` - (Optional) The amount of time in minutes before beginning a backup. * `completion_window` - (Optional) The amount of time in minutes AWS Backup attempts a backup before canceling the job and returning an error. From c5b33f172ab876a8b89c9700651e687e23462870 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 16:12:52 -0400 Subject: [PATCH 02/14] Revert "Add timezone argument to aws_backup_plan" This reverts commit 83f52a450e715c49a0cf16f1ecb8926b383889a6. --- internal/service/backup/plan.go | 11 -------- internal/service/backup/plan_test.go | 32 ++---------------------- website/docs/r/backup_plan.html.markdown | 2 -- 3 files changed, 2 insertions(+), 43 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index c0300883dd7..9093e0f7a4b 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -71,10 +71,6 @@ func ResourcePlan() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "schedule_timezone": { - Type: schema.TypeString, - Optional: true, - }, "enable_continuous_backup": { Type: schema.TypeBool, Optional: true, @@ -314,9 +310,6 @@ func expandPlanRules(ctx context.Context, vRules *schema.Set) []*backup.RuleInpu if vSchedule, ok := mRule["schedule"].(string); ok && vSchedule != "" { rule.ScheduleExpression = aws.String(vSchedule) } - if vScheduleTZ, ok := mRule["schedule_timezone"].(string); ok && vScheduleTZ != "" { - rule.ScheduleExpressionTimezone = aws.String(vScheduleTZ) - } if vEnableContinuousBackup, ok := mRule["enable_continuous_backup"].(bool); ok { rule.EnableContinuousBackup = aws.Bool(vEnableContinuousBackup) } @@ -415,7 +408,6 @@ func flattenPlanRules(ctx context.Context, rules []*backup.Rule) *schema.Set { "rule_name": aws.StringValue(rule.RuleName), "target_vault_name": aws.StringValue(rule.TargetBackupVaultName), "schedule": aws.StringValue(rule.ScheduleExpression), - "schedule_timezone": aws.StringValue(rule.ScheduleExpressionTimezone), "enable_continuous_backup": aws.BoolValue(rule.EnableContinuousBackup), "start_window": int(aws.Int64Value(rule.StartWindowMinutes)), "completion_window": int(aws.Int64Value(rule.CompletionWindowMinutes)), @@ -502,9 +494,6 @@ func planHash(vRule interface{}) int { if v, ok := mRule["schedule"].(string); ok { buf.WriteString(fmt.Sprintf("%s-", v)) } - if v, ok := mRule["schedule_timezone"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } if v, ok := mRule["enable_continuous_backup"].(bool); ok { buf.WriteString(fmt.Sprintf("%t-", v)) } diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 2612eff47d3..410815a0135 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -42,7 +42,6 @@ func TestAccBackupPlan_basic(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -137,14 +136,12 @@ func TestAccBackupPlan_withRules(t *testing.T) { "rule_name": rule1Name, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "rule_name": rule2Name, "target_vault_name": rName, "schedule": "cron(0 6 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -165,21 +162,18 @@ func TestAccBackupPlan_withRules(t *testing.T) { "rule_name": rule1Name, "target_vault_name": rName, "schedule": "cron(0 6 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "rule_name": rule2Name, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "rule_name": rule3Name, "target_vault_name": rName, "schedule": "cron(0 18 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -195,7 +189,6 @@ func TestAccBackupPlan_withRules(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -300,7 +293,6 @@ func TestAccBackupPlan_withRecoveryPointTags(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", "recovery_point_tags.%": "3", "recovery_point_tags.Name": rName, @@ -344,7 +336,6 @@ func TestAccBackupPlan_withRecoveryPointTags(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "lifecycle.#": "0", }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -627,7 +618,6 @@ func TestAccBackupPlan_enableContinuousBackup(t *testing.T) { "rule_name": rName, "target_vault_name": rName, "schedule": "cron(0 12 * * ? *)", - "schedule_timezone": "Pacific/Fiji", "enable_continuous_backup": "true", "lifecycle.#": "1", "lifecycle.0.delete_after": "35", @@ -732,7 +722,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" } } `, rName) @@ -751,7 +740,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" } tags = { @@ -776,7 +764,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" } tags = { @@ -801,13 +788,11 @@ resource "aws_backup_plan" "test" { rule_name = "%[1]s_1" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" } rule { rule_name = "%[1]s_2" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 6 * * ? *)" - schedule_timezone = "Pacific/Fiji" } } `, rName) @@ -826,19 +811,16 @@ resource "aws_backup_plan" "test" { rule_name = "%[1]s_1" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 6 * * ? *)" - schedule_timezone = "Pacific/Fiji" } rule { rule_name = "%[1]s_2" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" } rule { rule_name = "%[1]s_3" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 18 * * ? *)" - schedule_timezone = "Pacific/Fiji" } } `, rName) @@ -857,7 +839,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 7 @@ -880,7 +861,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { delete_after = 120 @@ -903,7 +883,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -927,7 +906,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" recovery_point_tags = { Name = %[1]q @@ -952,7 +930,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" + recovery_point_tags = { Name = %[1]q Key2 = "Value2b" @@ -980,7 +958,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1021,7 +998,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1068,7 +1044,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1105,7 +1080,7 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" + copy_action { destination_vault_arn = aws_backup_vault.test2.arn } @@ -1127,7 +1102,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1159,7 +1133,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { cold_storage_after = 30 @@ -1191,7 +1164,6 @@ resource "aws_backup_plan" "test" { rule_name = %[1]q target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" enable_continuous_backup = true lifecycle { diff --git a/website/docs/r/backup_plan.html.markdown b/website/docs/r/backup_plan.html.markdown index 6f5d82ba7f3..5c733efb8a5 100644 --- a/website/docs/r/backup_plan.html.markdown +++ b/website/docs/r/backup_plan.html.markdown @@ -20,7 +20,6 @@ resource "aws_backup_plan" "example" { rule_name = "tf_example_backup_rule" target_vault_name = aws_backup_vault.test.name schedule = "cron(0 12 * * ? *)" - schedule_timezone = "Pacific/Fiji" lifecycle { delete_after = 14 @@ -52,7 +51,6 @@ This resource supports the following arguments: * `rule_name` - (Required) An display name for a backup rule. * `target_vault_name` - (Required) The name of a logical container where backups are stored. * `schedule` - (Optional) A CRON expression specifying when AWS Backup initiates a backup job. -* `schedule_timezone` - (Optional) A time zone in "Pacific/Fiji" format. * `enable_continuous_backup` - (Optional) Enable continuous backups for supported resources. * `start_window` - (Optional) The amount of time in minutes before beginning a backup. * `completion_window` - (Optional) The amount of time in minutes AWS Backup attempts a backup before canceling the job and returning an error. From 6e8713579f3472327b1401c7ef0a6640ade8a6b4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 16:26:53 -0400 Subject: [PATCH 03/14] r/aws_backup_plan: Add `rule.schedule_expression_timezone` argument. --- .changelog/33653.txt | 4 ++++ internal/service/backup/plan.go | 29 ++++++++++++++++++------ internal/service/backup/plan_test.go | 9 ++++---- website/docs/r/backup_plan.html.markdown | 1 + 4 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 .changelog/33653.txt diff --git a/.changelog/33653.txt b/.changelog/33653.txt new file mode 100644 index 00000000000..0cd32d40ee5 --- /dev/null +++ b/.changelog/33653.txt @@ -0,0 +1,4 @@ + +```release-note:enhancement +resource/aws_backup_plan: Add `rule.schedule_expression_timezone` argument +``` \ No newline at end of file diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 7cfca497c9a..b7a606d1313 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -157,6 +157,11 @@ func resourcePlan() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "schedule_expression_timezone": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "start_window": { Type: schema.TypeInt, Optional: true, @@ -339,6 +344,12 @@ func expandPlanRules(ctx context.Context, vRules *schema.Set) []awstypes.BackupR if vSchedule, ok := mRule[names.AttrSchedule].(string); ok && vSchedule != "" { rule.ScheduleExpression = aws.String(vSchedule) } + if vSchedule, ok := mRule[names.AttrSchedule].(string); ok && vSchedule != "" { + rule.ScheduleExpression = aws.String(vSchedule) + } + if v, ok := mRule["schedule_expression_timezone"].(string); ok && v != "" { + rule.ScheduleExpressionTimezone = aws.String(v) + } if vEnableContinuousBackup, ok := mRule["enable_continuous_backup"].(bool); ok { rule.EnableContinuousBackup = aws.Bool(vEnableContinuousBackup) } @@ -440,13 +451,14 @@ func flattenPlanRules(ctx context.Context, rules []awstypes.BackupRule) *schema. for _, rule := range rules { mRule := map[string]interface{}{ - "rule_name": aws.ToString(rule.RuleName), - "target_vault_name": aws.ToString(rule.TargetBackupVaultName), - names.AttrSchedule: aws.ToString(rule.ScheduleExpression), - "enable_continuous_backup": aws.ToBool(rule.EnableContinuousBackup), - "start_window": int(aws.ToInt64(rule.StartWindowMinutes)), - "completion_window": int(aws.ToInt64(rule.CompletionWindowMinutes)), - "recovery_point_tags": KeyValueTags(ctx, rule.RecoveryPointTags).IgnoreAWS().Map(), + "rule_name": aws.ToString(rule.RuleName), + "target_vault_name": aws.ToString(rule.TargetBackupVaultName), + names.AttrSchedule: aws.ToString(rule.ScheduleExpression), + "schedule_expression_timezone": aws.ToString(rule.ScheduleExpressionTimezone), + "enable_continuous_backup": aws.ToBool(rule.EnableContinuousBackup), + "start_window": aws.ToInt64(rule.StartWindowMinutes), + "completion_window": aws.ToInt64(rule.CompletionWindowMinutes), + "recovery_point_tags": KeyValueTags(ctx, rule.RecoveryPointTags).IgnoreAWS().Map(), } if lifecycle := rule.Lifecycle; lifecycle != nil { @@ -526,6 +538,9 @@ func planHash(vRule interface{}) int { if v, ok := mRule[names.AttrSchedule].(string); ok { buf.WriteString(fmt.Sprintf("%s-", v)) } + if v, ok := mRule["schedule_expression_timezone"].(string); ok { + buf.WriteString(fmt.Sprintf("%s-", v)) + } if v, ok := mRule["enable_continuous_backup"].(bool); ok { buf.WriteString(fmt.Sprintf("%t-", v)) } diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 8de246aef41..62553981819 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -40,10 +40,11 @@ func TestAccBackupPlan_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, acctest.Ct1), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ - "rule_name": rName, - "target_vault_name": rName, - names.AttrSchedule: "cron(0 12 * * ? *)", - "lifecycle.#": acctest.Ct0, + "rule_name": rName, + "target_vault_name": rName, + names.AttrSchedule: "cron(0 12 * * ? *)", + "schedule_expression_timezone": "Etc/UTC", + "lifecycle.#": acctest.Ct0, }), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), resource.TestCheckResourceAttrSet(resourceName, names.AttrVersion), diff --git a/website/docs/r/backup_plan.html.markdown b/website/docs/r/backup_plan.html.markdown index e90eb3cd560..7ecfe8d326d 100644 --- a/website/docs/r/backup_plan.html.markdown +++ b/website/docs/r/backup_plan.html.markdown @@ -51,6 +51,7 @@ This resource supports the following arguments: * `rule_name` - (Required) An display name for a backup rule. * `target_vault_name` - (Required) The name of a logical container where backups are stored. * `schedule` - (Optional) A CRON expression specifying when AWS Backup initiates a backup job. +* `schedule_expression_timezone` - (Optional) The timezone in which the schedule expression is set. By default, ScheduleExpressions are in UTC. * `enable_continuous_backup` - (Optional) Enable continuous backups for supported resources. * `start_window` - (Optional) The amount of time in minutes before beginning a backup. * `completion_window` - (Optional) The amount of time in minutes AWS Backup attempts a backup before canceling the job and returning an error. From ce42ebed1e9d582065cb301b61a0993c4112e91a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 16:38:39 -0400 Subject: [PATCH 04/14] r/aws_backup_plan: Tidy up flex. --- internal/service/backup/plan.go | 196 ++++++++++---------- internal/service/backup/plan_data_source.go | 2 +- 2 files changed, 100 insertions(+), 98 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index b7a606d1313..add9222738b 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -198,9 +198,9 @@ func resourcePlanCreate(ctx context.Context, d *schema.ResourceData, meta interf name := d.Get(names.AttrName).(string) input := &backup.CreateBackupPlanInput{ BackupPlan: &awstypes.BackupPlanInput{ - AdvancedBackupSettings: expandPlanAdvancedSettings(d.Get("advanced_backup_setting").(*schema.Set)), + AdvancedBackupSettings: expandAdvancedBackupSetting(d.Get("advanced_backup_setting").(*schema.Set).List()), BackupPlanName: aws.String(name), - Rules: expandPlanRules(ctx, d.Get(names.AttrRule).(*schema.Set)), + Rules: expandBackupRuleInputs(ctx, d.Get(names.AttrRule).(*schema.Set).List()), }, BackupPlanTags: getTagsIn(ctx), } @@ -234,12 +234,12 @@ func resourcePlanRead(ctx context.Context, d *schema.ResourceData, meta interfac // AdvancedBackupSettings being read direct from output and not from under // output.BackupPlan is deliberate - the latter always contains nil. - if err := d.Set("advanced_backup_setting", flattenPlanAdvancedSettings(output.AdvancedBackupSettings)); err != nil { + if err := d.Set("advanced_backup_setting", flattenAdvancedBackupSettings(output.AdvancedBackupSettings)); err != nil { return sdkdiag.AppendErrorf(diags, "setting advanced_backup_setting: %s", err) } d.Set(names.AttrARN, output.BackupPlanArn) d.Set(names.AttrName, output.BackupPlan.BackupPlanName) - if err := d.Set(names.AttrRule, flattenPlanRules(ctx, output.BackupPlan.Rules)); err != nil { + if err := d.Set(names.AttrRule, flattenBackupRules(ctx, output.BackupPlan.Rules)); err != nil { return sdkdiag.AppendErrorf(diags, "setting rule: %s", err) } d.Set(names.AttrVersion, output.VersionId) @@ -251,12 +251,12 @@ func resourcePlanUpdate(ctx context.Context, d *schema.ResourceData, meta interf var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - if d.HasChanges(names.AttrRule, "advanced_backup_setting") { + if d.HasChanges("advanced_backup_setting", names.AttrRule) { input := &backup.UpdateBackupPlanInput{ BackupPlan: &awstypes.BackupPlanInput{ - AdvancedBackupSettings: expandPlanAdvancedSettings(d.Get("advanced_backup_setting").(*schema.Set)), + AdvancedBackupSettings: expandAdvancedBackupSetting(d.Get("advanced_backup_setting").(*schema.Set).List()), BackupPlanName: aws.String(d.Get(names.AttrName).(string)), - Rules: expandPlanRules(ctx, d.Get(names.AttrRule).(*schema.Set)), + Rules: expandBackupRuleInputs(ctx, d.Get(names.AttrRule).(*schema.Set).List()), }, BackupPlanId: aws.String(d.Id()), } @@ -325,106 +325,108 @@ func findPlan(ctx context.Context, conn *backup.Client, input *backup.GetBackupP return output, nil } -func expandPlanRules(ctx context.Context, vRules *schema.Set) []awstypes.BackupRuleInput { - rules := []awstypes.BackupRuleInput{} +func expandBackupRuleInputs(ctx context.Context, tfList []interface{}) []awstypes.BackupRuleInput { // nosemgrep:ci.backup-in-func-name + apiObjects := []awstypes.BackupRuleInput{} - for _, vRule := range vRules.List() { - rule := awstypes.BackupRuleInput{} + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + apiObject := awstypes.BackupRuleInput{} - mRule := vRule.(map[string]interface{}) - - if vRuleName, ok := mRule["rule_name"].(string); ok && vRuleName != "" { - rule.RuleName = aws.String(vRuleName) + if vRuleName, ok := tfMap["rule_name"].(string); ok && vRuleName != "" { + apiObject.RuleName = aws.String(vRuleName) } else { continue } - if vTargetVaultName, ok := mRule["target_vault_name"].(string); ok && vTargetVaultName != "" { - rule.TargetBackupVaultName = aws.String(vTargetVaultName) + if vTargetVaultName, ok := tfMap["target_vault_name"].(string); ok && vTargetVaultName != "" { + apiObject.TargetBackupVaultName = aws.String(vTargetVaultName) } - if vSchedule, ok := mRule[names.AttrSchedule].(string); ok && vSchedule != "" { - rule.ScheduleExpression = aws.String(vSchedule) + if vSchedule, ok := tfMap[names.AttrSchedule].(string); ok && vSchedule != "" { + apiObject.ScheduleExpression = aws.String(vSchedule) } - if vSchedule, ok := mRule[names.AttrSchedule].(string); ok && vSchedule != "" { - rule.ScheduleExpression = aws.String(vSchedule) + if vSchedule, ok := tfMap[names.AttrSchedule].(string); ok && vSchedule != "" { + apiObject.ScheduleExpression = aws.String(vSchedule) } - if v, ok := mRule["schedule_expression_timezone"].(string); ok && v != "" { - rule.ScheduleExpressionTimezone = aws.String(v) + if v, ok := tfMap["schedule_expression_timezone"].(string); ok && v != "" { + apiObject.ScheduleExpressionTimezone = aws.String(v) } - if vEnableContinuousBackup, ok := mRule["enable_continuous_backup"].(bool); ok { - rule.EnableContinuousBackup = aws.Bool(vEnableContinuousBackup) + if vEnableContinuousBackup, ok := tfMap["enable_continuous_backup"].(bool); ok { + apiObject.EnableContinuousBackup = aws.Bool(vEnableContinuousBackup) } - if vStartWindow, ok := mRule["start_window"].(int); ok { - rule.StartWindowMinutes = aws.Int64(int64(vStartWindow)) + if vStartWindow, ok := tfMap["start_window"].(int); ok { + apiObject.StartWindowMinutes = aws.Int64(int64(vStartWindow)) } - if vCompletionWindow, ok := mRule["completion_window"].(int); ok { - rule.CompletionWindowMinutes = aws.Int64(int64(vCompletionWindow)) + if vCompletionWindow, ok := tfMap["completion_window"].(int); ok { + apiObject.CompletionWindowMinutes = aws.Int64(int64(vCompletionWindow)) } - if vRecoveryPointTags, ok := mRule["recovery_point_tags"].(map[string]interface{}); ok && len(vRecoveryPointTags) > 0 { - rule.RecoveryPointTags = Tags(tftags.New(ctx, vRecoveryPointTags).IgnoreAWS()) + if vRecoveryPointTags, ok := tfMap["recovery_point_tags"].(map[string]interface{}); ok && len(vRecoveryPointTags) > 0 { + apiObject.RecoveryPointTags = Tags(tftags.New(ctx, vRecoveryPointTags).IgnoreAWS()) } - if v, ok := mRule["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - rule.Lifecycle = expandPlanLifecycle(v[0].(map[string]interface{})) + if v, ok := tfMap["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Lifecycle = expandLifecycle(v[0].(map[string]interface{})) } - if vCopyActions := expandPlanCopyActions(mRule["copy_action"].(*schema.Set).List()); len(vCopyActions) > 0 { - rule.CopyActions = vCopyActions + if vCopyActions := expandCopyActions(tfMap["copy_action"].(*schema.Set).List()); len(vCopyActions) > 0 { + apiObject.CopyActions = vCopyActions } - rules = append(rules, rule) + apiObjects = append(apiObjects, apiObject) } - return rules + return apiObjects } -func expandPlanAdvancedSettings(vAdvancedBackupSettings *schema.Set) []awstypes.AdvancedBackupSetting { - advancedBackupSettings := []awstypes.AdvancedBackupSetting{} +func expandAdvancedBackupSetting(tfList []interface{}) []awstypes.AdvancedBackupSetting { // nosemgrep:ci.backup-in-func-name + if len(tfList) == 0 { + return nil + } - for _, vAdvancedBackupSetting := range vAdvancedBackupSettings.List() { - advancedBackupSetting := awstypes.AdvancedBackupSetting{} + apiObjects := []awstypes.AdvancedBackupSetting{} - mAdvancedBackupSetting := vAdvancedBackupSetting.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + apiObject := awstypes.AdvancedBackupSetting{} - if v, ok := mAdvancedBackupSetting["backup_options"].(map[string]interface{}); ok && v != nil { - advancedBackupSetting.BackupOptions = flex.ExpandStringValueMap(v) + if v, ok := tfMap["backup_options"].(map[string]interface{}); ok && v != nil { + apiObject.BackupOptions = flex.ExpandStringValueMap(v) } - if v, ok := mAdvancedBackupSetting[names.AttrResourceType].(string); ok && v != "" { - advancedBackupSetting.ResourceType = aws.String(v) + if v, ok := tfMap[names.AttrResourceType].(string); ok && v != "" { + apiObject.ResourceType = aws.String(v) } // https://github.com/hashicorp/terraform-plugin-sdk/issues/588 // Map in Set may add empty element. Ignore it. - if advancedBackupSetting.ResourceType == nil { + if apiObject.ResourceType == nil { continue } - advancedBackupSettings = append(advancedBackupSettings, advancedBackupSetting) + apiObjects = append(apiObjects, apiObject) } - return advancedBackupSettings + return apiObjects } -func expandPlanCopyActions(actionList []interface{}) []awstypes.CopyAction { - actions := []awstypes.CopyAction{} +func expandCopyActions(tfList []interface{}) []awstypes.CopyAction { + apiObjects := []awstypes.CopyAction{} - for _, i := range actionList { - item := i.(map[string]interface{}) - action := awstypes.CopyAction{} + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + apiObject := awstypes.CopyAction{} - action.DestinationBackupVaultArn = aws.String(item["destination_vault_arn"].(string)) + apiObject.DestinationBackupVaultArn = aws.String(tfMap["destination_vault_arn"].(string)) - if v, ok := item["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - action.Lifecycle = expandPlanLifecycle(v[0].(map[string]interface{})) + if v, ok := tfMap["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Lifecycle = expandLifecycle(v[0].(map[string]interface{})) } - actions = append(actions, action) + apiObjects = append(apiObjects, apiObject) } - return actions + return apiObjects } -func expandPlanLifecycle(tfMap map[string]interface{}) *awstypes.Lifecycle { +func expandLifecycle(tfMap map[string]interface{}) *awstypes.Lifecycle { if tfMap == nil { return nil } @@ -446,62 +448,62 @@ func expandPlanLifecycle(tfMap map[string]interface{}) *awstypes.Lifecycle { return apiObject } -func flattenPlanRules(ctx context.Context, rules []awstypes.BackupRule) *schema.Set { - vRules := []interface{}{} - - for _, rule := range rules { - mRule := map[string]interface{}{ - "rule_name": aws.ToString(rule.RuleName), - "target_vault_name": aws.ToString(rule.TargetBackupVaultName), - names.AttrSchedule: aws.ToString(rule.ScheduleExpression), - "schedule_expression_timezone": aws.ToString(rule.ScheduleExpressionTimezone), - "enable_continuous_backup": aws.ToBool(rule.EnableContinuousBackup), - "start_window": aws.ToInt64(rule.StartWindowMinutes), - "completion_window": aws.ToInt64(rule.CompletionWindowMinutes), - "recovery_point_tags": KeyValueTags(ctx, rule.RecoveryPointTags).IgnoreAWS().Map(), +func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) *schema.Set { // nosemgrep:ci.backup-in-func-name + tfList := []interface{}{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "rule_name": aws.ToString(apiObject.RuleName), + "target_vault_name": aws.ToString(apiObject.TargetBackupVaultName), + names.AttrSchedule: aws.ToString(apiObject.ScheduleExpression), + "schedule_expression_timezone": aws.ToString(apiObject.ScheduleExpressionTimezone), + "enable_continuous_backup": aws.ToBool(apiObject.EnableContinuousBackup), + "start_window": aws.ToInt64(apiObject.StartWindowMinutes), + "completion_window": aws.ToInt64(apiObject.CompletionWindowMinutes), + "recovery_point_tags": KeyValueTags(ctx, apiObject.RecoveryPointTags).IgnoreAWS().Map(), } - if lifecycle := rule.Lifecycle; lifecycle != nil { - mRule["lifecycle"] = flattenPlanCopyActionLifecycle(lifecycle) + if lifecycle := apiObject.Lifecycle; lifecycle != nil { + tfMap["lifecycle"] = flattenLifecycle(lifecycle) } - mRule["copy_action"] = flattenPlanCopyActions(rule.CopyActions) + tfMap["copy_action"] = flattenCopyActions(apiObject.CopyActions) - vRules = append(vRules, mRule) + tfList = append(tfList, tfMap) } - return schema.NewSet(planHash, vRules) + return schema.NewSet(planHash, tfList) } -func flattenPlanAdvancedSettings(advancedBackupSettings []awstypes.AdvancedBackupSetting) *schema.Set { - vAdvancedBackupSettings := []interface{}{} +func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) *schema.Set { // nosemgrep:ci.backup-in-func-name + tfList := []interface{}{} - for _, advancedBackupSetting := range advancedBackupSettings { - mAdvancedBackupSetting := map[string]interface{}{ - "backup_options": advancedBackupSetting.BackupOptions, - names.AttrResourceType: aws.ToString(advancedBackupSetting.ResourceType), + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "backup_options": apiObject.BackupOptions, + names.AttrResourceType: aws.ToString(apiObject.ResourceType), } - vAdvancedBackupSettings = append(vAdvancedBackupSettings, mAdvancedBackupSetting) + tfList = append(tfList, tfMap) } - return schema.NewSet(planHash, vAdvancedBackupSettings) + return schema.NewSet(planHash, tfList) } -func flattenPlanCopyActions(copyActions []awstypes.CopyAction) []interface{} { - if len(copyActions) == 0 { +func flattenCopyActions(apiObjects []awstypes.CopyAction) []interface{} { + if len(apiObjects) == 0 { return nil } var tfList []interface{} - for _, copyAction := range copyActions { + for _, copyAction := range apiObjects { tfMap := map[string]interface{}{ "destination_vault_arn": aws.ToString(copyAction.DestinationBackupVaultArn), } if copyAction.Lifecycle != nil { - tfMap["lifecycle"] = flattenPlanCopyActionLifecycle(copyAction.Lifecycle) + tfMap["lifecycle"] = flattenLifecycle(copyAction.Lifecycle) } tfList = append(tfList, tfMap) @@ -510,18 +512,18 @@ func flattenPlanCopyActions(copyActions []awstypes.CopyAction) []interface{} { return tfList } -func flattenPlanCopyActionLifecycle(copyActionLifecycle *awstypes.Lifecycle) []interface{} { - if copyActionLifecycle == nil { +func flattenLifecycle(apiObject *awstypes.Lifecycle) []interface{} { + if apiObject == nil { return []interface{}{} } - m := map[string]interface{}{ - "delete_after": aws.ToInt64(copyActionLifecycle.DeleteAfterDays), - "cold_storage_after": aws.ToInt64(copyActionLifecycle.MoveToColdStorageAfterDays), - "opt_in_to_archive_for_supported_resources": aws.ToBool(copyActionLifecycle.OptInToArchiveForSupportedResources), + tfMap := map[string]interface{}{ + "delete_after": aws.ToInt64(apiObject.DeleteAfterDays), + "cold_storage_after": aws.ToInt64(apiObject.MoveToColdStorageAfterDays), + "opt_in_to_archive_for_supported_resources": aws.ToBool(apiObject.OptInToArchiveForSupportedResources), } - return []interface{}{m} + return []interface{}{tfMap} } func planHash(vRule interface{}) int { diff --git a/internal/service/backup/plan_data_source.go b/internal/service/backup/plan_data_source.go index ba32f7f9dd4..afbfb579f0b 100644 --- a/internal/service/backup/plan_data_source.go +++ b/internal/service/backup/plan_data_source.go @@ -142,7 +142,7 @@ func dataSourcePlanRead(ctx context.Context, d *schema.ResourceData, meta interf d.SetId(aws.ToString(output.BackupPlanId)) d.Set(names.AttrARN, output.BackupPlanArn) d.Set(names.AttrName, output.BackupPlan.BackupPlanName) - if err := d.Set(names.AttrRule, flattenPlanRules(ctx, output.BackupPlan.Rules)); err != nil { + if err := d.Set(names.AttrRule, flattenBackupRules(ctx, output.BackupPlan.Rules)); err != nil { return sdkdiag.AppendErrorf(diags, "setting rule: %s", err) } d.Set(names.AttrVersion, output.VersionId) From 9a671fe8e22d2f514fa3505a214374d5ea4a2dee Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 07:48:17 -0400 Subject: [PATCH 05/14] flattenAdvancedBackupSettings: Return '[]interface{}'. --- internal/service/backup/plan.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index add9222738b..3f70ec74464 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -475,7 +475,7 @@ func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) * return schema.NewSet(planHash, tfList) } -func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) *schema.Set { // nosemgrep:ci.backup-in-func-name +func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) []interface{} { // nosemgrep:ci.backup-in-func-name tfList := []interface{}{} for _, apiObject := range apiObjects { @@ -487,7 +487,7 @@ func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) tfList = append(tfList, tfMap) } - return schema.NewSet(planHash, tfList) + return tfList } func flattenCopyActions(apiObjects []awstypes.CopyAction) []interface{} { From ef70d34b1e9d2901e3124436ecde453d823b140f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 08:26:26 -0400 Subject: [PATCH 06/14] r/aws_backup_plan: Default value for 'rule.schedule_expression_timezone'. --- internal/service/backup/plan.go | 145 ++++++----------------- internal/service/backup/plan_test.go | 107 ++++++++--------- website/docs/r/backup_plan.html.markdown | 2 +- 3 files changed, 92 insertions(+), 162 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 3f70ec74464..8768786c72c 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -4,9 +4,7 @@ package backup import ( - "bytes" "context" - "fmt" "log" "time" @@ -19,7 +17,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" @@ -160,7 +157,7 @@ func resourcePlan() *schema.Resource { "schedule_expression_timezone": { Type: schema.TypeString, Optional: true, - Computed: true, + Default: "Etc/UTC", }, "start_window": { Type: schema.TypeInt, @@ -177,7 +174,6 @@ func resourcePlan() *schema.Resource { }, }, }, - Set: planHash, }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), @@ -332,43 +328,37 @@ func expandBackupRuleInputs(ctx context.Context, tfList []interface{}) []awstype tfMap := tfMapRaw.(map[string]interface{}) apiObject := awstypes.BackupRuleInput{} - if vRuleName, ok := tfMap["rule_name"].(string); ok && vRuleName != "" { - apiObject.RuleName = aws.String(vRuleName) - } else { - continue - } - if vTargetVaultName, ok := tfMap["target_vault_name"].(string); ok && vTargetVaultName != "" { - apiObject.TargetBackupVaultName = aws.String(vTargetVaultName) + if v, ok := tfMap["completion_window"].(int); ok { + apiObject.CompletionWindowMinutes = aws.Int64(int64(v)) } - if vSchedule, ok := tfMap[names.AttrSchedule].(string); ok && vSchedule != "" { - apiObject.ScheduleExpression = aws.String(vSchedule) + if v := expandCopyActions(tfMap["copy_action"].(*schema.Set).List()); len(v) > 0 { + apiObject.CopyActions = v } - if vSchedule, ok := tfMap[names.AttrSchedule].(string); ok && vSchedule != "" { - apiObject.ScheduleExpression = aws.String(vSchedule) + if v, ok := tfMap["enable_continuous_backup"].(bool); ok { + apiObject.EnableContinuousBackup = aws.Bool(v) } - if v, ok := tfMap["schedule_expression_timezone"].(string); ok && v != "" { - apiObject.ScheduleExpressionTimezone = aws.String(v) + if v, ok := tfMap["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Lifecycle = expandLifecycle(v[0].(map[string]interface{})) } - if vEnableContinuousBackup, ok := tfMap["enable_continuous_backup"].(bool); ok { - apiObject.EnableContinuousBackup = aws.Bool(vEnableContinuousBackup) + if v, ok := tfMap["recovery_point_tags"].(map[string]interface{}); ok && len(v) > 0 { + apiObject.RecoveryPointTags = Tags(tftags.New(ctx, v).IgnoreAWS()) } - if vStartWindow, ok := tfMap["start_window"].(int); ok { - apiObject.StartWindowMinutes = aws.Int64(int64(vStartWindow)) + if v, ok := tfMap["rule_name"].(string); ok && v != "" { + apiObject.RuleName = aws.String(v) + } else { + continue } - if vCompletionWindow, ok := tfMap["completion_window"].(int); ok { - apiObject.CompletionWindowMinutes = aws.Int64(int64(vCompletionWindow)) + if v, ok := tfMap[names.AttrSchedule].(string); ok && v != "" { + apiObject.ScheduleExpression = aws.String(v) } - - if vRecoveryPointTags, ok := tfMap["recovery_point_tags"].(map[string]interface{}); ok && len(vRecoveryPointTags) > 0 { - apiObject.RecoveryPointTags = Tags(tftags.New(ctx, vRecoveryPointTags).IgnoreAWS()) + if v, ok := tfMap["schedule_expression_timezone"].(string); ok && v != "" { + apiObject.ScheduleExpressionTimezone = aws.String(v) } - - if v, ok := tfMap["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - apiObject.Lifecycle = expandLifecycle(v[0].(map[string]interface{})) + if v, ok := tfMap["start_window"].(int); ok { + apiObject.StartWindowMinutes = aws.Int64(int64(v)) } - - if vCopyActions := expandCopyActions(tfMap["copy_action"].(*schema.Set).List()); len(vCopyActions) > 0 { - apiObject.CopyActions = vCopyActions + if v, ok := tfMap["target_vault_name"].(string); ok && v != "" { + apiObject.TargetBackupVaultName = aws.String(v) } apiObjects = append(apiObjects, apiObject) @@ -448,31 +438,36 @@ func expandLifecycle(tfMap map[string]interface{}) *awstypes.Lifecycle { return apiObject } -func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) *schema.Set { // nosemgrep:ci.backup-in-func-name +func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) []interface{} { // nosemgrep:ci.backup-in-func-name tfList := []interface{}{} for _, apiObject := range apiObjects { tfMap := map[string]interface{}{ + "completion_window": aws.ToInt64(apiObject.CompletionWindowMinutes), + "enable_continuous_backup": aws.ToBool(apiObject.EnableContinuousBackup), "rule_name": aws.ToString(apiObject.RuleName), - "target_vault_name": aws.ToString(apiObject.TargetBackupVaultName), names.AttrSchedule: aws.ToString(apiObject.ScheduleExpression), "schedule_expression_timezone": aws.ToString(apiObject.ScheduleExpressionTimezone), - "enable_continuous_backup": aws.ToBool(apiObject.EnableContinuousBackup), "start_window": aws.ToInt64(apiObject.StartWindowMinutes), - "completion_window": aws.ToInt64(apiObject.CompletionWindowMinutes), - "recovery_point_tags": KeyValueTags(ctx, apiObject.RecoveryPointTags).IgnoreAWS().Map(), + "target_vault_name": aws.ToString(apiObject.TargetBackupVaultName), + } + + if v := apiObject.CopyActions; len(v) > 0 { + tfMap["copy_action"] = flattenCopyActions(v) } - if lifecycle := apiObject.Lifecycle; lifecycle != nil { - tfMap["lifecycle"] = flattenLifecycle(lifecycle) + if v := apiObject.Lifecycle; v != nil { + tfMap["lifecycle"] = flattenLifecycle(v) } - tfMap["copy_action"] = flattenCopyActions(apiObject.CopyActions) + if v := KeyValueTags(ctx, apiObject.RecoveryPointTags).IgnoreAWS().Map(); len(v) > 0 { + tfMap["recovery_point_tags"] = v + } tfList = append(tfList, tfMap) } - return schema.NewSet(planHash, tfList) + return tfList } func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) []interface{} { // nosemgrep:ci.backup-in-func-name @@ -525,69 +520,3 @@ func flattenLifecycle(apiObject *awstypes.Lifecycle) []interface{} { return []interface{}{tfMap} } - -func planHash(vRule interface{}) int { - var buf bytes.Buffer - - mRule := vRule.(map[string]interface{}) - - if v, ok := mRule["rule_name"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - if v, ok := mRule["target_vault_name"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - if v, ok := mRule[names.AttrSchedule].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - if v, ok := mRule["schedule_expression_timezone"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - if v, ok := mRule["enable_continuous_backup"].(bool); ok { - buf.WriteString(fmt.Sprintf("%t-", v)) - } - if v, ok := mRule["start_window"].(int); ok { - buf.WriteString(fmt.Sprintf("%d-", v)) - } - if v, ok := mRule["completion_window"].(int); ok { - buf.WriteString(fmt.Sprintf("%d-", v)) - } - - if vRecoveryPointTags, ok := mRule["recovery_point_tags"].(map[string]interface{}); ok && len(vRecoveryPointTags) > 0 { - buf.WriteString(fmt.Sprintf("%d-", tftags.New(context.Background(), vRecoveryPointTags).Hash())) - } - - if vLifecycle, ok := mRule["lifecycle"].([]interface{}); ok && len(vLifecycle) > 0 && vLifecycle[0] != nil { - mLifecycle := vLifecycle[0].(map[string]interface{}) - - if v, ok := mLifecycle["delete_after"].(int); ok { - buf.WriteString(fmt.Sprintf("%d-", v)) - } - if v, ok := mLifecycle["cold_storage_after"].(int); ok { - buf.WriteString(fmt.Sprintf("%d-", v)) - } - } - - if vCopyActions, ok := mRule["copy_action"].(*schema.Set); ok && vCopyActions.Len() > 0 { - for _, a := range vCopyActions.List() { - action := a.(map[string]interface{}) - if mLifecycle, ok := action["lifecycle"].([]interface{}); ok { - for _, l := range mLifecycle { - lifecycle := l.(map[string]interface{}) - if v, ok := lifecycle["delete_after"].(int); ok { - buf.WriteString(fmt.Sprintf("%d-", v)) - } - if v, ok := lifecycle["cold_storage_after"].(int); ok { - buf.WriteString(fmt.Sprintf("%d-", v)) - } - } - } - - if v, ok := action["destination_vault_arn"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } - } - } - - return create.StringHashcode(buf.String()) -} diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 62553981819..049cd8bb75f 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -12,7 +12,10 @@ import ( "github.com/aws/aws-sdk-go-v2/service/backup" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" @@ -59,7 +62,7 @@ func TestAccBackupPlan_basic(t *testing.T) { }) } -func TestAccBackupPlan_withTags(t *testing.T) { +func TestAccBackupPlan_disappears(t *testing.T) { ctx := acctest.Context(t) var plan backup.GetBackupPlanOutput resourceName := "aws_backup_plan.test" @@ -72,15 +75,38 @@ func TestAccBackupPlan_withTags(t *testing.T) { CheckDestroy: testAccCheckPlanDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccPlanConfig_tags(rName), + Config: testAccPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(ctx, resourceName, &plan), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourcePlan(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccBackupPlan_tags(t *testing.T) { + ctx := acctest.Context(t) + var plan backup.GetBackupPlanOutput + resourceName := "aws_backup_plan.test" + rName := fmt.Sprintf("tf-testacc-backup-%s", sdkacctest.RandString(14)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1), + })), + }, Check: resource.ComposeTestCheckFunc( testAccCheckPlanExists(ctx, resourceName, &plan), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct3), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), - resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), - resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2a"), ), }, { @@ -89,24 +115,26 @@ func TestAccBackupPlan_withTags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccPlanConfig_tagsUpdated(rName), + Config: testAccPlanConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated), + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, Check: resource.ComposeTestCheckFunc( testAccCheckPlanExists(ctx, resourceName, &plan), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct3), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), - resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2b"), - resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), ), }, { - Config: testAccPlanConfig_basic(rName), + Config: testAccPlanConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, Check: resource.ComposeTestCheckFunc( testAccCheckPlanExists(ctx, resourceName, &plan), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), ), }, }, @@ -652,30 +680,6 @@ func TestAccBackupPlan_enableContinuousBackup(t *testing.T) { }) } -func TestAccBackupPlan_disappears(t *testing.T) { - ctx := acctest.Context(t) - var plan backup.GetBackupPlanOutput - resourceName := "aws_backup_plan.test" - rName := fmt.Sprintf("tf-testacc-backup-%s", sdkacctest.RandString(14)) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPlanDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccPlanConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckPlanExists(ctx, resourceName, &plan), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourcePlan(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - func testAccCheckPlanDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) @@ -763,7 +767,7 @@ resource "aws_backup_plan" "test" { `, rName) } -func testAccPlanConfig_tags(rName string) string { +func testAccPlanConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_backup_vault" "test" { name = %[1]q @@ -779,15 +783,13 @@ resource "aws_backup_plan" "test" { } tags = { - Name = %[1]q - Key1 = "Value1" - Key2 = "Value2a" + %[2]q = %[3]q } } -`, rName) +`, rName, tagKey1, tagValue1) } -func testAccPlanConfig_tagsUpdated(rName string) string { +func testAccPlanConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_backup_vault" "test" { name = %[1]q @@ -803,12 +805,11 @@ resource "aws_backup_plan" "test" { } tags = { - Name = %[1]q - Key2 = "Value2b" - Key3 = "Value3" + %[2]q = %[3]q + %[4]q = %[5]q } } -`, rName) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } func testAccPlanConfig_twoRules(rName string) string { diff --git a/website/docs/r/backup_plan.html.markdown b/website/docs/r/backup_plan.html.markdown index 7ecfe8d326d..13c7083d9c9 100644 --- a/website/docs/r/backup_plan.html.markdown +++ b/website/docs/r/backup_plan.html.markdown @@ -51,7 +51,7 @@ This resource supports the following arguments: * `rule_name` - (Required) An display name for a backup rule. * `target_vault_name` - (Required) The name of a logical container where backups are stored. * `schedule` - (Optional) A CRON expression specifying when AWS Backup initiates a backup job. -* `schedule_expression_timezone` - (Optional) The timezone in which the schedule expression is set. By default, ScheduleExpressions are in UTC. +* `schedule_expression_timezone` - (Optional) The timezone in which the schedule expression is set. Default value: `"Etc/UTC"`. * `enable_continuous_backup` - (Optional) Enable continuous backups for supported resources. * `start_window` - (Optional) The amount of time in minutes before beginning a backup. * `completion_window` - (Optional) The amount of time in minutes AWS Backup attempts a backup before canceling the job and returning an error. From 0b4391315d894d040a6d1de46dbec119d07e66eb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 12:58:23 -0400 Subject: [PATCH 07/14] Add 'TestAccBackupPlan_upgradeScheduleExpressionTimezone'. --- internal/service/backup/plan_test.go | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 049cd8bb75f..716b75ba8b5 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -680,6 +680,38 @@ func TestAccBackupPlan_enableContinuousBackup(t *testing.T) { }) } +func TestAccBackupPlan_upgradeScheduleExpressionTimezone(t *testing.T) { + ctx := acctest.Context(t) + var plan backup.GetBackupPlanOutput + resourceName := "aws_backup_plan.test" + rName := fmt.Sprintf("tf-testacc-backup-%s", sdkacctest.RandString(14)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + CheckDestroy: testAccCheckPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: "5.70.0", + }, + }, + Config: testAccPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(ctx, resourceName, &plan), + ), + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: testAccPlanConfig_basic(rName), + PlanOnly: true, + }, + }, + }) +} + func testAccCheckPlanDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) From a25f67a036067eb831019a989bb7b66b1599a0d1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 12:59:24 -0400 Subject: [PATCH 08/14] r/aws_backup_plan: Restore SetFunc for 'rule'. --- internal/service/backup/plan.go | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 8768786c72c..14f0b51a850 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -6,6 +6,8 @@ package backup import ( "context" "log" + "strconv" + "strings" "time" "github.com/YakDriver/regexache" @@ -17,9 +19,11 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -157,7 +161,7 @@ func resourcePlan() *schema.Resource { "schedule_expression_timezone": { Type: schema.TypeString, Optional: true, - Default: "Etc/UTC", + Computed: true, }, "start_window": { Type: schema.TypeInt, @@ -174,6 +178,7 @@ func resourcePlan() *schema.Resource { }, }, }, + Set: backupRuleSetFunc, }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), @@ -438,7 +443,7 @@ func expandLifecycle(tfMap map[string]interface{}) *awstypes.Lifecycle { return apiObject } -func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) []interface{} { // nosemgrep:ci.backup-in-func-name +func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) *schema.Set { // nosemgrep:ci.backup-in-func-name tfList := []interface{}{} for _, apiObject := range apiObjects { @@ -467,7 +472,7 @@ func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) [ tfList = append(tfList, tfMap) } - return tfList + return schema.NewSet(backupRuleSetFunc, tfList) } func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) []interface{} { // nosemgrep:ci.backup-in-func-name @@ -520,3 +525,46 @@ func flattenLifecycle(apiObject *awstypes.Lifecycle) []interface{} { return []interface{}{tfMap} } + +func backupRuleSetFunc(v interface{}) int { // nosemgrep:ci.backup-in-func-name + tfMap := v.(map[string]interface{}) + const ( + defaultScheduleExpressionTimezone = "Etc/UTC" + ) + simpleAttributesSetFunc := sdkv2.SimpleSchemaSetFunc("completion_window", "enable_continuous_backup", "rule_name", names.AttrSchedule, "start_window", "target_vault_name") + lifecycleSetFunc := sdkv2.SimpleSchemaSetFunc("cold_storage_after", "delete_after") + var str strings.Builder + + str.WriteRune('-') + str.WriteString(strconv.Itoa(simpleAttributesSetFunc(tfMap))) + if v, ok := tfMap["schedule_expression_timezone"].(string); ok && v != defaultScheduleExpressionTimezone { + str.WriteRune('-') + str.WriteString(v) + } + if v, ok := tfMap["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + str.WriteRune('-') + str.WriteString(strconv.Itoa(lifecycleSetFunc(v[0]))) + } + if v, ok := tfMap["recovery_point_tags"].(map[string]interface{}); ok && len(v) > 0 { + str.WriteRune('-') + str.WriteString(strconv.Itoa(tftags.New(context.Background(), v).Hash())) + } + if v, ok := tfMap["copy_action"].(*schema.Set); ok && v.Len() > 0 { + for _, tfMapRaw := range v.List() { + tfMap := tfMapRaw.(map[string]interface{}) + if v, ok := tfMap["lifecycle"].([]interface{}); ok { + for _, tfMapRaw := range v { + str.WriteRune('-') + str.WriteString(strconv.Itoa(lifecycleSetFunc(tfMapRaw))) + } + } + + if v, ok := tfMap["destination_vault_arn"].(string); ok { + str.WriteRune('-') + str.WriteString(v) + } + } + } + + return create.StringHashcode(str.String()) +} From 6466adc32188b62723e15434d13e5156400b2b64 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 14:32:00 -0400 Subject: [PATCH 09/14] Revert "r/aws_backup_plan: Restore SetFunc for 'rule'." This reverts commit a25f67a036067eb831019a989bb7b66b1599a0d1. --- internal/service/backup/plan.go | 54 ++------------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 14f0b51a850..8768786c72c 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -6,8 +6,6 @@ package backup import ( "context" "log" - "strconv" - "strings" "time" "github.com/YakDriver/regexache" @@ -19,11 +17,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" - "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -161,7 +157,7 @@ func resourcePlan() *schema.Resource { "schedule_expression_timezone": { Type: schema.TypeString, Optional: true, - Computed: true, + Default: "Etc/UTC", }, "start_window": { Type: schema.TypeInt, @@ -178,7 +174,6 @@ func resourcePlan() *schema.Resource { }, }, }, - Set: backupRuleSetFunc, }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), @@ -443,7 +438,7 @@ func expandLifecycle(tfMap map[string]interface{}) *awstypes.Lifecycle { return apiObject } -func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) *schema.Set { // nosemgrep:ci.backup-in-func-name +func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) []interface{} { // nosemgrep:ci.backup-in-func-name tfList := []interface{}{} for _, apiObject := range apiObjects { @@ -472,7 +467,7 @@ func flattenBackupRules(ctx context.Context, apiObjects []awstypes.BackupRule) * tfList = append(tfList, tfMap) } - return schema.NewSet(backupRuleSetFunc, tfList) + return tfList } func flattenAdvancedBackupSettings(apiObjects []awstypes.AdvancedBackupSetting) []interface{} { // nosemgrep:ci.backup-in-func-name @@ -525,46 +520,3 @@ func flattenLifecycle(apiObject *awstypes.Lifecycle) []interface{} { return []interface{}{tfMap} } - -func backupRuleSetFunc(v interface{}) int { // nosemgrep:ci.backup-in-func-name - tfMap := v.(map[string]interface{}) - const ( - defaultScheduleExpressionTimezone = "Etc/UTC" - ) - simpleAttributesSetFunc := sdkv2.SimpleSchemaSetFunc("completion_window", "enable_continuous_backup", "rule_name", names.AttrSchedule, "start_window", "target_vault_name") - lifecycleSetFunc := sdkv2.SimpleSchemaSetFunc("cold_storage_after", "delete_after") - var str strings.Builder - - str.WriteRune('-') - str.WriteString(strconv.Itoa(simpleAttributesSetFunc(tfMap))) - if v, ok := tfMap["schedule_expression_timezone"].(string); ok && v != defaultScheduleExpressionTimezone { - str.WriteRune('-') - str.WriteString(v) - } - if v, ok := tfMap["lifecycle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - str.WriteRune('-') - str.WriteString(strconv.Itoa(lifecycleSetFunc(v[0]))) - } - if v, ok := tfMap["recovery_point_tags"].(map[string]interface{}); ok && len(v) > 0 { - str.WriteRune('-') - str.WriteString(strconv.Itoa(tftags.New(context.Background(), v).Hash())) - } - if v, ok := tfMap["copy_action"].(*schema.Set); ok && v.Len() > 0 { - for _, tfMapRaw := range v.List() { - tfMap := tfMapRaw.(map[string]interface{}) - if v, ok := tfMap["lifecycle"].([]interface{}); ok { - for _, tfMapRaw := range v { - str.WriteRune('-') - str.WriteString(strconv.Itoa(lifecycleSetFunc(tfMapRaw))) - } - } - - if v, ok := tfMap["destination_vault_arn"].(string); ok { - str.WriteRune('-') - str.WriteString(v) - } - } - } - - return create.StringHashcode(str.String()) -} From 007e60c1bbeaeec90ea585f46b1cb4318ee36157 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 15:05:03 -0400 Subject: [PATCH 10/14] r/aws_backup_plan: Add StateUpgraders. --- internal/service/backup/plan.go | 15 ++- internal/service/backup/plan_migrate.go | 157 ++++++++++++++++++++++++ internal/service/backup/plan_test.go | 4 +- 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 internal/service/backup/plan_migrate.go diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 8768786c72c..cd3154ffca2 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -26,6 +26,10 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) +const ( + defaultPlanRuleScheduleExpressionTimezone = "Etc/UTC" +) + // @SDKResource("aws_backup_plan", name="Plan") // @Tags(identifierAttribute="arn") func resourcePlan() *schema.Resource { @@ -39,6 +43,15 @@ func resourcePlan() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Type: planResourceV0().CoreConfigSchema().ImpliedType(), + Upgrade: planStateUpgradeV0, + Version: 0, + }, + }, + Schema: map[string]*schema.Schema{ "advanced_backup_setting": { Type: schema.TypeSet, @@ -157,7 +170,7 @@ func resourcePlan() *schema.Resource { "schedule_expression_timezone": { Type: schema.TypeString, Optional: true, - Default: "Etc/UTC", + Default: defaultPlanRuleScheduleExpressionTimezone, }, "start_window": { Type: schema.TypeInt, diff --git a/internal/service/backup/plan_migrate.go b/internal/service/backup/plan_migrate.go new file mode 100644 index 00000000000..35dfd5f8d54 --- /dev/null +++ b/internal/service/backup/plan_migrate.go @@ -0,0 +1,157 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func planStateUpgradeV0(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + var tfList []interface{} + if v, ok := rawState[names.AttrRule].([]interface{}); ok { + for _, tfMapRaw := range v { + tfMap := tfMapRaw.(map[string]interface{}) + tfMap["schedule_expression_timezone"] = defaultPlanRuleScheduleExpressionTimezone + tfList = append(tfList, tfMap) + } + rawState[names.AttrRule] = tfList + } + + return rawState, nil +} + +func planResourceV0() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "advanced_backup_setting": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "backup_options": { + Type: schema.TypeMap, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + names.AttrResourceType: { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + names.AttrARN: { + Type: schema.TypeString, + Computed: true, + }, + names.AttrName: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + names.AttrRule: { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "completion_window": { + Type: schema.TypeInt, + Optional: true, + Default: 180, + }, + "copy_action": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination_vault_arn": { + Type: schema.TypeString, + Required: true, + }, + "lifecycle": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cold_storage_after": { + Type: schema.TypeInt, + Optional: true, + }, + "delete_after": { + Type: schema.TypeInt, + Optional: true, + }, + "opt_in_to_archive_for_supported_resources": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "enable_continuous_backup": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "lifecycle": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cold_storage_after": { + Type: schema.TypeInt, + Optional: true, + }, + "delete_after": { + Type: schema.TypeInt, + Optional: true, + }, + "opt_in_to_archive_for_supported_resources": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + "recovery_point_tags": tftags.TagsSchema(), + "rule_name": { + Type: schema.TypeString, + Required: true, + }, + names.AttrSchedule: { + Type: schema.TypeString, + Optional: true, + }, + "start_window": { + Type: schema.TypeInt, + Optional: true, + Default: 60, + }, + "target_vault_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + names.AttrVersion: { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 716b75ba8b5..5906dabd159 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -706,7 +706,9 @@ func TestAccBackupPlan_upgradeScheduleExpressionTimezone(t *testing.T) { { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Config: testAccPlanConfig_basic(rName), - PlanOnly: true, + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(ctx, resourceName, &plan), + ), }, }, }) From cd6a5d1c7dd0c3b72ce82f81f99b7d1f379c1990 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 15:17:44 -0400 Subject: [PATCH 11/14] Add 'TestAccBackupPlan_scheduleExpressionTimezone'. --- internal/service/backup/plan_test.go | 68 ++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 5906dabd159..f86a71d76b8 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -714,6 +714,55 @@ func TestAccBackupPlan_upgradeScheduleExpressionTimezone(t *testing.T) { }) } +func TestAccBackupPlan_scheduleExpressionTimezone(t *testing.T) { + ctx := acctest.Context(t) + var plan backup.GetBackupPlanOutput + resourceName := "aws_backup_plan.test" + rName := fmt.Sprintf("tf-testacc-backup-%s", sdkacctest.RandString(14)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccPlanConfig_scheduleExpressionTimezone(rName, "Pacific/Tahiti"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(ctx, resourceName, &plan), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, acctest.Ct1), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "lifecycle.#": acctest.Ct0, + "rule_name": rName, + names.AttrSchedule: "cron(0 12 * * ? *)", + "schedule_expression_timezone": "Pacific/Tahiti", + "target_vault_name": rName, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPlanConfig_scheduleExpressionTimezone(rName, "Africa/Abidjan"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPlanExists(ctx, resourceName, &plan), + resource.TestCheckResourceAttr(resourceName, acctest.CtRulePound, acctest.Ct1), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "lifecycle.#": acctest.Ct0, + "rule_name": rName, + names.AttrSchedule: "cron(0 12 * * ? *)", + "schedule_expression_timezone": "Africa/Abidjan", + "target_vault_name": rName, + }), + ), + }, + }, + }) +} + func testAccCheckPlanDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) @@ -1244,3 +1293,22 @@ resource "aws_backup_plan" "test" { } `, rName) } + +func testAccPlanConfig_scheduleExpressionTimezone(rName, scheduleExpressionTimezone string) string { + return fmt.Sprintf(` +resource "aws_backup_vault" "test" { + name = %[1]q +} + +resource "aws_backup_plan" "test" { + name = %[1]q + + rule { + rule_name = %[1]q + target_vault_name = aws_backup_vault.test.name + schedule = "cron(0 12 * * ? *)" + schedule_expression_timezone = %[2]q + } +} +`, rName, scheduleExpressionTimezone) +} From ff0f5d9253175f08511c6dea56be5c20311e0df9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 15:35:02 -0400 Subject: [PATCH 12/14] r/aws_backup_plan: Add sweeper. --- internal/service/backup/sweep.go | 56 ++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index c1f8e404570..d5943664f36 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -19,12 +19,17 @@ import ( func RegisterSweepers() { resource.AddTestSweepers("aws_backup_framework", &resource.Sweeper{ Name: "aws_backup_framework", - F: sweepFramework, + F: sweepFrameworks, + }) + + resource.AddTestSweepers("aws_backup_plan", &resource.Sweeper{ + Name: "aws_backup_plan", + F: sweepPlans, }) resource.AddTestSweepers("aws_backup_report_plan", &resource.Sweeper{ Name: "aws_backup_report_plan", - F: sweepReportPlan, + F: sweepReportPlans, }) resource.AddTestSweepers("aws_backup_restore_testing_plan", &resource.Sweeper{ @@ -42,7 +47,7 @@ func RegisterSweepers() { resource.AddTestSweepers("aws_backup_vault_lock_configuration", &resource.Sweeper{ Name: "aws_backup_vault_lock_configuration", - F: sweepVaultLockConfiguration, + F: sweepVaultLockConfigurations, }) resource.AddTestSweepers("aws_backup_vault_notifications", &resource.Sweeper{ @@ -66,7 +71,7 @@ func RegisterSweepers() { }) } -func sweepFramework(region string) error { +func sweepFrameworks(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) if err != nil { @@ -105,7 +110,46 @@ func sweepFramework(region string) error { return nil } -func sweepReportPlan(region string) error { +func sweepPlans(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.BackupClient(ctx) + input := &backup.ListBackupPlansInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := backup.NewListBackupPlansPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Backup Plan sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Backup Plans (%s): %w", region, err) + } + + for _, v := range page.BackupPlansList { + r := resourcePlan() + d := r.Data(nil) + d.SetId(aws.ToString(v.BackupPlanId)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + } + + if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { + return fmt.Errorf("error sweeping Backup Plans (%s): %w", region, err) + } + + return nil +} + +func sweepReportPlans(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) if err != nil { @@ -233,7 +277,7 @@ func sweepRestoreTestingSelections(region string) error { return nil } -func sweepVaultLockConfiguration(region string) error { +func sweepVaultLockConfigurations(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) if err != nil { From 9d51aa859885a0bc22b3f7556ba46cdd1de1e43c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 15:41:19 -0400 Subject: [PATCH 13/14] r/aws_backup_selection: Add sweeper. --- internal/service/backup/sweep.go | 80 +++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index d5943664f36..3b0e6790ff3 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -6,6 +6,7 @@ package backup import ( "fmt" "log" + "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/backup" @@ -25,6 +26,14 @@ func RegisterSweepers() { resource.AddTestSweepers("aws_backup_plan", &resource.Sweeper{ Name: "aws_backup_plan", F: sweepPlans, + Dependencies: []string{ + "aws_backup_selection", + }, + }) + + resource.AddTestSweepers("aws_backup_selection", &resource.Sweeper{ + Name: "aws_backup_selection", + F: sweepSelections, }) resource.AddTestSweepers("aws_backup_report_plan", &resource.Sweeper{ @@ -134,9 +143,16 @@ func sweepPlans(region string) error { } for _, v := range page.BackupPlansList { + planID := aws.ToString(v.BackupPlanId) + + if strings.HasPrefix(planID, "aws/") { + log.Printf("[INFO] Skipping Backup Plan: %s", planID) + continue + } + r := resourcePlan() d := r.Data(nil) - d.SetId(aws.ToString(v.BackupPlanId)) + d.SetId(planID) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -149,6 +165,68 @@ func sweepPlans(region string) error { return nil } +func sweepSelections(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.BackupClient(ctx) + input := &backup.ListBackupPlansInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := backup.NewListBackupPlansPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Backup Selection sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Backup Plans (%s): %w", region, err) + } + + for _, v := range page.BackupPlansList { + planID := aws.ToString(v.BackupPlanId) + + if strings.HasPrefix(planID, "aws/") { + log.Printf("[INFO] Skipping Backup Plan: %s", planID) + continue + } + + input := &backup.ListBackupSelectionsInput{ + BackupPlanId: aws.String(planID), + } + + pages := backup.NewListBackupSelectionsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + continue + } + + for _, v := range page.BackupSelectionsList { + r := resourceSelection() + d := r.Data(nil) + d.SetId(aws.ToString(v.SelectionId)) + d.Set("plan_id", planID) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + } + } + } + + if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { + return fmt.Errorf("error sweeping Backup Selections (%s): %w", region, err) + } + + return nil +} + func sweepReportPlans(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) From f592105d7b8fd66c27d25f70a290be8da5e130c4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 7 Oct 2024 15:55:13 -0400 Subject: [PATCH 14/14] d/aws_backup_plan: Add `rule.schedule_expression_timezone` attribute. --- .changelog/33653.txt | 5 ++++- internal/service/backup/plan_data_source.go | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.changelog/33653.txt b/.changelog/33653.txt index 0cd32d40ee5..fc7bac5d707 100644 --- a/.changelog/33653.txt +++ b/.changelog/33653.txt @@ -1,4 +1,7 @@ - ```release-note:enhancement resource/aws_backup_plan: Add `rule.schedule_expression_timezone` argument +``` + +```release-note:enhancement +data-source/aws_backup_plan: Add `rule.schedule_expression_timezone` attribute ``` \ No newline at end of file diff --git a/internal/service/backup/plan_data_source.go b/internal/service/backup/plan_data_source.go index afbfb579f0b..c9e5662de55 100644 --- a/internal/service/backup/plan_data_source.go +++ b/internal/service/backup/plan_data_source.go @@ -107,6 +107,10 @@ func dataSourcePlan() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "schedule_expression_timezone": { + Type: schema.TypeString, + Computed: true, + }, "start_window": { Type: schema.TypeInt, Computed: true,