diff --git a/internal/service/backup/consts.go b/internal/service/backup/consts.go index 573f338a20d..a17ee9b08bb 100644 --- a/internal/service/backup/consts.go +++ b/internal/service/backup/consts.go @@ -53,3 +53,27 @@ func reportSettingTemplate_Values() []string { reportSettingTemplateRestoreJobReport, } } + +const ( + restoreTestingRecoveryPointSelectionAlgorithmLatestWithinWindow = "LATEST_WITHIN_WINDOW" + restoreTestingRecoveryPointSelectionAlgorithmRandomWithinWindow = "RANDOM_WITHIN_WINDOW" +) + +func restoreTestingRecoveryPointSelectionAlgorithm_Values() []string { + return []string{ + restoreTestingRecoveryPointSelectionAlgorithmLatestWithinWindow, + restoreTestingRecoveryPointSelectionAlgorithmRandomWithinWindow, + } +} + +const ( + restoreTestingRecoveryPointTypeContinuous = "CONTINUOUS" + restoreTestingRecoveryPointTypeSnapshot = "SNAPSHOT" +) + +func restoreTestingRecoveryPointType_Values() []string { + return []string{ + restoreTestingRecoveryPointTypeContinuous, + restoreTestingRecoveryPointTypeSnapshot, + } +} diff --git a/internal/service/backup/restore_testing_plan.go b/internal/service/backup/restore_testing_plan.go new file mode 100644 index 00000000000..9c06f684a56 --- /dev/null +++ b/internal/service/backup/restore_testing_plan.go @@ -0,0 +1,281 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/backup" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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/flex" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_backup_restore_testing_plan", name="RestoreTestingPlan") +// @Tags(identifierAttribute="arn") +func ResourceRestoreTestingPlan() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceRestoreTestingPlanCreate, + ReadWithoutTimeout: resourceRestoreTestingPlanRead, + UpdateWithoutTimeout: resourceRestoreTestingPlanUpdate, + DeleteWithoutTimeout: resourceRestoreTestingPlanDelete, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.Set("name", d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "schedule": { + Type: schema.TypeString, + Required: true, + }, + "schedule_timezone": { + Type: schema.TypeString, + Optional: true, + Default: "UTC", + }, + "start_window": { + Type: schema.TypeInt, + Optional: true, + Default: 24, + ValidateFunc: validation.IntBetween(1, 168), + }, + "recovery_point_selection": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "algorithm": { + Type: schema.TypeString, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(restoreTestingRecoveryPointSelectionAlgorithm_Values(), false), + }, + }, + "exclude_vaults": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "include_vaults": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "recovery_point_types": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(restoreTestingRecoveryPointType_Values(), false), + }, + }, + "selection_window": { + Type: schema.TypeInt, + Optional: true, + Default: 30, + ValidateFunc: validation.IntBetween(1, 365), + }, + }, + }, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + "version": { + Type: schema.TypeString, + Computed: true, + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceRestoreTestingPlanCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).BackupConn(ctx) + + name := d.Get("name").(string) + + input := &backup.CreateRestoreTestingPlanInput{ + RestoreTestingPlan: &backup.RestoreTestingPlanForCreate{ + RestoreTestingPlanName: aws.String(name), + ScheduleExpression: aws.String(d.Get("schedule").(string)), + ScheduleExpressionTimezone: aws.String(d.Get("schedule_timezone").(string)), + StartWindowHours: aws.Int64(int64(d.Get("start_window").(int))), + }, + Tags: getTagsIn(ctx), + } + + if v, ok := d.GetOk("recovery_point_selection"); ok && len(v.([]interface{})) > 0 { + input.RestoreTestingPlan.RecoveryPointSelection = expandRecoveryPointSelection(d.Get("recovery_point_selection").([]interface{})) + } + + _, err := conn.CreateRestoreTestingPlanWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating Restore Testing Plan (%s): %s", name, err) + } + + d.SetId(name) + + return append(diags, resourceRestoreTestingPlanRead(ctx, d, meta)...) +} + +func resourceRestoreTestingPlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).BackupConn(ctx) + + output, err := FindRestoreTestingPlanByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Restore Testing Plan (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Restore Testing Plan (%s): %s", d.Id(), err) + } + + if err := d.Set("recovery_point_selection", flattenRestoreTestingPlanRecoveryPointSelection(output.RestoreTestingPlan.RecoveryPointSelection)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting recovery_point_selection: %s", err) + } + + d.Set("arn", output.RestoreTestingPlan.RestoreTestingPlanArn) + d.Set("name", output.RestoreTestingPlan.RestoreTestingPlanName) + d.Set("schedule", output.RestoreTestingPlan.ScheduleExpression) + d.Set("schedule_timezone", output.RestoreTestingPlan.ScheduleExpressionTimezone) + d.Set("start_window", output.RestoreTestingPlan.StartWindowHours) + + return diags +} + +func FindRestoreTestingPlanByID(ctx context.Context, conn *backup.Backup, id string) (*backup.GetRestoreTestingPlanOutput, error) { + input := &backup.GetRestoreTestingPlanInput{ + RestoreTestingPlanName: aws.String(id), + } + + output, err := conn.GetRestoreTestingPlanWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, backup.ErrCodeResourceNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.RestoreTestingPlan == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func resourceRestoreTestingPlanUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).BackupConn(ctx) + + if d.HasChanges("schedule", "schedule_timezone", "start_window", "recovery_point_selection") { + input := &backup.UpdateRestoreTestingPlanInput{ + RestoreTestingPlanName: aws.String(d.Id()), + RestoreTestingPlan: &backup.RestoreTestingPlanForUpdate{ + RecoveryPointSelection: expandRecoveryPointSelection(d.Get("recovery_point_selection").([]interface{})), + ScheduleExpression: aws.String(d.Get("schedule").(string)), + ScheduleExpressionTimezone: aws.String(d.Get("schedule_timezone").(string)), + StartWindowHours: aws.Int64(int64(d.Get("start_window").(int))), + }, + } + + _, err := conn.UpdateRestoreTestingPlanWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Restore Testing Plan (%s): %s", d.Id(), err) + } + } + + return append(diags, resourceRestoreTestingPlanRead(ctx, d, meta)...) +} + +func resourceRestoreTestingPlanDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).BackupConn(ctx) + + log.Printf("[DEBUG] Deleting Restore Testing Plan: %s", d.Id()) + const ( + timeout = 2 * time.Minute + ) + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, timeout, func() (interface{}, error) { + return conn.DeleteRestoreTestingPlanWithContext(ctx, &backup.DeleteRestoreTestingPlanInput{ + RestoreTestingPlanName: aws.String(d.Id()), + }) + }, backup.ErrCodeInvalidRequestException, "Related recovery point selections must be deleted prior to restore testing plan") + + if tfawserr.ErrCodeEquals(err, backup.ErrCodeResourceNotFoundException) { + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Restore Testing Plan (%s): %s", d.Id(), err) + } + + return diags +} + +func expandRecoveryPointSelection(recoveryPointSelectionMaps []interface{}) *backup.RestoreTestingRecoveryPointSelection { + if len(recoveryPointSelectionMaps) == 0 { + return nil + } + + recoveryPointSelectionMap := recoveryPointSelectionMaps[0].(map[string]interface{}) + + return &backup.RestoreTestingRecoveryPointSelection{ + Algorithm: aws.String(recoveryPointSelectionMap["algorithm"].(string)), + ExcludeVaults: flex.ExpandStringSet(recoveryPointSelectionMap["exclude_vaults"].(*schema.Set)), + IncludeVaults: flex.ExpandStringSet(recoveryPointSelectionMap["include_vaults"].(*schema.Set)), + RecoveryPointTypes: flex.ExpandStringSet(recoveryPointSelectionMap["recovery_point_types"].(*schema.Set)), + SelectionWindowDays: aws.Int64(int64(recoveryPointSelectionMap["selection_window"].(int))), + } +} + +func flattenRestoreTestingPlanRecoveryPointSelection(recoveryPointSelection *backup.RestoreTestingRecoveryPointSelection) []map[string]interface{} { + vRecoveryPointSelection := make(map[string]interface{}) + + vRecoveryPointSelection["algorithm"] = aws.StringValue(recoveryPointSelection.Algorithm) + vRecoveryPointSelection["exclude_vaults"] = aws.StringValueSlice(recoveryPointSelection.ExcludeVaults) + vRecoveryPointSelection["include_vaults"] = aws.StringValueSlice(recoveryPointSelection.IncludeVaults) + vRecoveryPointSelection["recovery_point_types"] = aws.StringValueSlice(recoveryPointSelection.RecoveryPointTypes) + vRecoveryPointSelection["selection_window"] = aws.Int64Value(recoveryPointSelection.SelectionWindowDays) + + return []map[string]interface{}{vRecoveryPointSelection} +} diff --git a/internal/service/backup/restore_testing_plan_test.go b/internal/service/backup/restore_testing_plan_test.go new file mode 100644 index 00000000000..ba3ecded62c --- /dev/null +++ b/internal/service/backup/restore_testing_plan_test.go @@ -0,0 +1,432 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup_test + +import ( + "context" + "fmt" + "github.com/YakDriver/regexache" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "testing" + + "github.com/aws/aws-sdk-go/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/terraform" + "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" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccRestoreTestingPlan_basic(t *testing.T) { + ctx := acctest.Context(t) + var restoreTestingPlan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_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: testAccCheckRestoreTestingPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "backup", regexache.MustCompile(`restore-testing-plan:.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "RANDOM_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.include_vaults.*", "*"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "CONTINUOUS"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "SNAPSHOT"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRestoreTestingPlan_disappears(t *testing.T) { + ctx := acctest.Context(t) + var restoreTestingPlan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_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: testAccCheckRestoreTestingPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceRestoreTestingPlan(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccRestoreTestingPlan_withTags(t *testing.T) { + ctx := acctest.Context(t) + var restoreTestingPlan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := fmt.Sprintf("tf_testacc_backup_%s", sdkacctest.RandString(14)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRestoreTestingPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingPlanConfig_tags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2a"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRestoreTestingPlanConfig_tagsUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2b"), + resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + ), + }, + { + Config: testAccRestoreTestingPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccRestoreTestingPlan_updateRecoveryPointSelection(t *testing.T) { + ctx := acctest.Context(t) + var restoreTestingPlan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := fmt.Sprintf("tf_testacc_backup_%s", sdkacctest.RandString(14)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRestoreTestingPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "backup", regexache.MustCompile(`restore-testing-plan:.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "RANDOM_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.include_vaults.*", "*"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "CONTINUOUS"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "SNAPSHOT"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRestoreTestingPlanConfig_recoveryPointSelectionUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "backup", regexache.MustCompile(`restore-testing-plan:.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "LATEST_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "SNAPSHOT"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + Config: testAccRestoreTestingPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "RANDOM_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccRestoreTestingPlan_withOptionalArguments(t *testing.T) { + ctx := acctest.Context(t) + var restoreTestingPlan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := fmt.Sprintf("tf_testacc_backup_%s", sdkacctest.RandString(14)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRestoreTestingPlanDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingPlanConfig_optionalArguments(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "backup", regexache.MustCompile(`restore-testing-plan:.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "schedule_timezone", "EST5EDT"), + resource.TestCheckResourceAttr(resourceName, "start_window", "168"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "RANDOM_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "CONTINUOUS"), + resource.TestCheckTypeSetElemAttr(resourceName, "recovery_point_selection.0.recovery_point_types.*", "SNAPSHOT"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window", "365"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRestoreTestingPlanConfig_optionalArgumentsUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "backup", regexache.MustCompile(`restore-testing-plan:.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "schedule_timezone", "PST8PDT"), + resource.TestCheckResourceAttr(resourceName, "start_window", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "RANDOM_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + Config: testAccRestoreTestingPlanConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoreTestingPlan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule", "cron(0 12 * * ? *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "RANDOM_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func testAccCheckRestoreTestingPlanDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupConn(ctx) + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_backup_plan" { + continue + } + + _, err := tfbackup.FindRestoreTestingPlanByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Restore Testing Plan %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckRestoreTestingPlanExists(ctx context.Context, n string, v *backup.GetRestoreTestingPlanOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupConn(ctx) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + output, err := tfbackup.FindRestoreTestingPlanByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccRestoreTestingPlanConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q + schedule = "cron(0 12 * * ? *)" + recovery_point_selection { + algorithm = "RANDOM_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + } +} +`, rName) +} + +func testAccRestoreTestingPlanConfig_tags(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q + schedule = "cron(0 12 * * ? *)" + recovery_point_selection { + algorithm = "RANDOM_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + } + + tags = { + Name = %[1]q + Key1 = "Value1" + Key2 = "Value2a" + } +} +`, rName) +} + +func testAccRestoreTestingPlanConfig_tagsUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q + schedule = "cron(0 12 * * ? *)" + recovery_point_selection { + algorithm = "RANDOM_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + } + + tags = { + Name = %[1]q + Key2 = "Value2b" + Key3 = "Value3" + } +} +`, rName) +} + +func testAccRestoreTestingPlanConfig_recoveryPointSelectionUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q + schedule = "cron(0 12 * * ? *)" + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["SNAPSHOT"] + } +} +`, rName) +} + +func testAccRestoreTestingConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_vault" "test_one" { + name = "%[1]s_1" +} + +resource "aws_backup_vault" "test_two" { + name = "%[1]s_2" +} +`, rName) +} + +func testAccRestoreTestingPlanConfig_optionalArguments(rName string) string { + return acctest.ConfigCompose( + testAccRestoreTestingConfig_base(rName), + fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q + schedule = "cron(0 12 * * ? *)" + schedule_timezone = "EST5EDT" + start_window = 168 + recovery_point_selection { + algorithm = "RANDOM_WITHIN_WINDOW" + include_vaults = [aws_backup_vault.test_one.arn] + exclude_vaults = [aws_backup_vault.test_two.arn] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + selection_window = 365 + } +} +`, rName)) +} + +func testAccRestoreTestingPlanConfig_optionalArgumentsUpdated(rName string) string { + return acctest.ConfigCompose( + testAccRestoreTestingConfig_base(rName), + fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q + schedule = "cron(0 12 * * ? *)" + schedule_timezone = "PST8PDT" + start_window = 1 + recovery_point_selection { + algorithm = "RANDOM_WITHIN_WINDOW" + include_vaults = [aws_backup_vault.test_one.arn, aws_backup_vault.test_two.arn] + exclude_vaults = [] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + selection_window = 1 + } +} +`, rName)) +} diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 0e8ed95b223..7e6952d3884 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -82,6 +82,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "arn", }, }, + { + Factory: ResourceRestoreTestingPlan, + TypeName: "aws_backup_restore_testing_plan", + Name: "RestoreTestingPlan", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, { Factory: ResourceSelection, TypeName: "aws_backup_selection", diff --git a/website/docs/r/backup_restore_testing_plan.html.markdown b/website/docs/r/backup_restore_testing_plan.html.markdown new file mode 100644 index 00000000000..86f57057f8d --- /dev/null +++ b/website/docs/r/backup_restore_testing_plan.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "Backup" +layout: "aws" +page_title: "AWS: aws_backup_restore_testing_plan" +description: |- + Provides an AWS Backup Restore Testing plan resource. +--- + +# Resource: aws_backup_restore_testing_plan + +Provides an AWS Backup Restore Testing plan resource. + +## Example Usage + +```terraform +resource "aws_backup_restore_testing_plan" "example" { + name = "tf_example_restore_testing_plan" + schedule = "cron(0 12 * * ? *)" + + recovery_point_selection { + algorithm = "RANDOM_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + } +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `name` - (Required) The display name of a restore testing plan.\ +* `recovery_point_selection` - (Required) A recovery point selection object that specifies the selection of protected resources and the method when perform restores. +* `schedule` - (Required) A CRON expression specifying when AWS Backup initiates a restore testing job. +* `schedule_timezone` - (Optional) The timezone of the schedule. Defaults to `UTC` +* `start_window` - (Optional) The number of hours before cancelling a job if it doesn't start successfully. Defaults to `24` +* `tags` - (Optional) Metadata that you can assign to help organize the plans you create. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +### Recovery Point Selection Arguments + +`recovery_point_selection` supports the following attributes: + +* `algorithm` - (Required) The algorithm used when restoring a recovery point. For more information, see [RestoreTestingRecoveryPointSelection](https://docs.aws.amazon.com/aws-backup/latest/devguide/API_RecoveryPointSelector.html). +* `include_vaults` - (Required) An array of strings that contains ARNs of vaults which are included for selection. For more information, see [RestoreTestingRecoveryPointSelection](https://docs.aws.amazon.com/aws-backup/latest/devguide/API_RecoveryPointSelector.html). +* `recovery_point_types` - (Required) An array of strings that contains the types of recovery points to select. For more information, see [RestoreTestingRecoveryPointSelection](https://docs.aws.amazon.com/aws-backup/latest/devguide/API_RecoveryPointSelector.html). +* `exclude_vaults` - (Optional) An array of strings that contains ARNs of vaults which are excluded for selection. +* `selection_window` - (Optional) The window in days for the selection of eligible recovery points. Defaults to `30` + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - The id of the restore testing plan. +* `arn` - The ARN of the restore testing plan. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Restore Testing Plan using the `id`. For example: + +```terraform +import { + to = aws_backup_restore_testing_plan.example + id = "" +} +``` + +Using `terraform import`, import Restore Testing Plan using the `id`. For example: + +```console +% terraform import aws_backup_restore_testing_plan.example +```