From 7f5be6ae598d79b740f7bb9fb45f8d53a8ffcef7 Mon Sep 17 00:00:00 2001 From: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:08:19 +0100 Subject: [PATCH 01/17] Adding in the aws_backup_restore_testing resources This introduces the `aws_backup_restore_testing_plan` and `aws_backup_restore_testing_selection` resource for managing AWS Backup restore testing plans. Squashed commit of the following: commit 4b7b51a441b3af68604aa0b2f5d61e6cc3c96b35 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Thu Jul 25 15:09:24 2024 +0100 Running lints on docs commit 8ed1d3846d0535d065f74c051a73d04f2e403317 Merge: 860a996b6a c5d502a267 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Thu Jul 25 14:53:43 2024 +0100 Merge remote-tracking branch 'upstream/main' into f-backup-restore-testing commit 860a996b6a9cb72167f8beda1652a6be1923af00 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Tue Jul 23 12:35:32 2024 +0100 Formatting and linting fixes commit 1bb5b7050eb9ec627fdebfed17f6f3a02cc229f4 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Mon Jul 22 09:22:42 2024 +0100 Applying the final changes to private function commit fec3894c8c310f267dd195c26501f5388339d14b Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Mon Jul 22 09:13:43 2024 +0100 Applying the final code review suggestions commit cb9fc821e451fc51c4fb6443075c12d0af9aca72 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Mon Jul 22 09:08:13 2024 +0100 Apply suggestions for updated test methods names Co-authored-by: aristosvo <8375124+aristosvo@users.noreply.github.com> commit c7d2c796a3dfc5237176e2071a4bbc14b1e9fd45 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Mon Jul 22 09:06:15 2024 +0100 Apply suggestions for redundant comments Co-authored-by: aristosvo <8375124+aristosvo@users.noreply.github.com> commit a70f6d7d7b9185ab5959424068d2cbcbbc3880f1 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Mon Jul 22 09:03:45 2024 +0100 Update to v2 imports as suggested Co-authored-by: aristosvo <8375124+aristosvo@users.noreply.github.com> commit db5e901942732f5faf08b772b661f783fddfa91f Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Wed Jul 10 14:59:25 2024 +0100 Fixes after rebasing commit 360a7cdc2305920ccefb0cf65167d065b92a1a3d Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Mon Apr 22 14:21:45 2024 +0100 Removal of the go mod files for easier review commit 96d17d969e10bea43aed70efdf87a1b3d239337f Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Fri Apr 19 16:16:33 2024 +0100 Introduction of the `aws_backup_restore_testing_selection` resource commit 838b716222d2cf5fe586c985c373099855497781 Author: Thomas Franklin <20070560+Thomas-Franklin@users.noreply.github.com> Date: Thu Apr 18 15:25:03 2024 +0100 Adding in the `aws_backup_restore_testing_plan` resource This introduces the `aws_backup_restore_testing_plan` resource for managing AWS Backup restore testing plans. This is resource 1 of 2, where 2 depends on this resource. Closes: https://github.com/hashicorp/terraform-provider-aws/issues/34699 --- internal/service/backup/exports_test.go | 8 +- internal/service/backup/find.go | 49 ++ .../service/backup/restore_testing_plan.go | 455 +++++++++++++++++ .../backup/restore_testing_plan_test.go | 478 ++++++++++++++++++ .../backup/restore_testing_selection.go | 419 +++++++++++++++ .../backup/restore_testing_selection_test.go | 276 ++++++++++ .../service/backup/service_package_gen.go | 14 +- .../backup_restore_testing_plan.html.markdown | 76 +++ ...up_restore_testing_selection.html.markdown | 104 ++++ 9 files changed, 1876 insertions(+), 3 deletions(-) create mode 100644 internal/service/backup/restore_testing_plan.go create mode 100644 internal/service/backup/restore_testing_plan_test.go create mode 100644 internal/service/backup/restore_testing_selection.go create mode 100644 internal/service/backup/restore_testing_selection_test.go create mode 100644 website/docs/r/backup_restore_testing_plan.html.markdown create mode 100644 website/docs/r/backup_restore_testing_selection.html.markdown diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 4466e1cf367..0e334a09e77 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -5,6 +5,10 @@ package backup // Exports for use in tests only. var ( - FindVaultAccessPolicyByName = findVaultAccessPolicyByName - FindVaultByName = findVaultByName + FindVaultAccessPolicyByName = findVaultAccessPolicyByName + FindVaultByName = findVaultByName + FindRestoreTestingPlanByName = findRestoreTestingPlanByName + FindRestoreTestingSelectionByName = findRestoreTestingSelectionByName + RestoreTestingPlanResource = newResourceRestoreTestingPlan + RestoreTestingSelectionResource = newResourceRestoreTestingSelection ) diff --git a/internal/service/backup/find.go b/internal/service/backup/find.go index cdee7f09329..73c9b6c2a73 100644 --- a/internal/service/backup/find.go +++ b/internal/service/backup/find.go @@ -136,3 +136,52 @@ func findFrameworkByName(ctx context.Context, conn *backup.Client, name string) return output, nil } + +func findRestoreTestingPlanByName(ctx context.Context, conn *backup.Client, name string) (*backup.GetRestoreTestingPlanOutput, error) { + in := &backup.GetRestoreTestingPlanInput{ + RestoreTestingPlanName: aws.String(name), + } + + out, err := conn.GetRestoreTestingPlan(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.RestoreTestingPlan == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +func findRestoreTestingSelectionByName(ctx context.Context, conn *backup.Client, name string, restoreTestingPlanName string) (*backup.GetRestoreTestingSelectionOutput, error) { + in := &backup.GetRestoreTestingSelectionInput{ + RestoreTestingPlanName: aws.String(restoreTestingPlanName), + RestoreTestingSelectionName: aws.String(name), + } + + out, err := conn.GetRestoreTestingSelection(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.RestoreTestingSelection == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} diff --git a/internal/service/backup/restore_testing_plan.go b/internal/service/backup/restore_testing_plan.go new file mode 100644 index 00000000000..d6fafebb602 --- /dev/null +++ b/internal/service/backup/restore_testing_plan.go @@ -0,0 +1,455 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup + +import ( + "context" + "errors" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/backup" + awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Restore Testing Plan") +// @Tags(identifierAttribute="arn") +func newResourceRestoreTestingPlan(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &restoreTestingPlanResource{} + r.SetDefaultCreateTimeout(5 * time.Minute) + r.SetDefaultDeleteTimeout(5 * time.Minute) + r.SetDefaultUpdateTimeout(5 * time.Minute) + return r, nil +} + +const ( + ResNameRestoreTestingPlan = "Restore Testing Plan" +) + +type restoreTestingPlanResource struct { + framework.ResourceWithConfigure + framework.WithImportByID + framework.WithTimeouts +} + +func (r *restoreTestingPlanResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_backup_restore_testing_plan" +} + +func (r *restoreTestingPlanResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 50), + stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9A-Za-z_]+$`), "must contain only alphanumeric characters, and underscores"), + }, + }, + "schedule_expression": schema.StringAttribute{ + Required: true, + }, + "schedule_expression_timezone": schema.StringAttribute{ + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "start_window_hours": schema.Int64Attribute{ + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(1, 168), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + "recovery_point_selection": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[restoreRecoveryPointSelectionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "algorithm": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("RANDOM_WITHIN_WINDOW", "LATEST_WITHIN_WINDOW"), + }, + }, + "include_vaults": schema.SetAttribute{ + CustomType: fwtypes.SetOfStringType, + ElementType: types.StringType, + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexache.MustCompile(`^arn:aws:backup:\w+(?:-\w+)+:\d{12}:backup-vault:[A-Za-z0-9_\-\*]+|\*$`), "must be either an AWS ARN for a backup vault or a *"), + ), + }, + }, + "recovery_point_types": schema.SetAttribute{ + Required: true, + CustomType: fwtypes.SetOfStringType, + ElementType: types.StringType, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.OneOf("CONTINUOUS", "SNAPSHOT"), + ), + }, + }, + "exclude_vaults": schema.SetAttribute{ + CustomType: fwtypes.SetOfStringType, + ElementType: types.StringType, + Optional: true, + Computed: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexache.MustCompile(`^arn:aws:backup:\w+(?:-\w+)+:\d{12}:backup-vault:[A-Za-z0-9_\-\*]+|\*$`), "must be either an AWS ARN for a backup vault or a *"), + ), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, + "selection_window_days": schema.Int64Attribute{ + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(1, 365), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + }, + }, + }, + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Delete: true, + }), + }, + } +} + +func (r *restoreTestingPlanResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().BackupClient(ctx) + + var plan restoreTestingPlanResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := &awstypes.RestoreTestingPlanForCreate{} + resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...) + if resp.Diagnostics.HasError() { + return + } + + tags := make(map[string]string) + for k, v := range getTagsIn(ctx) { + tags[k] = v + } + + out, err := conn.CreateRestoreTestingPlan(ctx, &backup.CreateRestoreTestingPlanInput{ + RestoreTestingPlan: in, + Tags: tags, + }) + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), err), + err.Error(), + ) + return + } + if out == nil || out.RestoreTestingPlanName == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), nil), + errors.New("empty output").Error(), + ) + return + } + + plan.RestoreTestingPlanArn = flex.StringToFramework(ctx, out.RestoreTestingPlanArn) + + // "wait" for creation.. this is to get the _optional_ values + created, err := waitRestoreTestingPlanLatest(ctx, conn, plan.RestoreTestingPlanName.ValueString(), r.CreateTimeout(ctx, plan.Timeouts)) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameRestoreTestingPlan, "", err), + err.Error(), + ) + return + } + + // var state restoreTestingSelectionResourceModel + resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingPlan, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *restoreTestingPlanResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().BackupClient(ctx) + + var state restoreTestingPlanResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findRestoreTestingPlanByName(ctx, conn, state.RestoreTestingPlanName.ValueString()) + + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionSetting, ResNameRestoreTestingPlan, state.RestoreTestingPlanName.ValueString(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out.RestoreTestingPlan, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if ok := out.ResultMetadata.Has("Tags"); ok { + v := out.ResultMetadata.Get("Tags") + setTagsOut(ctx, v.(map[string]string)) + } + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *restoreTestingPlanResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().BackupClient(ctx) + + var state, plan restoreTestingPlanResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if !state.RestoreTestingPlanName.Equal(plan.RestoreTestingPlanName) { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), errors.New("name changes are not supported")), + "changing the name of a restore testing plan is not supported", + ) + return + } + + if !state.RecoveryPointSelection.Equal(plan.RecoveryPointSelection) || + !state.ScheduleExpression.Equal(plan.ScheduleExpression) || + !state.ScheduleExpressionTimezone.Equal(plan.ScheduleExpressionTimezone) || + !state.StartWindowHours.Equal(plan.StartWindowHours) { + + in := &awstypes.RestoreTestingPlanForUpdate{} + resp.Diagnostics.Append(flex.Expand(ctx, &plan, in)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := conn.UpdateRestoreTestingPlan(ctx, &backup.UpdateRestoreTestingPlanInput{ + RestoreTestingPlanName: aws.String(plan.RestoreTestingPlanName.ValueString()), + RestoreTestingPlan: in, + }) + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), err), + err.Error(), + ) + return + } + if out == nil || out.RestoreTestingPlanName == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), nil), + errors.New("empty output").Error(), + ) + return + } + + plan.RestoreTestingPlanArn = flex.StringToFramework(ctx, out.RestoreTestingPlanArn) + + // "wait" for update.. this is to get the _optional_ values + created, err := waitRestoreTestingPlanLatest(ctx, conn, plan.RestoreTestingPlanName.ValueString(), r.UpdateTimeout(ctx, plan.Timeouts)) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForUpdate, ResNameRestoreTestingPlan, "", err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingPlan, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *restoreTestingPlanResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().BackupClient(ctx) + + var state restoreTestingPlanResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &backup.DeleteRestoreTestingPlanInput{ + RestoreTestingPlanName: state.RestoreTestingPlanName.ValueStringPointer(), + } + + _, err := conn.DeleteRestoreTestingPlan(ctx, in) + + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameRestoreTestingPlan, state.RestoreTestingPlanName.String(), err), + err.Error(), + ) + return + } + + if _, err := waitRestoreTestingPlanDeleted(ctx, conn, state.RestoreTestingPlanName.ValueString(), r.DeleteTimeout(ctx, state.Timeouts)); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForDeletion, ResNameRestoreTestingPlan, state.RestoreTestingPlanName.String(), err), + err.Error(), + ) + return + } +} + +func (r *restoreTestingPlanResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func (r *restoreTestingPlanResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrName), req.ID)...) +} + +const ( + stateNormal = "NORMAL" + stateNotFound = "NOT_FOUND" +) + +func waitRestoreTestingPlanDeleted(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.GetRestoreTestingPlanOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{stateNormal}, + Target: []string{}, + Refresh: statusRestorePlan(ctx, conn, name), + Timeout: timeout, + } + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if out, ok := outputRaw.(*backup.GetRestoreTestingPlanOutput); ok { + return out, err + } + + return nil, err +} + +func waitRestoreTestingPlanLatest(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.GetRestoreTestingPlanOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{}, + Target: []string{stateNormal}, + Refresh: statusRestorePlan(ctx, conn, name), + Timeout: timeout, + } + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if out, ok := outputRaw.(*backup.GetRestoreTestingPlanOutput); ok { + return out, err + } + + return nil, err +} + +func statusRestorePlan(ctx context.Context, conn *backup.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findRestoreTestingPlanByName(ctx, conn, name) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, stateNormal, nil + } +} + +type restoreTestingPlanResourceModel struct { + RestoreTestingPlanArn types.String `tfsdk:"arn"` + RestoreTestingPlanName types.String `tfsdk:"name"` + ScheduleExpression types.String `tfsdk:"schedule_expression"` + ScheduleExpressionTimezone types.String `tfsdk:"schedule_expression_timezone"` + StartWindowHours types.Int64 `tfsdk:"start_window_hours"` + RecoveryPointSelection fwtypes.ListNestedObjectValueOf[restoreRecoveryPointSelectionModel] `tfsdk:"recovery_point_selection"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type restoreRecoveryPointSelectionModel struct { + Algorithm types.String `tfsdk:"algorithm"` + IncludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"include_vaults"` + RecoveryPointTypes fwtypes.SetValueOf[types.String] `tfsdk:"recovery_point_types"` + ExcludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"exclude_vaults"` + SelectionWindowDays types.Int64 `tfsdk:"selection_window_days"` +} 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..6c76853dd43 --- /dev/null +++ b/internal/service/backup/restore_testing_plan_test.go @@ -0,0 +1,478 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/backup" + "github.com/aws/aws-sdk-go-v2/service/backup/types" + 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" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/names" + + tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" +) + +func TestAccBackupRestoreTestingPlan_basic(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), // no tags + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccBackupRestoreTestingPlan_disappears(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.RestoreTestingPlanResource, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccBackupRestoreTestingPlan_tags(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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_tags("Name", "RestoreTestingPlan", rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", "RestoreTestingPlan"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + { + Config: testAccRestoreTestingPlanConfig_tags("Name", "Testing1", rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", "Testing1"), + ), + }, + }, + }) +} + +func TestAccBackupRestoreTestingPlan_includeVaults(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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_includeVaults(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "LATEST_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", "1"), + acctest.CheckResourceAttrRegionalARN(resourceName, "recovery_point_selection.0.include_vaults.0", "backup", fmt.Sprintf("backup-vault:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccBackupRestoreTestingPlan_excludeVaults(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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_excludeVaults(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "LATEST_WITHIN_WINDOW"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", "1"), + acctest.CheckResourceAttrRegionalARN(resourceName, "recovery_point_selection.0.exclude_vaults.0", "backup", fmt.Sprintf("backup-vault:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccBackupRestoreTestingPlan_additionals(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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_additionals("365", "cron(0 12 ? * * *)", rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.exclude_vaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "365"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "start_window_hours", "168"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccBackupRestoreTestingPlan_additionalwithupdates(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingPlanOutput + resourceName := "aws_backup_restore_testing_plan.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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_additionals("365", "cron(0 1 ? * * *)", rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.exclude_vaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "365"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 1 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "start_window_hours", "168"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + { + Config: testAccRestoreTestingPlanConfig_additionals("1", "cron(0 12 ? * * *)", rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.exclude_vaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "1"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression_timezone", "Europe/London"), + resource.TestCheckResourceAttr(resourceName, "start_window_hours", "12"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", "RestoreTestingPlan"), + ), + }, + }, + }) +} + +func testAccCheckRestoreTestingPlanDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_backup_restore_testing_plan" { + continue + } + + if rs.Primary.Attributes["name"] == "" { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, "unknown", errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + _, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], err) + } + + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckRestoreTestingPlanExists(ctx context.Context, name string, restoretestingplan *backup.GetRestoreTestingPlanOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not found")) + } + + if rs.Primary.Attributes["name"] == "" { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + resp, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) + + if err != nil { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, err) + } + + *restoretestingplan = *resp + + return nil + } +} + +func testAccRestoreTestingPlanConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + enable_key_rotation = true +} + +resource "aws_backup_vault" "test" { + name = "%[1]s" + kms_key_arn = aws_kms_key.test.arn +} +`, rName) +} + +func testAccRestoreTestingPlanConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = "%[1]s" + + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 +} +`, rName) +} + +func testAccRestoreTestingPlanConfig_tags(tagName, tagValue, rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = "%[3]s" + + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 + + tags = { + "%[1]s" = "%[2]s" + } +} +`, tagName, tagValue, rName) +} + +func testAccRestoreTestingPlanConfig_additionals(selectionWindowDays, scheduleExpression, rName string) string { + return fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = "%[3]s" + + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS", "SNAPSHOT"] + selection_window_days = %[1]s + } + + schedule_expression = "%[2]s" + start_window_hours = 168 +} +`, selectionWindowDays, scheduleExpression, rName) +} + +func testAccRestoreTestingPlanConfig_includeVaults(rName string) string { + return acctest.ConfigCompose( + testAccRestoreTestingPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = "%[1]s" + + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = [resource.aws_backup_vault.test.arn] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 +} +`, rName), + ) +} + +func testAccRestoreTestingPlanConfig_excludeVaults(rName string) string { + return acctest.ConfigCompose( + testAccRestoreTestingPlanConfig_base(rName), + fmt.Sprintf(` +resource "aws_backup_restore_testing_plan" "test" { + name = "%[1]s" + + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + exclude_vaults = [resource.aws_backup_vault.test.arn] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 +} +`, rName), + ) +} diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go new file mode 100644 index 00000000000..e9ba9b0ff4f --- /dev/null +++ b/internal/service/backup/restore_testing_selection.go @@ -0,0 +1,419 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/backup" + awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Restore Testing Plan Selection") +func newResourceRestoreTestingSelection(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceRestoreTestingSelection{} + + r.SetDefaultCreateTimeout(5 * time.Minute) + r.SetDefaultUpdateTimeout(5 * time.Minute) + r.SetDefaultDeleteTimeout(5 * time.Minute) + + return r, nil +} + +const ( + ResNameRestoreTestingSelection = "Restore Testing Selection" +) + +type resourceRestoreTestingSelection struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceRestoreTestingSelection) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_backup_restore_testing_selection" +} + +func (r *resourceRestoreTestingSelection) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 50), + stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9A-Za-z_]+$`), "must contain only alphanumeric characters, and underscores"), + }, + }, + "restore_testing_plan_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "protected_resource_type": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "iam_role_arn": schema.StringAttribute{ + Required: true, + }, + "restore_metadata_overrides": schema.MapAttribute{ + Optional: true, + Computed: true, + CustomType: fwtypes.NewMapTypeOf[types.String](ctx), + }, + "validation_window_hours": schema.Int64Attribute{ + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(1, 168), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "protected_resource_conditions": schema.SingleNestedBlock{ + CustomType: fwtypes.NewObjectTypeOf[protectedResourceConditionsModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.Object{ + objectvalidator.IsRequired(), + }, + Attributes: map[string]schema.Attribute{ + "string_equals": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueMap](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + "string_not_equals": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueMap](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *resourceRestoreTestingSelection) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().BackupClient(ctx) + + var plan restoreTestingSelectionResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + requestBody := &awstypes.RestoreTestingSelectionForCreate{} + resp.Diagnostics.Append(flex.Expand(ctx, plan, requestBody)...) + if resp.Diagnostics.HasError() { + return + } + + input := &backup.CreateRestoreTestingSelectionInput{ + RestoreTestingSelection: requestBody, + RestoreTestingPlanName: plan.RestoreTestingPlanName.ValueStringPointer(), + } + + out, err := conn.CreateRestoreTestingSelection(ctx, input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingSelection, plan.RestoreTestingSelectionName.ValueString(), err), + err.Error(), + ) + return + } + if out == nil || out.CreationTime == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingSelection, plan.RestoreTestingSelectionName.ValueString(), err), + errors.New("empty output").Error(), + ) + return + } + + // "wait" for creation.. this is to get the _optional_ values + created, err := waitRestoreTestingSelectionLatest(ctx, conn, plan.RestoreTestingSelectionName.ValueString(), plan.RestoreTestingPlanName.ValueString(), r.CreateTimeout(ctx, plan.Timeouts)) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameRestoreTestingPlan, "", err), + err.Error(), + ) + return + } + + // var state restoreTestingSelectionResourceModel + resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingSelection, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceRestoreTestingSelection) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().BackupClient(ctx) + + var state restoreTestingSelectionResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findRestoreTestingSelectionByName(ctx, conn, state.RestoreTestingSelectionName.ValueString(), state.RestoreTestingPlanName.ValueString()) + + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionSetting, ResNameRestoreTestingSelection, state.RestoreTestingSelectionName.ValueString(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out.RestoreTestingSelection, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceRestoreTestingSelection) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().BackupClient(ctx) + + var state, plan restoreTestingSelectionResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if !state.RestoreTestingPlanName.Equal(plan.RestoreTestingPlanName) { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), errors.New("name changes are not supported")), + "changing the name of a restore testing plan is not supported", + ) + return + } + + if !state.RestoreMetadataOverrides.Equal(plan.RestoreMetadataOverrides) || + !state.ValidationWindowHours.Equal(plan.ValidationWindowHours) || + !state.IamRoleArn.Equal(plan.IamRoleArn) || + !state.ProtectedResourceConditions.Equal(plan.ProtectedResourceConditions) { + + in := &awstypes.RestoreTestingSelectionForUpdate{} + resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := conn.UpdateRestoreTestingSelection(ctx, &backup.UpdateRestoreTestingSelectionInput{ + RestoreTestingSelectionName: plan.RestoreTestingSelectionName.ValueStringPointer(), + RestoreTestingPlanName: plan.RestoreTestingPlanName.ValueStringPointer(), + RestoreTestingSelection: in, + }) + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingSelection, plan.RestoreTestingPlanName.ValueString(), err), + err.Error(), + ) + return + } + if out == nil || out.RestoreTestingPlanName == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingSelection, plan.RestoreTestingPlanName.ValueString(), nil), + errors.New("empty output").Error(), + ) + return + } + + // "wait" for update.. this is to get the _optional_ values + created, err := waitRestoreTestingSelectionLatest(ctx, conn, plan.RestoreTestingSelectionName.ValueString(), plan.RestoreTestingPlanName.ValueString(), r.UpdateTimeout(ctx, plan.Timeouts)) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForUpdate, ResNameRestoreTestingSelection, "", err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingSelection, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceRestoreTestingSelection) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().BackupClient(ctx) + + var state restoreTestingSelectionResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &backup.DeleteRestoreTestingSelectionInput{ + RestoreTestingSelectionName: state.RestoreTestingSelectionName.ValueStringPointer(), + RestoreTestingPlanName: state.RestoreTestingPlanName.ValueStringPointer(), + } + + _, err := conn.DeleteRestoreTestingSelection(ctx, in) + + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameRestoreTestingPlan, state.RestoreTestingSelectionName.String(), err), + err.Error(), + ) + return + } + + if _, err := waitRestoreTestingSelectionDeleted(ctx, conn, state.RestoreTestingSelectionName.ValueString(), state.RestoreTestingPlanName.ValueString(), r.DeleteTimeout(ctx, state.Timeouts)); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForDeletion, ResNameRestoreTestingPlan, state.RestoreTestingSelectionName.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceRestoreTestingSelection) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + parts := strings.Split(req.ID, ":") + if len(parts) != 2 { + resp.Diagnostics.AddError("Resource Import Invalid ID", fmt.Sprintf(`Unexpected format for import ID (%s), use: "RestoreTestingSelectionName:RestoreTestingPlanName"`, req.ID)) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrName), parts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("restore_testing_plan_name"), parts[1])...) +} + +// ==== WAITERS ==== // +func waitRestoreTestingSelectionLatest(ctx context.Context, conn *backup.Client, name string, restoreTestingPlanName string, timeout time.Duration) (*backup.GetRestoreTestingSelectionOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{}, + Target: []string{stateNormal}, + Refresh: statusRestoreSelection(ctx, conn, name, restoreTestingPlanName), + Timeout: timeout, + } + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if out, ok := outputRaw.(*backup.GetRestoreTestingSelectionOutput); ok { + return out, err + } + + return nil, err + +} + +func waitRestoreTestingSelectionDeleted(ctx context.Context, conn *backup.Client, name string, restoreTestingPlanName string, timeout time.Duration) (*backup.GetRestoreTestingSelectionOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{stateNormal}, + Target: []string{}, + Refresh: statusRestoreSelection(ctx, conn, name, restoreTestingPlanName), + Timeout: timeout, + } + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if out, ok := outputRaw.(*backup.GetRestoreTestingSelectionOutput); ok { + return out, err + } + + return nil, err + +} + +func statusRestoreSelection(ctx context.Context, conn *backup.Client, name, restoreTestingPlanName string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findRestoreTestingSelectionByName(ctx, conn, name, restoreTestingPlanName) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, stateNormal, nil + } +} + +type restoreTestingSelectionResourceModel struct { + RestoreTestingSelectionName types.String `tfsdk:"name"` + RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` + ProtectedResourceConditions fwtypes.ObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` + IamRoleArn types.String `tfsdk:"iam_role_arn"` + ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` + ProtectedResourceType types.String `tfsdk:"protected_resource_type"` + RestoreMetadataOverrides fwtypes.MapValueOf[basetypes.StringValue] `tfsdk:"restore_metadata_overrides"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type protectedResourceConditionsModel struct { + StringEquals fwtypes.ListNestedObjectValueOf[keyValueMap] `tfsdk:"string_equals"` + StringNotEquals fwtypes.ListNestedObjectValueOf[keyValueMap] `tfsdk:"string_not_equals"` +} + +type keyValueMap struct { + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` +} diff --git a/internal/service/backup/restore_testing_selection_test.go b/internal/service/backup/restore_testing_selection_test.go new file mode 100644 index 00000000000..1c47ff66c38 --- /dev/null +++ b/internal/service/backup/restore_testing_selection_test.go @@ -0,0 +1,276 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/backup" + "github.com/aws/aws-sdk-go-v2/service/backup/types" + 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" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingSelectionOutput + resourceName := "aws_backup_restore_testing_selection.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRestoreTestingPlanSelection(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingSelectionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName), + resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: fmt.Sprintf("%s:%s", rName, rName), + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccBackupRestoreTestingSelection_disappears(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingselection backup.GetRestoreTestingSelectionOutput + resourceName := "aws_backup_restore_testing_selection.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + 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: testAccRestoreTestingSelectionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingselection), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.RestoreTestingSelectionResource, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { + ctx := acctest.Context(t) + var restoretestingplan backup.GetRestoreTestingSelectionOutput + resourceName := "aws_backup_restore_testing_selection.test" + rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRestoreTestingPlanSelection(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRestoreTestingSelectionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName), + resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: fmt.Sprintf("%s:%s", rName, rName), + ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + { + Config: testAccRestoreTestingSelectionConfig_updates(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName), + resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), + ), + }, + }, + }) +} + +func testAccCheckRestoreTestingPlanSelection(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_backup_restore_testing_selection" { + continue + } + + if rs.Primary.Attributes["name"] == "" { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, "unknown", errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + _, err := tfbackup.FindRestoreTestingSelectionByName(ctx, conn, rs.Primary.Attributes["name"], rs.Primary.Attributes["restore_testing_plan_name"]) + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], err) + } + + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckRestoreTestingSelectionExists(ctx context.Context, name string, restoretestingplan *backup.GetRestoreTestingSelectionOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not found")) + } + + if rs.Primary.Attributes["name"] == "" { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + resp, err := tfbackup.FindRestoreTestingSelectionByName(ctx, conn, rs.Primary.Attributes["name"], rs.Primary.Attributes["restore_testing_plan_name"]) + + if err != nil { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, err) + } + + *restoretestingplan = *resp + + return nil + } +} + +func testAccRestoreTestingSelectionConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = "%[1]s" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "" + Principal = { + Service = "ec2.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_kms_key" "test" { + enable_key_rotation = true +} + +resource "aws_kms_alias" "a" { + name = "alias/%[1]s" + target_key_id = aws_kms_key.test.key_id +} + +resource "aws_backup_vault" "test" { + name = "%[1]s" + kms_key_arn = aws_kms_key.test.arn +} + +resource "aws_backup_restore_testing_plan" "test" { + name = "%[1]s" + + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 +} +`, rName) +} + +func testAccRestoreTestingSelectionConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccRestoreTestingSelectionConfig_base(rName), + fmt.Sprintf(` +resource "aws_backup_restore_testing_selection" "test" { + name = "%[1]s" + + restore_testing_plan_name = aws_backup_restore_testing_plan.test.name + protected_resource_type = "EC2" + iam_role_arn = aws_iam_role.test.arn + + protected_resource_conditions { + } +} +`, rName), + ) +} + +func testAccRestoreTestingSelectionConfig_updates(rName string) string { + return acctest.ConfigCompose( + testAccRestoreTestingSelectionConfig_base(rName), + fmt.Sprintf(` +resource "aws_backup_restore_testing_selection" "test" { + name = "%[1]s" + + restore_testing_plan_name = aws_backup_restore_testing_plan.test.name + protected_resource_type = "EC2" + iam_role_arn = aws_iam_role.test.arn + + protected_resource_conditions { + string_equals = [ + { + key = "aws:ResourceTag/backup" + value = true + } + ] + } + + validation_window_hours = 10 + + restore_metadata_overrides = { + instanceType = "t2.micro" + } +} +`, rName), + ) +} diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 4cdedfc56f4..fe535c63b63 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -19,7 +19,19 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceRestoreTestingPlan, + Name: "Restore Testing Plan", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }, + }, + { + Factory: newResourceRestoreTestingSelection, + Name: "Restore Testing Plan Selection", + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { 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..e2304d16ca9 --- /dev/null +++ b/website/docs/r/backup_restore_testing_plan.html.markdown @@ -0,0 +1,76 @@ +--- +subcategory: "Backup" +layout: "aws" +page_title: "AWS: aws_backup_restore_testing_plan" +description: |- + Terraform resource for managing an AWS Backup Restore Testing Plan. +--- +# Resource: aws_backup_restore_testing_plan + +Terraform resource for managing an AWS Backup Restore Testing Plan. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_backup_restore_testing_plan" "example" { + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` (Required): The name of the restore testing plan. Must be between 1 and 50 characters long and contain only alphanumeric characters and underscores. +* `schedule_expression` (Required): The schedule expression for the restore testing plan. +* `schedule_expression_timezone` (Optional): The timezone for the schedule expression. If not provided, the state value will be used. +* `start_window_hours` (Optional): The number of hours in the start window for the restore testing plan. Must be between 1 and 168. +* `recovery_point_selection` (Required): Specifies the recovery point selection configuration. See [RecoveryPointSelection](#recoverypointselection) section for more details. + +### RecoveryPointSelection + +* `algorithm` (Required): Specifies the algorithm used for selecting recovery points. Valid values are "RANDOM_WITHIN_WINDOW" and "LATEST_WITHIN_WINDOW". +* `include_vaults` (Required): Specifies the backup vaults to include in the recovery point selection. Each value must be a valid AWS ARN for a backup vault or "*" to include all backup vaults. +* `recovery_point_types` (Required): Specifies the types of recovery points to include in the selection. Valid values are "CONTINUOUS" and "SNAPSHOT". +* `exclude_vaults` (Optional): Specifies the backup vaults to exclude from the recovery point selection. Each value must be a valid AWS ARN for a backup vault or "*" to exclude all backup vaults. +* `selection_window_days` (Optional): Specifies the number of days within which the recovery points should be selected. Must be a value between 1 and 365. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Restore Testing Plan. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `5m`) +* `update` - (Default `5m`) +* `delete` - (Default `5m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Backup Restore Testing Plan using the `name`. For example: + +```terraform +import { + to = aws_backup_restore_testing_plan.example + id = "my_testing_plan" +} +``` + +Using `terraform import`, import Backup Restore Testing Plan using the `name`. For example: + +```console +% terraform import aws_backup_restore_testing_plan.example my_testing_plan +``` diff --git a/website/docs/r/backup_restore_testing_selection.html.markdown b/website/docs/r/backup_restore_testing_selection.html.markdown new file mode 100644 index 00000000000..c2619ab2b8a --- /dev/null +++ b/website/docs/r/backup_restore_testing_selection.html.markdown @@ -0,0 +1,104 @@ +--- +subcategory: "Backup" +layout: "aws" +page_title: "AWS: aws_backup_restore_testing_selection" +description: |- + Terraform resource for managing an AWS Backup Restore Testing Selection. +--- + +# Resource: aws_backup_restore_testing_selection + +Terraform resource for managing an AWS Backup Restore Testing Selection. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_backup_restore_testing_selection" "example" { + name = "ec2_selection" + + restore_testing_plan_name = aws_backup_restore_testing_plan.example.name + protected_resource_type = "EC2" + iam_role_arn = aws_iam_role.example.arn + + protected_resource_conditions { + string_equals = [] + string_not_equals = [] + } +} +``` + +### Advanced Usage + +```terraform +resource "aws_backup_restore_testing_selection" "example" { + name = "ec2_selection" + + restore_testing_plan_name = aws_backup_restore_testing_plan.example.name + protected_resource_type = "EC2" + iam_role_arn = aws_iam_role.example.arn + + protected_resource_conditions { + string_equals = [ + { + key = "Backup Test", + value = true + } + ] + string_not_equals = [] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the backup restore testing selection. + +* `restore_testing_plan_name` - (Required) The name of the restore testing plan. + +* `protected_resource_type` - (Required) The type of the protected resource. + +* `iam_role_arn` - (Required) The ARN of the IAM role. + +* `protected_resource_conditions` - (Required) The conditions for the protected resource. + +* `restore_metadata_overrides` - (Optional) Override certain restore metadata keys. See the complete list of [restore testing inferred metadata](https://docs.aws.amazon.com/aws-backup/latest/devguide/restore-testing-inferred-metadata.html) . + +The `protected_resource_conditions` block supports the following arguments: + +* `string_equals` - (Optional) The list of string equals conditions for resource tags. Filters the values of your tagged resources for only those resources that you tagged with the same value. Also called "exact matching.". See [the structure for details](#keyvalues) + +* `string_not_equals` - (Optional) The list of string not equals conditions for resource tags. Filters the values of your tagged resources for only those resources that you tagged that do not have the same value. Also called "negated matching.". See [the structure for details](#keyvalues) + +### KeyValues + +* `key` - The Tag name, must start with one of the following prefixes: [aws:ResourceTag/] with a Minimum length of 1. Maximum length of 128, and can contain characters that are letters, white space, and numbers that can be represented in UTF-8 and the following characters: `+ - = . _ : /`. +* `value` - The value of the Tag. Maximum length of 256. + +## Timeouts + +The following timeouts are available for this resource: + +* `create` - (Default `5m`) +* `update` - (Default `5m`) +* `delete` - (Default `5m`) + +## Import + +To import an existing backup restore testing selection, use the following `import` block: + +``` +import { + to = aws_backup_restore_testing_selection.example + id = "my_testing_selection:my_testing_plan" +} +``` + +Using `terraform import`, import Backup Restore Testing Selection using the `name:restore_testing_plan_name`. For example: + +```console +% terraform import aws_backup_restore_testing_selection.example restore_testing_selection_12345678:restore_testing_plan_12345678 +``` From e5a8bfd65584f3c16cd200b930ee8b974de7c1cd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 3 Oct 2024 12:05:11 -0400 Subject: [PATCH 02/17] Add CHANGELOG entries. --- .changelog/37039.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/37039.txt diff --git a/.changelog/37039.txt b/.changelog/37039.txt new file mode 100644 index 00000000000..eef1a001481 --- /dev/null +++ b/.changelog/37039.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_backup_restore_testing_plan +``` + +```release-note:new-resource +aws_backup_restore_testing_selection +``` \ No newline at end of file From fe7d7e5881ecd0796d7ceeb4feb2cca3f54aa86e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 3 Oct 2024 14:20:34 -0400 Subject: [PATCH 03/17] r/aws_backup_restore_testing_plan: Tidy up. --- internal/service/backup/exports_test.go | 2 +- .../backup/logically_air_gapped_vault.go | 2 +- .../service/backup/restore_testing_plan.go | 382 ++++++------------ .../backup/restore_testing_plan_test.go | 178 ++++---- .../backup/restore_testing_selection.go | 13 +- .../backup/restore_testing_selection_test.go | 12 +- .../service/backup/service_package_gen.go | 10 +- 7 files changed, 239 insertions(+), 360 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 1bdaa51b150..5b490363172 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -12,7 +12,7 @@ var ( ResourceRegionSettings = resourceRegionSettings ResourceReportPlan = resourceReportPlan ResourceSelection = resourceSelection - RestoreTestingPlanResource = newResourceRestoreTestingPlan + ResourceRestoreTestingPlan = newRestoreTestingPlanResource RestoreTestingSelectionResource = newResourceRestoreTestingSelection ResourceVault = resourceVault ResourceVaultLockConfiguration = resourceVaultLockConfiguration diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go index 56ceaef5c99..a11e7022b1f 100644 --- a/internal/service/backup/logically_air_gapped_vault.go +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -206,9 +206,9 @@ type logicallyAirGappedVaultResourceModel struct { ID types.String `tfsdk:"id"` MaxRetentionDays types.Int64 `tfsdk:"max_retention_days"` MinRetentionDays types.Int64 `tfsdk:"min_retention_days"` - Timeouts timeouts.Value `tfsdk:"timeouts"` Tags tftags.Map `tfsdk:"tags"` TagsAll tftags.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } func findLogicallyAirGappedBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { // nosemgrep:ci.backup-in-func-name diff --git a/internal/service/backup/restore_testing_plan.go b/internal/service/backup/restore_testing_plan.go index 3f4c14d0090..af392419895 100644 --- a/internal/service/backup/restore_testing_plan.go +++ b/internal/service/backup/restore_testing_plan.go @@ -5,14 +5,12 @@ package backup import ( "context" - "errors" - "time" + "fmt" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/backup" awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" - "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" @@ -26,11 +24,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + sdkid "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -39,30 +38,22 @@ import ( // @FrameworkResource(name="Restore Testing Plan") // @Tags(identifierAttribute="arn") -func newResourceRestoreTestingPlan(_ context.Context) (resource.ResourceWithConfigure, error) { +func newRestoreTestingPlanResource(_ context.Context) (resource.ResourceWithConfigure, error) { r := &restoreTestingPlanResource{} - r.SetDefaultCreateTimeout(5 * time.Minute) - r.SetDefaultDeleteTimeout(5 * time.Minute) - r.SetDefaultUpdateTimeout(5 * time.Minute) + return r, nil } -const ( - ResNameRestoreTestingPlan = "Restore Testing Plan" -) - type restoreTestingPlanResource struct { framework.ResourceWithConfigure - framework.WithImportByID - framework.WithTimeouts } -func (r *restoreTestingPlanResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_backup_restore_testing_plan" +func (*restoreTestingPlanResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_backup_restore_testing_plan" } -func (r *restoreTestingPlanResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *restoreTestingPlanResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrARN: framework.ARNAttributeComputedOnly(), names.AttrName: schema.StringAttribute{ @@ -89,7 +80,7 @@ func (r *restoreTestingPlanResource) Schema(ctx context.Context, req resource.Sc Optional: true, Computed: true, Validators: []validator.Int64{ - int64validator.Between(1, 168), + int64validator.Between(0, 168), }, PlanModifiers: []planmodifier.Int64{ int64planmodifier.UseStateForUnknown(), @@ -102,52 +93,44 @@ func (r *restoreTestingPlanResource) Schema(ctx context.Context, req resource.Sc "recovery_point_selection": schema.ListNestedBlock{ CustomType: fwtypes.NewListNestedObjectTypeOf[restoreRecoveryPointSelectionModel](ctx), Validators: []validator.List{ + listvalidator.IsRequired(), listvalidator.SizeAtLeast(1), listvalidator.SizeAtMost(1), }, NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ "algorithm": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf("RANDOM_WITHIN_WINDOW", "LATEST_WITHIN_WINDOW"), - }, + CustomType: fwtypes.StringEnumType[awstypes.RestoreTestingRecoveryPointSelectionAlgorithm](), + Required: true, }, - "include_vaults": schema.SetAttribute{ + "exclude_vaults": schema.SetAttribute{ CustomType: fwtypes.SetOfStringType, ElementType: types.StringType, - Required: true, + Optional: true, + Computed: true, Validators: []validator.Set{ - setvalidator.SizeAtLeast(1), setvalidator.ValueStringsAre( stringvalidator.RegexMatches(regexache.MustCompile(`^arn:aws:backup:\w+(?:-\w+)+:\d{12}:backup-vault:[A-Za-z0-9_\-\*]+|\*$`), "must be either an AWS ARN for a backup vault or a *"), ), }, - }, - "recovery_point_types": schema.SetAttribute{ - Required: true, - CustomType: fwtypes.SetOfStringType, - ElementType: types.StringType, - Validators: []validator.Set{ - setvalidator.SizeAtLeast(1), - setvalidator.ValueStringsAre( - stringvalidator.OneOf("CONTINUOUS", "SNAPSHOT"), - ), + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), }, }, - "exclude_vaults": schema.SetAttribute{ + "include_vaults": schema.SetAttribute{ CustomType: fwtypes.SetOfStringType, ElementType: types.StringType, - Optional: true, - Computed: true, + Required: true, Validators: []validator.Set{ setvalidator.ValueStringsAre( stringvalidator.RegexMatches(regexache.MustCompile(`^arn:aws:backup:\w+(?:-\w+)+:\d{12}:backup-vault:[A-Za-z0-9_\-\*]+|\*$`), "must be either an AWS ARN for a backup vault or a *"), ), }, - PlanModifiers: []planmodifier.Set{ - setplanmodifier.UseStateForUnknown(), - }, + }, + "recovery_point_types": schema.SetAttribute{ + CustomType: fwtypes.NewSetTypeOf[fwtypes.StringEnum[awstypes.RestoreTestingRecoveryPointType]](ctx), + Required: true, + ElementType: fwtypes.StringEnumType[awstypes.RestoreTestingRecoveryPointType](), }, "selection_window_days": schema.Int64Attribute{ Optional: true, @@ -162,214 +145,149 @@ func (r *restoreTestingPlanResource) Schema(ctx context.Context, req resource.Sc }, }, }, - "timeouts": timeouts.Block(ctx, timeouts.Opts{ - Create: true, - Delete: true, - }), }, } } -func (r *restoreTestingPlanResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().BackupClient(ctx) - - var plan restoreTestingPlanResourceModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingPlanResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data restoreTestingPlanResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &awstypes.RestoreTestingPlanForCreate{} - resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...) - if resp.Diagnostics.HasError() { - return - } + conn := r.Meta().BackupClient(ctx) - tags := make(map[string]string) - for k, v := range getTagsIn(ctx) { - tags[k] = v + name := data.RestoreTestingPlanName.ValueString() + input := &backup.CreateRestoreTestingPlanInput{ + CreatorRequestId: aws.String(sdkid.UniqueId()), + RestoreTestingPlan: &awstypes.RestoreTestingPlanForCreate{}, + Tags: getTagsIn(ctx), + } + response.Diagnostics.Append(fwflex.Expand(ctx, data, input.RestoreTestingPlan)...) + if response.Diagnostics.HasError() { + return } - out, err := conn.CreateRestoreTestingPlan(ctx, &backup.CreateRestoreTestingPlanInput{ - RestoreTestingPlan: in, - Tags: tags, - }) + _, err := conn.CreateRestoreTestingPlan(ctx, input) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), err), - err.Error(), - ) - return - } - if out == nil || out.RestoreTestingPlanName == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), nil), - errors.New("empty output").Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("creating Backup Restore Testing Plan (%s)", name), err.Error()) + return } - plan.RestoreTestingPlanArn = flex.StringToFramework(ctx, out.RestoreTestingPlanArn) + // Set values for unknowns. + restoreTestingPlan, err := findRestoreTestingPlanByName(ctx, conn, name) - // "wait" for creation.. this is to get the _optional_ values - created, err := waitRestoreTestingPlanLatest(ctx, conn, plan.RestoreTestingPlanName.ValueString(), r.CreateTimeout(ctx, plan.Timeouts)) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameRestoreTestingPlan, "", err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Backup Restore Testing Plan (%s)", name), err.Error()) + return } - // var state restoreTestingSelectionResourceModel - resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingPlan, &plan)...) - if resp.Diagnostics.HasError() { + response.Diagnostics.Append(fwflex.Flatten(ctx, restoreTestingPlan, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *restoreTestingPlanResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().BackupClient(ctx) - - var state restoreTestingPlanResourceModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingPlanResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data restoreTestingPlanResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - out, err := findRestoreTestingPlanByName(ctx, conn, state.RestoreTestingPlanName.ValueString()) + conn := r.Meta().BackupClient(ctx) + + name := data.RestoreTestingPlanName.ValueString() + restoreTestingPlan, err := findRestoreTestingPlanByName(ctx, conn, name) if tfresource.NotFound(err) { - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionSetting, ResNameRestoreTestingPlan, state.RestoreTestingPlanName.ValueString(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Backup Restore Testing Plan (%s)", name), err.Error()) + return } - resp.Diagnostics.Append(flex.Flatten(ctx, out.RestoreTestingPlan, &state)...) - if resp.Diagnostics.HasError() { + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, restoreTestingPlan, &data)...) + if response.Diagnostics.HasError() { return } - if ok := out.ResultMetadata.Has("Tags"); ok { - v := out.ResultMetadata.Get("Tags") - setTagsOut(ctx, v.(map[string]string)) - } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *restoreTestingPlanResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().BackupClient(ctx) - - var state, plan restoreTestingPlanResourceModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingPlanResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new restoreTestingPlanResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { return } - - if !state.RestoreTestingPlanName.Equal(plan.RestoreTestingPlanName) { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), errors.New("name changes are not supported")), - "changing the name of a restore testing plan is not supported", - ) + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { return } - if !state.RecoveryPointSelection.Equal(plan.RecoveryPointSelection) || - !state.ScheduleExpression.Equal(plan.ScheduleExpression) || - !state.ScheduleExpressionTimezone.Equal(plan.ScheduleExpressionTimezone) || - !state.StartWindowHours.Equal(plan.StartWindowHours) { + conn := r.Meta().BackupClient(ctx) - in := &awstypes.RestoreTestingPlanForUpdate{} - resp.Diagnostics.Append(flex.Expand(ctx, &plan, in)...) - if resp.Diagnostics.HasError() { - return + if !old.RecoveryPointSelection.Equal(new.RecoveryPointSelection) || + !old.ScheduleExpression.Equal(new.ScheduleExpression) || + !old.ScheduleExpressionTimezone.Equal(new.ScheduleExpressionTimezone) || + !old.StartWindowHours.Equal(new.StartWindowHours) { + name := new.RestoreTestingPlanName.ValueString() + input := &backup.UpdateRestoreTestingPlanInput{ + RestoreTestingPlan: &awstypes.RestoreTestingPlanForUpdate{}, + RestoreTestingPlanName: aws.String(name), } - - out, err := conn.UpdateRestoreTestingPlan(ctx, &backup.UpdateRestoreTestingPlanInput{ - RestoreTestingPlanName: aws.String(plan.RestoreTestingPlanName.ValueString()), - RestoreTestingPlan: in, - }) - - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), err), - err.Error(), - ) - return - } - if out == nil || out.RestoreTestingPlanName == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), nil), - errors.New("empty output").Error(), - ) + response.Diagnostics.Append(fwflex.Expand(ctx, new, input.RestoreTestingPlan)...) + if response.Diagnostics.HasError() { return } - plan.RestoreTestingPlanArn = flex.StringToFramework(ctx, out.RestoreTestingPlanArn) + _, err := conn.UpdateRestoreTestingPlan(ctx, input) - // "wait" for update.. this is to get the _optional_ values - created, err := waitRestoreTestingPlanLatest(ctx, conn, plan.RestoreTestingPlanName.ValueString(), r.UpdateTimeout(ctx, plan.Timeouts)) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForUpdate, ResNameRestoreTestingPlan, "", err), - err.Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("updating Backup Restore Testing Plan (%s)", name), err.Error()) - resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingPlan, &plan)...) - if resp.Diagnostics.HasError() { return } - - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) - return } - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} -func (r *restoreTestingPlanResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().BackupClient(ctx) + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} - var state restoreTestingPlanResourceModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingPlanResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data restoreTestingPlanResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &backup.DeleteRestoreTestingPlanInput{ - RestoreTestingPlanName: state.RestoreTestingPlanName.ValueStringPointer(), - } + conn := r.Meta().BackupClient(ctx) - _, err := conn.DeleteRestoreTestingPlan(ctx, in) + name := data.RestoreTestingPlanName.ValueString() + _, err := conn.DeleteRestoreTestingPlan(ctx, &backup.DeleteRestoreTestingPlanInput{ + RestoreTestingPlanName: aws.String(name), + }) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return - } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameRestoreTestingPlan, state.RestoreTestingPlanName.String(), err), - err.Error(), - ) + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return } - if _, err := waitRestoreTestingPlanDeleted(ctx, conn, state.RestoreTestingPlanName.ValueString(), r.DeleteTimeout(ctx, state.Timeouts)); err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForDeletion, ResNameRestoreTestingPlan, state.RestoreTestingPlanName.String(), err), - err.Error(), - ) + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting Backup Restore Testing Plan (%s)", name), err.Error()) + return } } @@ -378,102 +296,54 @@ func (r *restoreTestingPlanResource) ModifyPlan(ctx context.Context, request res r.SetTagsAll(ctx, request, response) } -func (r *restoreTestingPlanResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrName), req.ID)...) +func (r *restoreTestingPlanResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrName), request.ID)...) } -func findRestoreTestingPlanByName(ctx context.Context, conn *backup.Client, name string) (*backup.GetRestoreTestingPlanOutput, error) { - in := &backup.GetRestoreTestingPlanInput{ +func findRestoreTestingPlanByName(ctx context.Context, conn *backup.Client, name string) (*awstypes.RestoreTestingPlanForGet, error) { + input := &backup.GetRestoreTestingPlanInput{ RestoreTestingPlanName: aws.String(name), } - out, err := conn.GetRestoreTestingPlan(ctx, in) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - return nil, err - } - - if out == nil || out.RestoreTestingPlan == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out, nil + return findRestoreTestingPlan(ctx, conn, input) } -func statusRestorePlan(ctx context.Context, conn *backup.Client, name string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := findRestoreTestingPlanByName(ctx, conn, name) - if tfresource.NotFound(err) { - return nil, "", nil - } +func findRestoreTestingPlan(ctx context.Context, conn *backup.Client, input *backup.GetRestoreTestingPlanInput) (*awstypes.RestoreTestingPlanForGet, error) { + output, err := conn.GetRestoreTestingPlan(ctx, input) - if err != nil { - return nil, "", err + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } - - return output, stateNormal, nil - } -} - -const ( - stateNormal = "NORMAL" - stateNotFound = "NOT_FOUND" -) - -func waitRestoreTestingPlanDeleted(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.GetRestoreTestingPlanOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{stateNormal}, - Target: []string{}, - Refresh: statusRestorePlan(ctx, conn, name), - Timeout: timeout, - } - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if out, ok := outputRaw.(*backup.GetRestoreTestingPlanOutput); ok { - return out, err } - return nil, err -} - -func waitRestoreTestingPlanLatest(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.GetRestoreTestingPlanOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{}, - Target: []string{stateNormal}, - Refresh: statusRestorePlan(ctx, conn, name), - Timeout: timeout, + if err != nil { + return nil, err } - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*backup.GetRestoreTestingPlanOutput); ok { - return out, err + if output == nil || output.RestoreTestingPlan == nil { + return nil, tfresource.NewEmptyResultError(input) } - return nil, err + return output.RestoreTestingPlan, nil } type restoreTestingPlanResourceModel struct { - RestoreTestingPlanArn types.String `tfsdk:"arn"` + RecoveryPointSelection fwtypes.ListNestedObjectValueOf[restoreRecoveryPointSelectionModel] `tfsdk:"recovery_point_selection"` + RestoreTestingPlanARN types.String `tfsdk:"arn"` RestoreTestingPlanName types.String `tfsdk:"name"` ScheduleExpression types.String `tfsdk:"schedule_expression"` ScheduleExpressionTimezone types.String `tfsdk:"schedule_expression_timezone"` StartWindowHours types.Int64 `tfsdk:"start_window_hours"` - RecoveryPointSelection fwtypes.ListNestedObjectValueOf[restoreRecoveryPointSelectionModel] `tfsdk:"recovery_point_selection"` - Tags types.Map `tfsdk:"tags"` - TagsAll types.Map `tfsdk:"tags_all"` - Timeouts timeouts.Value `tfsdk:"timeouts"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` } type restoreRecoveryPointSelectionModel struct { - Algorithm types.String `tfsdk:"algorithm"` - IncludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"include_vaults"` - RecoveryPointTypes fwtypes.SetValueOf[types.String] `tfsdk:"recovery_point_types"` - ExcludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"exclude_vaults"` - SelectionWindowDays types.Int64 `tfsdk:"selection_window_days"` + Algorithm fwtypes.StringEnum[awstypes.RestoreTestingRecoveryPointSelectionAlgorithm] `tfsdk:"algorithm"` + ExcludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"exclude_vaults"` + IncludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"include_vaults"` + RecoveryPointTypes fwtypes.SetValueOf[fwtypes.StringEnum[awstypes.RestoreTestingRecoveryPointType]] `tfsdk:"recovery_point_types"` + SelectionWindowDays types.Int64 `tfsdk:"selection_window_days"` } diff --git a/internal/service/backup/restore_testing_plan_test.go b/internal/service/backup/restore_testing_plan_test.go index 6c76853dd43..796d5884c30 100644 --- a/internal/service/backup/restore_testing_plan_test.go +++ b/internal/service/backup/restore_testing_plan_test.go @@ -5,30 +5,27 @@ package backup_test import ( "context" - "errors" "fmt" "strings" "testing" - "github.com/aws/aws-sdk-go-v2/service/backup" - "github.com/aws/aws-sdk-go-v2/service/backup/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" 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" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" - "github.com/hashicorp/terraform-provider-aws/names" - tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccBackupRestoreTestingPlan_basic(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -57,7 +54,6 @@ func TestAccBackupRestoreTestingPlan_basic(t *testing.T) { ImportStateVerify: true, ImportStateId: rName, ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, }, }) @@ -65,9 +61,10 @@ func TestAccBackupRestoreTestingPlan_basic(t *testing.T) { func TestAccBackupRestoreTestingPlan_disappears(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -81,7 +78,7 @@ func TestAccBackupRestoreTestingPlan_disappears(t *testing.T) { Config: testAccRestoreTestingPlanConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.RestoreTestingPlanResource, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceRestoreTestingPlan, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -91,7 +88,7 @@ func TestAccBackupRestoreTestingPlan_disappears(t *testing.T) { func TestAccBackupRestoreTestingPlan_tags(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") resource.ParallelTest(t, resource.TestCase{ @@ -104,17 +101,11 @@ func TestAccBackupRestoreTestingPlan_tags(t *testing.T) { CheckDestroy: testAccCheckRestoreTestingPlanDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccRestoreTestingPlanConfig_tags("Name", "RestoreTestingPlan", rName), + Config: testAccRestoreTestingPlanConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), - 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.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "RestoreTestingPlan"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), ), }, { @@ -123,20 +114,22 @@ func TestAccBackupRestoreTestingPlan_tags(t *testing.T) { ImportStateVerify: true, ImportStateId: rName, ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, { - Config: testAccRestoreTestingPlanConfig_tags("Name", "Testing1", rName), + Config: testAccRestoreTestingPlanConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), - 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.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Testing1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccRestoreTestingPlanConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), ), }, }, @@ -145,7 +138,7 @@ func TestAccBackupRestoreTestingPlan_tags(t *testing.T) { func TestAccBackupRestoreTestingPlan_includeVaults(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") resource.ParallelTest(t, resource.TestCase{ @@ -176,7 +169,6 @@ func TestAccBackupRestoreTestingPlan_includeVaults(t *testing.T) { ImportStateVerify: true, ImportStateId: rName, ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, }, }) @@ -184,7 +176,7 @@ func TestAccBackupRestoreTestingPlan_includeVaults(t *testing.T) { func TestAccBackupRestoreTestingPlan_excludeVaults(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") resource.ParallelTest(t, resource.TestCase{ @@ -215,7 +207,6 @@ func TestAccBackupRestoreTestingPlan_excludeVaults(t *testing.T) { ImportStateVerify: true, ImportStateId: rName, ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, }, }) @@ -223,7 +214,7 @@ func TestAccBackupRestoreTestingPlan_excludeVaults(t *testing.T) { func TestAccBackupRestoreTestingPlan_additionals(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") resource.ParallelTest(t, resource.TestCase{ @@ -256,15 +247,14 @@ func TestAccBackupRestoreTestingPlan_additionals(t *testing.T) { ImportStateVerify: true, ImportStateId: rName, ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, }, }) } -func TestAccBackupRestoreTestingPlan_additionalwithupdates(t *testing.T) { +func TestAccBackupRestoreTestingPlan_additionalsWithUpdate(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingPlanOutput + var restoretestingplan awstypes.RestoreTestingPlanForGet resourceName := "aws_backup_restore_testing_plan.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") resource.ParallelTest(t, resource.TestCase{ @@ -297,7 +287,6 @@ func TestAccBackupRestoreTestingPlan_additionalwithupdates(t *testing.T) { ImportStateVerify: true, ImportStateId: rName, ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, { Config: testAccRestoreTestingPlanConfig_additionals("1", "cron(0 12 ? * * *)", rName), @@ -308,12 +297,10 @@ func TestAccBackupRestoreTestingPlan_additionalwithupdates(t *testing.T) { 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.exclude_vaults.#", "0"), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "1"), resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), - resource.TestCheckResourceAttr(resourceName, "schedule_expression_timezone", "Europe/London"), - resource.TestCheckResourceAttr(resourceName, "start_window_hours", "12"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "RestoreTestingPlan"), + resource.TestCheckResourceAttr(resourceName, "start_window_hours", "168"), ), }, }, @@ -322,72 +309,71 @@ func TestAccBackupRestoreTestingPlan_additionalwithupdates(t *testing.T) { func testAccCheckRestoreTestingPlanDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_backup_restore_testing_plan" { continue } - if rs.Primary.Attributes["name"] == "" { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, "unknown", errors.New("not set")) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) _, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) - if errs.IsA[*types.ResourceNotFoundException](err) { - return nil + + if tfresource.NotFound(err) { + continue } + if err != nil { - return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], err) + return err } - return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], errors.New("not destroyed")) + return fmt.Errorf("Backup Restore Testing Plan %s still exists", rs.Primary.Attributes["name"]) } return nil } } -func testAccCheckRestoreTestingPlanExists(ctx context.Context, name string, restoretestingplan *backup.GetRestoreTestingPlanOutput) resource.TestCheckFunc { +func testAccCheckRestoreTestingPlanExists(ctx context.Context, n string, v *awstypes.RestoreTestingPlanForGet) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not found")) - } - - if rs.Primary.Attributes["name"] == "" { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - resp, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) + + output, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) if err != nil { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, err) + return err } - *restoretestingplan = *resp + *v = *output return nil } } -func testAccRestoreTestingPlanConfig_base(rName string) string { +func testAccRestoreTestingPlanConfig_basic(rName string) string { return fmt.Sprintf(` -resource "aws_kms_key" "test" { - enable_key_rotation = true -} +resource "aws_backup_restore_testing_plan" "test" { + name = %[1]q -resource "aws_backup_vault" "test" { - name = "%[1]s" - kms_key_arn = aws_kms_key.test.arn + recovery_point_selection { + algorithm = "LATEST_WITHIN_WINDOW" + include_vaults = ["*"] + recovery_point_types = ["CONTINUOUS"] + } + + schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 } `, rName) } -func testAccRestoreTestingPlanConfig_basic(rName string) string { +func testAccRestoreTestingPlanConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_backup_restore_testing_plan" "test" { - name = "%[1]s" + name = %[1]q recovery_point_selection { algorithm = "LATEST_WITHIN_WINDOW" @@ -396,14 +382,18 @@ resource "aws_backup_restore_testing_plan" "test" { } schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 + + tags = { + %[2]q = %[3]q + } } -`, rName) +`, rName, tagKey1, tagValue1) } -func testAccRestoreTestingPlanConfig_tags(tagName, tagValue, rName string) string { +func testAccRestoreTestingPlanConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_backup_restore_testing_plan" "test" { - name = "%[3]s" + name = %[1]q recovery_point_selection { algorithm = "LATEST_WITHIN_WINDOW" @@ -414,16 +404,17 @@ resource "aws_backup_restore_testing_plan" "test" { schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 tags = { - "%[1]s" = "%[2]s" + %[2]q = %[3]q + %[4]q = %[5]q } } -`, tagName, tagValue, rName) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } func testAccRestoreTestingPlanConfig_additionals(selectionWindowDays, scheduleExpression, rName string) string { return fmt.Sprintf(` resource "aws_backup_restore_testing_plan" "test" { - name = "%[3]s" + name = %[3]q recovery_point_selection { algorithm = "LATEST_WITHIN_WINDOW" @@ -432,18 +423,33 @@ resource "aws_backup_restore_testing_plan" "test" { selection_window_days = %[1]s } - schedule_expression = "%[2]s" + schedule_expression = %[2]q start_window_hours = 168 } `, selectionWindowDays, scheduleExpression, rName) } +func testAccRestoreTestingPlanConfig_baseVaults(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + enable_key_rotation = true + description = %[1]q + deletion_window_in_days = 7 +} + +resource "aws_backup_vault" "test" { + name = %[1]q + kms_key_arn = aws_kms_key.test.arn +} +`, rName) +} + func testAccRestoreTestingPlanConfig_includeVaults(rName string) string { return acctest.ConfigCompose( - testAccRestoreTestingPlanConfig_base(rName), + testAccRestoreTestingPlanConfig_baseVaults(rName), fmt.Sprintf(` resource "aws_backup_restore_testing_plan" "test" { - name = "%[1]s" + name = %[1]q recovery_point_selection { algorithm = "LATEST_WITHIN_WINDOW" @@ -453,16 +459,15 @@ resource "aws_backup_restore_testing_plan" "test" { schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 } -`, rName), - ) +`, rName)) } func testAccRestoreTestingPlanConfig_excludeVaults(rName string) string { return acctest.ConfigCompose( - testAccRestoreTestingPlanConfig_base(rName), + testAccRestoreTestingPlanConfig_baseVaults(rName), fmt.Sprintf(` resource "aws_backup_restore_testing_plan" "test" { - name = "%[1]s" + name = %[1]q recovery_point_selection { algorithm = "LATEST_WITHIN_WINDOW" @@ -473,6 +478,5 @@ resource "aws_backup_restore_testing_plan" "test" { schedule_expression = "cron(0 12 ? * * *)" # Daily at 12:00 } -`, rName), - ) +`, rName)) } diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index 315edbd377b..479c134bd12 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -184,7 +184,7 @@ func (r *resourceRestoreTestingSelection) Create(ctx context.Context, req resour created, err := waitRestoreTestingSelectionLatest(ctx, conn, plan.RestoreTestingSelectionName.ValueString(), plan.RestoreTestingPlanName.ValueString(), r.CreateTimeout(ctx, plan.Timeouts)) if err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameRestoreTestingPlan, "", err), + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameRestoreTestingSelection, "", err), err.Error(), ) return @@ -243,7 +243,7 @@ func (r *resourceRestoreTestingSelection) Update(ctx context.Context, req resour if !state.RestoreTestingPlanName.Equal(plan.RestoreTestingPlanName) { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingPlan, plan.RestoreTestingPlanName.ValueString(), errors.New("name changes are not supported")), + create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingSelection, plan.RestoreTestingPlanName.ValueString(), errors.New("name changes are not supported")), "changing the name of a restore testing plan is not supported", ) return @@ -323,7 +323,7 @@ func (r *resourceRestoreTestingSelection) Delete(ctx context.Context, req resour return } resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameRestoreTestingPlan, state.RestoreTestingSelectionName.String(), err), + create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameRestoreTestingSelection, state.RestoreTestingSelectionName.String(), err), err.Error(), ) return @@ -331,7 +331,7 @@ func (r *resourceRestoreTestingSelection) Delete(ctx context.Context, req resour if _, err := waitRestoreTestingSelectionDeleted(ctx, conn, state.RestoreTestingSelectionName.ValueString(), state.RestoreTestingPlanName.ValueString(), r.DeleteTimeout(ctx, state.Timeouts)); err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForDeletion, ResNameRestoreTestingPlan, state.RestoreTestingSelectionName.String(), err), + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForDeletion, ResNameRestoreTestingSelection, state.RestoreTestingSelectionName.String(), err), err.Error(), ) return @@ -373,6 +373,11 @@ func findRestoreTestingSelectionByName(ctx context.Context, conn *backup.Client, return out, nil } +const ( + stateNormal = "NORMAL" + stateNotFound = "NOT_FOUND" +) + func statusRestoreSelection(ctx context.Context, conn *backup.Client, name, restoreTestingPlanName string) retry.StateRefreshFunc { return func() (interface{}, string, error) { output, err := findRestoreTestingSelectionByName(ctx, conn, name, restoreTestingPlanName) diff --git a/internal/service/backup/restore_testing_selection_test.go b/internal/service/backup/restore_testing_selection_test.go index 1c47ff66c38..78baddfe322 100644 --- a/internal/service/backup/restore_testing_selection_test.go +++ b/internal/service/backup/restore_testing_selection_test.go @@ -136,7 +136,7 @@ func testAccCheckRestoreTestingPlanSelection(ctx context.Context) resource.TestC } if rs.Primary.Attributes["name"] == "" { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, "unknown", errors.New("not set")) + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, "unknown", errors.New("not set")) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) @@ -145,10 +145,10 @@ func testAccCheckRestoreTestingPlanSelection(ctx context.Context) resource.TestC return nil } if err != nil { - return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], err) + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingSelection, rs.Primary.Attributes["name"], err) } - return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingPlan, rs.Primary.Attributes["name"], errors.New("not destroyed")) + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingSelection, rs.Primary.Attributes["name"], errors.New("not destroyed")) } return nil @@ -159,18 +159,18 @@ func testAccCheckRestoreTestingSelectionExists(ctx context.Context, name string, return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not found")) + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, name, errors.New("not found")) } if rs.Primary.Attributes["name"] == "" { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, errors.New("not set")) + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, name, errors.New("not set")) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) resp, err := tfbackup.FindRestoreTestingSelectionByName(ctx, conn, rs.Primary.Attributes["name"], rs.Primary.Attributes["restore_testing_plan_name"]) if err != nil { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingPlan, name, err) + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, name, err) } *restoretestingplan = *resp diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 6368e205d03..36334f38a82 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -28,16 +28,16 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic }, }, { - Factory: newResourceRestoreTestingPlan, + Factory: newResourceRestoreTestingSelection, + Name: "Restore Testing Plan Selection", + }, + { + Factory: newRestoreTestingPlanResource, Name: "Restore Testing Plan", Tags: &types.ServicePackageResourceTags{ IdentifierAttribute: names.AttrARN, }, }, - { - Factory: newResourceRestoreTestingSelection, - Name: "Restore Testing Plan Selection", - }, } } From 9e472f9ddd051d261519e5f76bb71611e3f77d89 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 3 Oct 2024 15:42:30 -0400 Subject: [PATCH 04/17] r/aws_backup_restore_testing_selection: Tidy up. --- internal/service/backup/exports_test.go | 6 +- .../backup/restore_testing_selection.go | 447 +++++++----------- .../backup/restore_testing_selection_test.go | 72 ++- .../service/backup/service_package_gen.go | 8 +- 4 files changed, 223 insertions(+), 310 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 5b490363172..8f8652efe7c 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -11,9 +11,9 @@ var ( ResourcePlan = resourcePlan ResourceRegionSettings = resourceRegionSettings ResourceReportPlan = resourceReportPlan - ResourceSelection = resourceSelection ResourceRestoreTestingPlan = newRestoreTestingPlanResource - RestoreTestingSelectionResource = newResourceRestoreTestingSelection + ResourceRestoreTestingSelection = newRestoreTestingSelectionResource + ResourceSelection = resourceSelection ResourceVault = resourceVault ResourceVaultLockConfiguration = resourceVaultLockConfiguration ResourceVaultNotifications = resourceVaultNotifications @@ -27,7 +27,7 @@ var ( FindRegionSettings = findRegionSettings FindReportPlanByName = findReportPlanByName FindRestoreTestingPlanByName = findRestoreTestingPlanByName - FindRestoreTestingSelectionByName = findRestoreTestingSelectionByName + FindRestoreTestingSelectionByTwoPartKey = findRestoreTestingSelectionByTwoPartKey FindSelectionByTwoPartKey = findSelectionByTwoPartKey FindVaultAccessPolicyByName = findVaultAccessPolicyByName FindVaultNotificationsByName = findVaultNotificationsByName diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index 479c134bd12..3d25e6593d7 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -5,67 +5,60 @@ package backup import ( "context" - "errors" "fmt" "strings" - "time" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/backup" awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" - "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + sdkid "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) // @FrameworkResource(name="Restore Testing Plan Selection") -func newResourceRestoreTestingSelection(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceRestoreTestingSelection{} - - r.SetDefaultCreateTimeout(5 * time.Minute) - r.SetDefaultUpdateTimeout(5 * time.Minute) - r.SetDefaultDeleteTimeout(5 * time.Minute) +func newRestoreTestingSelectionResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &restoreTestingSelectionResource{} return r, nil } -const ( - ResNameRestoreTestingSelection = "Restore Testing Selection" -) - -type resourceRestoreTestingSelection struct { +type restoreTestingSelectionResource struct { framework.ResourceWithConfigure - framework.WithTimeouts } -func (r *resourceRestoreTestingSelection) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_backup_restore_testing_selection" +func (*restoreTestingSelectionResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_backup_restore_testing_selection" } -func (r *resourceRestoreTestingSelection) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ + "iam_role_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + }, names.AttrName: schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -76,11 +69,10 @@ func (r *resourceRestoreTestingSelection) Schema(ctx context.Context, req resour stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9A-Za-z_]+$`), "must contain only alphanumeric characters, and underscores"), }, }, - "restore_testing_plan_name": schema.StringAttribute{ - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, + "protected_resource_arns": schema.ListAttribute{ + CustomType: fwtypes.ListOfARNType, + ElementType: fwtypes.ARNType, + Optional: true, }, "protected_resource_type": schema.StringAttribute{ Required: true, @@ -88,13 +80,19 @@ func (r *resourceRestoreTestingSelection) Schema(ctx context.Context, req resour stringplanmodifier.RequiresReplace(), }, }, - "iam_role_arn": schema.StringAttribute{ - Required: true, - }, "restore_metadata_overrides": schema.MapAttribute{ + CustomType: fwtypes.NewMapTypeOf[types.String](ctx), Optional: true, Computed: true, - CustomType: fwtypes.NewMapTypeOf[types.String](ctx), + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.UseStateForUnknown(), + }, + }, + "restore_testing_plan_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "validation_window_hours": schema.Int64Attribute{ Optional: true, @@ -108,342 +106,261 @@ func (r *resourceRestoreTestingSelection) Schema(ctx context.Context, req resour }, }, Blocks: map[string]schema.Block{ - "protected_resource_conditions": schema.SingleNestedBlock{ - CustomType: fwtypes.NewObjectTypeOf[protectedResourceConditionsModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Validators: []validator.Object{ - objectvalidator.IsRequired(), + "protected_resource_conditions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), }, - Attributes: map[string]schema.Attribute{ - "string_equals": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueMap](ctx), - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "string_equals": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Required: true, + }, + "value": schema.StringAttribute{ + Required: true, + }, + }, + }, }, - }, - "string_not_equals": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueMap](ctx), - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), + "string_not_equals": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Required: true, + }, + "value": schema.StringAttribute{ + Required: true, + }, + }, + }, }, }, }, }, - "timeouts": timeouts.Block(ctx, timeouts.Opts{ - Create: true, - Update: true, - Delete: true, - }), }, } } -func (r *resourceRestoreTestingSelection) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().BackupClient(ctx) - - var plan restoreTestingSelectionResourceModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingSelectionResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data restoreTestingSelectionResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - requestBody := &awstypes.RestoreTestingSelectionForCreate{} - resp.Diagnostics.Append(flex.Expand(ctx, plan, requestBody)...) - if resp.Diagnostics.HasError() { - return - } + conn := r.Meta().BackupClient(ctx) + restoreTestingPlanName := data.RestoreTestingPlanName.ValueString() + name := data.RestoreTestingSelectionName.ValueString() input := &backup.CreateRestoreTestingSelectionInput{ - RestoreTestingSelection: requestBody, - RestoreTestingPlanName: plan.RestoreTestingPlanName.ValueStringPointer(), + CreatorRequestId: aws.String(sdkid.UniqueId()), + RestoreTestingPlanName: aws.String(restoreTestingPlanName), + RestoreTestingSelection: &awstypes.RestoreTestingSelectionForCreate{}, } - - out, err := conn.CreateRestoreTestingSelection(ctx, input) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingSelection, plan.RestoreTestingSelectionName.ValueString(), err), - err.Error(), - ) + response.Diagnostics.Append(fwflex.Expand(ctx, data, input.RestoreTestingSelection)...) + if response.Diagnostics.HasError() { return } - if out == nil || out.CreationTime == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameRestoreTestingSelection, plan.RestoreTestingSelectionName.ValueString(), err), - errors.New("empty output").Error(), - ) + + _, err := conn.CreateRestoreTestingSelection(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("creating Backup Restore Testing Selection (%s)", name), err.Error()) + return } - // "wait" for creation.. this is to get the _optional_ values - created, err := waitRestoreTestingSelectionLatest(ctx, conn, plan.RestoreTestingSelectionName.ValueString(), plan.RestoreTestingPlanName.ValueString(), r.CreateTimeout(ctx, plan.Timeouts)) + // Set values for unknowns. + restoreTestingSelection, err := findRestoreTestingSelectionByTwoPartKey(ctx, conn, restoreTestingPlanName, name) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameRestoreTestingSelection, "", err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Backup Restore Testing Selection (%s)", name), err.Error()) + return } - // var state restoreTestingSelectionResourceModel - resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingSelection, &plan)...) - if resp.Diagnostics.HasError() { + response.Diagnostics.Append(fwflex.Flatten(ctx, restoreTestingSelection, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceRestoreTestingSelection) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().BackupClient(ctx) - - var state restoreTestingSelectionResourceModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingSelectionResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data restoreTestingSelectionResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - out, err := findRestoreTestingSelectionByName(ctx, conn, state.RestoreTestingSelectionName.ValueString(), state.RestoreTestingPlanName.ValueString()) + conn := r.Meta().BackupClient(ctx) + + restoreTestingPlanName := data.RestoreTestingPlanName.ValueString() + name := data.RestoreTestingSelectionName.ValueString() + restoreTestingSelection, err := findRestoreTestingSelectionByTwoPartKey(ctx, conn, restoreTestingPlanName, name) if tfresource.NotFound(err) { - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionSetting, ResNameRestoreTestingSelection, state.RestoreTestingSelectionName.ValueString(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Backup Restore Testing Selection (%s)", name), err.Error()) + return } - resp.Diagnostics.Append(flex.Flatten(ctx, out.RestoreTestingSelection, &state)...) - if resp.Diagnostics.HasError() { + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, restoreTestingSelection, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceRestoreTestingSelection) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().BackupClient(ctx) - - var state, plan restoreTestingSelectionResourceModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *restoreTestingSelectionResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new restoreTestingSelectionResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { return } - - if !state.RestoreTestingPlanName.Equal(plan.RestoreTestingPlanName) { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingSelection, plan.RestoreTestingPlanName.ValueString(), errors.New("name changes are not supported")), - "changing the name of a restore testing plan is not supported", - ) + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { return } - if !state.RestoreMetadataOverrides.Equal(plan.RestoreMetadataOverrides) || - !state.ValidationWindowHours.Equal(plan.ValidationWindowHours) || - !state.IamRoleArn.Equal(plan.IamRoleArn) || - !state.ProtectedResourceConditions.Equal(plan.ProtectedResourceConditions) { + conn := r.Meta().BackupClient(ctx) - in := &awstypes.RestoreTestingSelectionForUpdate{} - resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...) - if resp.Diagnostics.HasError() { + if !old.IAMRoleARN.Equal(new.IAMRoleARN) || + !old.ProtectedResourceConditions.Equal(new.ProtectedResourceConditions) || + !old.RestoreMetadataOverrides.Equal(new.RestoreMetadataOverrides) || + !old.ValidationWindowHours.Equal(new.ValidationWindowHours) { + restoreTestingPlanName := new.RestoreTestingPlanName.ValueString() + name := new.RestoreTestingSelectionName.ValueString() + input := &backup.UpdateRestoreTestingSelectionInput{ + RestoreTestingPlanName: aws.String(restoreTestingPlanName), + RestoreTestingSelection: &awstypes.RestoreTestingSelectionForUpdate{}, + RestoreTestingSelectionName: aws.String(name), + } + response.Diagnostics.Append(fwflex.Expand(ctx, new, input.RestoreTestingSelection)...) + if response.Diagnostics.HasError() { return } - out, err := conn.UpdateRestoreTestingSelection(ctx, &backup.UpdateRestoreTestingSelectionInput{ - RestoreTestingSelectionName: plan.RestoreTestingSelectionName.ValueStringPointer(), - RestoreTestingPlanName: plan.RestoreTestingPlanName.ValueStringPointer(), - RestoreTestingSelection: in, - }) + _, err := conn.UpdateRestoreTestingSelection(ctx, input) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingSelection, plan.RestoreTestingPlanName.ValueString(), err), - err.Error(), - ) - return - } - if out == nil || out.RestoreTestingPlanName == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionUpdating, ResNameRestoreTestingSelection, plan.RestoreTestingPlanName.ValueString(), nil), - errors.New("empty output").Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("updating Backup Restore Testing Selection (%s)", name), err.Error()) - // "wait" for update.. this is to get the _optional_ values - created, err := waitRestoreTestingSelectionLatest(ctx, conn, plan.RestoreTestingSelectionName.ValueString(), plan.RestoreTestingPlanName.ValueString(), r.UpdateTimeout(ctx, plan.Timeouts)) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForUpdate, ResNameRestoreTestingSelection, "", err), - err.Error(), - ) return } + } - resp.Diagnostics.Append(flex.Flatten(ctx, created.RestoreTestingSelection, &plan)...) - if resp.Diagnostics.HasError() { - return - } + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +func (r *restoreTestingSelectionResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data restoreTestingSelectionResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} -func (r *resourceRestoreTestingSelection) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { conn := r.Meta().BackupClient(ctx) - var state restoreTestingSelectionResourceModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } + restoreTestingPlanName := data.RestoreTestingPlanName.ValueString() + name := data.RestoreTestingSelectionName.ValueString() + _, err := conn.DeleteRestoreTestingSelection(ctx, &backup.DeleteRestoreTestingSelectionInput{ + RestoreTestingPlanName: aws.String(restoreTestingPlanName), + RestoreTestingSelectionName: aws.String(name), + }) - in := &backup.DeleteRestoreTestingSelectionInput{ - RestoreTestingSelectionName: state.RestoreTestingSelectionName.ValueStringPointer(), - RestoreTestingPlanName: state.RestoreTestingPlanName.ValueStringPointer(), + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return } - _, err := conn.DeleteRestoreTestingSelection(ctx, in) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return - } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameRestoreTestingSelection, state.RestoreTestingSelectionName.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("deleting Backup Restore Testing Selection (%s)", name), err.Error()) - if _, err := waitRestoreTestingSelectionDeleted(ctx, conn, state.RestoreTestingSelectionName.ValueString(), state.RestoreTestingPlanName.ValueString(), r.DeleteTimeout(ctx, state.Timeouts)); err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForDeletion, ResNameRestoreTestingSelection, state.RestoreTestingSelectionName.String(), err), - err.Error(), - ) return } } -func (r *resourceRestoreTestingSelection) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - parts := strings.Split(req.ID, ":") +func (r *restoreTestingSelectionResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + parts := strings.Split(request.ID, ":") if len(parts) != 2 { - resp.Diagnostics.AddError("Resource Import Invalid ID", fmt.Sprintf(`Unexpected format for import ID (%s), use: "RestoreTestingSelectionName:RestoreTestingPlanName"`, req.ID)) + response.Diagnostics.AddError("Resource Import Invalid ID", fmt.Sprintf(`Unexpected format for import ID (%s), use: "RestoreTestingSelectionName:RestoreTestingPlanName"`, request.ID)) return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrName), parts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("restore_testing_plan_name"), parts[1])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrName), parts[0])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("restore_testing_plan_name"), parts[1])...) } -func findRestoreTestingSelectionByName(ctx context.Context, conn *backup.Client, name string, restoreTestingPlanName string) (*backup.GetRestoreTestingSelectionOutput, error) { - in := &backup.GetRestoreTestingSelectionInput{ - RestoreTestingPlanName: aws.String(restoreTestingPlanName), - RestoreTestingSelectionName: aws.String(name), - } - - out, err := conn.GetRestoreTestingSelection(ctx, in) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - return nil, err +func (r *restoreTestingSelectionResource) ConfigValidators(_ context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.ExactlyOneOf( + path.MatchRoot("protected_resource_arns"), + path.MatchRoot("protected_resource_conditions"), + ), } +} - if out == nil || out.RestoreTestingSelection == nil { - return nil, tfresource.NewEmptyResultError(in) +func findRestoreTestingSelectionByTwoPartKey(ctx context.Context, conn *backup.Client, restoreTestingPlanName, restoreTestingSelectionName string) (*awstypes.RestoreTestingSelectionForGet, error) { + input := &backup.GetRestoreTestingSelectionInput{ + RestoreTestingPlanName: aws.String(restoreTestingPlanName), + RestoreTestingSelectionName: aws.String(restoreTestingSelectionName), } - return out, nil + return findRestoreTestingSelection(ctx, conn, input) } -const ( - stateNormal = "NORMAL" - stateNotFound = "NOT_FOUND" -) - -func statusRestoreSelection(ctx context.Context, conn *backup.Client, name, restoreTestingPlanName string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := findRestoreTestingSelectionByName(ctx, conn, name, restoreTestingPlanName) - if tfresource.NotFound(err) { - return nil, "", nil - } +func findRestoreTestingSelection(ctx context.Context, conn *backup.Client, input *backup.GetRestoreTestingSelectionInput) (*awstypes.RestoreTestingSelectionForGet, error) { + output, err := conn.GetRestoreTestingSelection(ctx, input) - if err != nil { - return nil, "", err + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } - - return output, stateNormal, nil } -} - -func waitRestoreTestingSelectionLatest(ctx context.Context, conn *backup.Client, name string, restoreTestingPlanName string, timeout time.Duration) (*backup.GetRestoreTestingSelectionOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{}, - Target: []string{stateNormal}, - Refresh: statusRestoreSelection(ctx, conn, name, restoreTestingPlanName), - Timeout: timeout, - } - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if out, ok := outputRaw.(*backup.GetRestoreTestingSelectionOutput); ok { - return out, err - } - - return nil, err -} - -func waitRestoreTestingSelectionDeleted(ctx context.Context, conn *backup.Client, name string, restoreTestingPlanName string, timeout time.Duration) (*backup.GetRestoreTestingSelectionOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{stateNormal}, - Target: []string{}, - Refresh: statusRestoreSelection(ctx, conn, name, restoreTestingPlanName), - Timeout: timeout, + if err != nil { + return nil, err } - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*backup.GetRestoreTestingSelectionOutput); ok { - return out, err + if output == nil || output.RestoreTestingSelection == nil { + return nil, tfresource.NewEmptyResultError(input) } - return nil, err - + return output.RestoreTestingSelection, nil } type restoreTestingSelectionResourceModel struct { - RestoreTestingSelectionName types.String `tfsdk:"name"` - RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` - ProtectedResourceConditions fwtypes.ObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` - IamRoleArn types.String `tfsdk:"iam_role_arn"` - ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` - ProtectedResourceType types.String `tfsdk:"protected_resource_type"` - RestoreMetadataOverrides fwtypes.MapValueOf[basetypes.StringValue] `tfsdk:"restore_metadata_overrides"` - Timeouts timeouts.Value `tfsdk:"timeouts"` + IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` + ProtectedResourceARNs fwtypes.ListValueOf[fwtypes.ARN] `tfsdk:"protected_resource_arns"` + ProtectedResourceConditions fwtypes.ListNestedObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` + ProtectedResourceType types.String `tfsdk:"protected_resource_type"` + RestoreMetadataOverrides fwtypes.MapValueOf[basetypes.StringValue] `tfsdk:"restore_metadata_overrides"` + RestoreTestingSelectionName types.String `tfsdk:"name"` + RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` + ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` } type protectedResourceConditionsModel struct { - StringEquals fwtypes.ListNestedObjectValueOf[keyValueMap] `tfsdk:"string_equals"` - StringNotEquals fwtypes.ListNestedObjectValueOf[keyValueMap] `tfsdk:"string_not_equals"` + StringEquals fwtypes.ListNestedObjectValueOf[keyValueModel] `tfsdk:"string_equals"` + StringNotEquals fwtypes.ListNestedObjectValueOf[keyValueModel] `tfsdk:"string_not_equals"` } -type keyValueMap struct { +type keyValueModel struct { Key types.String `tfsdk:"key"` Value types.String `tfsdk:"value"` } diff --git a/internal/service/backup/restore_testing_selection_test.go b/internal/service/backup/restore_testing_selection_test.go index 78baddfe322..3a2c0a3d304 100644 --- a/internal/service/backup/restore_testing_selection_test.go +++ b/internal/service/backup/restore_testing_selection_test.go @@ -5,29 +5,27 @@ package backup_test import ( "context" - "errors" "fmt" "strings" "testing" - "github.com/aws/aws-sdk-go-v2/service/backup" - "github.com/aws/aws-sdk-go-v2/service/backup/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" 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" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingSelectionOutput + var restoretestingplan awstypes.RestoreTestingSelectionForGet resourceName := "aws_backup_restore_testing_selection.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -60,9 +58,10 @@ func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { func TestAccBackupRestoreTestingSelection_disappears(t *testing.T) { ctx := acctest.Context(t) - var restoretestingselection backup.GetRestoreTestingSelectionOutput + var restoretestingselection awstypes.RestoreTestingSelectionForGet resourceName := "aws_backup_restore_testing_selection.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -76,7 +75,7 @@ func TestAccBackupRestoreTestingSelection_disappears(t *testing.T) { Config: testAccRestoreTestingSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingselection), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.RestoreTestingSelectionResource, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceRestoreTestingSelection, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -86,9 +85,10 @@ func TestAccBackupRestoreTestingSelection_disappears(t *testing.T) { func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { ctx := acctest.Context(t) - var restoretestingplan backup.GetRestoreTestingSelectionOutput + var restoretestingplan awstypes.RestoreTestingSelectionForGet resourceName := "aws_backup_restore_testing_selection.test" rName := strings.ReplaceAll(sdkacctest.RandomWithPrefix(acctest.ResourcePrefix), "-", "_") + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -135,45 +135,41 @@ func testAccCheckRestoreTestingPlanSelection(ctx context.Context) resource.TestC continue } - if rs.Primary.Attributes["name"] == "" { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, "unknown", errors.New("not set")) - } - conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - _, err := tfbackup.FindRestoreTestingSelectionByName(ctx, conn, rs.Primary.Attributes["name"], rs.Primary.Attributes["restore_testing_plan_name"]) - if errs.IsA[*types.ResourceNotFoundException](err) { - return nil + + _, err := tfbackup.FindRestoreTestingSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["restore_testing_plan_name"], rs.Primary.Attributes["name"]) + + if tfresource.NotFound(err) { + continue } + if err != nil { - return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingSelection, rs.Primary.Attributes["name"], err) + return err } - return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameRestoreTestingSelection, rs.Primary.Attributes["name"], errors.New("not destroyed")) + return fmt.Errorf("Backup Restore Testing Selection %s still exists", rs.Primary.Attributes["name"]) } return nil } } -func testAccCheckRestoreTestingSelectionExists(ctx context.Context, name string, restoretestingplan *backup.GetRestoreTestingSelectionOutput) resource.TestCheckFunc { +func testAccCheckRestoreTestingSelectionExists(ctx context.Context, n string, v *awstypes.RestoreTestingSelectionForGet) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, name, errors.New("not found")) - } - - if rs.Primary.Attributes["name"] == "" { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - resp, err := tfbackup.FindRestoreTestingSelectionByName(ctx, conn, rs.Primary.Attributes["name"], rs.Primary.Attributes["restore_testing_plan_name"]) + + output, err := tfbackup.FindRestoreTestingSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["restore_testing_plan_name"], rs.Primary.Attributes["name"]) if err != nil { - return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameRestoreTestingSelection, name, err) + return err } - *restoretestingplan = *resp + *v = *output return nil } @@ -182,7 +178,7 @@ func testAccCheckRestoreTestingSelectionExists(ctx context.Context, name string, func testAccRestoreTestingSelectionConfig_base(rName string) string { return fmt.Sprintf(` resource "aws_iam_role" "test" { - name = "%[1]s" + name = %[1]q assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -200,7 +196,9 @@ resource "aws_iam_role" "test" { } resource "aws_kms_key" "test" { - enable_key_rotation = true + enable_key_rotation = true + description = %[1]q + deletion_window_in_days = 7 } resource "aws_kms_alias" "a" { @@ -209,12 +207,12 @@ resource "aws_kms_alias" "a" { } resource "aws_backup_vault" "test" { - name = "%[1]s" + name = %[1]q kms_key_arn = aws_kms_key.test.arn } resource "aws_backup_restore_testing_plan" "test" { - name = "%[1]s" + name = "%[1]s_plan" recovery_point_selection { algorithm = "LATEST_WITHIN_WINDOW" @@ -232,7 +230,7 @@ func testAccRestoreTestingSelectionConfig_basic(rName string) string { testAccRestoreTestingSelectionConfig_base(rName), fmt.Sprintf(` resource "aws_backup_restore_testing_selection" "test" { - name = "%[1]s" + name = %[1]q restore_testing_plan_name = aws_backup_restore_testing_plan.test.name protected_resource_type = "EC2" @@ -241,8 +239,7 @@ resource "aws_backup_restore_testing_selection" "test" { protected_resource_conditions { } } -`, rName), - ) +`, rName)) } func testAccRestoreTestingSelectionConfig_updates(rName string) string { @@ -250,7 +247,7 @@ func testAccRestoreTestingSelectionConfig_updates(rName string) string { testAccRestoreTestingSelectionConfig_base(rName), fmt.Sprintf(` resource "aws_backup_restore_testing_selection" "test" { - name = "%[1]s" + name = %[1]q restore_testing_plan_name = aws_backup_restore_testing_plan.test.name protected_resource_type = "EC2" @@ -271,6 +268,5 @@ resource "aws_backup_restore_testing_selection" "test" { instanceType = "t2.micro" } } -`, rName), - ) +`, rName)) } diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 36334f38a82..8bc5ccc40c5 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -27,10 +27,6 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic IdentifierAttribute: names.AttrARN, }, }, - { - Factory: newResourceRestoreTestingSelection, - Name: "Restore Testing Plan Selection", - }, { Factory: newRestoreTestingPlanResource, Name: "Restore Testing Plan", @@ -38,6 +34,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic IdentifierAttribute: names.AttrARN, }, }, + { + Factory: newRestoreTestingSelectionResource, + Name: "Restore Testing Plan Selection", + }, } } From 75621351b1ad09b287ac28efaa0954188f325c96 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 08:20:11 -0400 Subject: [PATCH 05/17] r/aws_backup_restore_testing_selection: Add 'protected_resource_arns' to the documentation. --- .../backup_restore_testing_plan.html.markdown | 8 ------- ...up_restore_testing_selection.html.markdown | 21 ++++++------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/website/docs/r/backup_restore_testing_plan.html.markdown b/website/docs/r/backup_restore_testing_plan.html.markdown index e2304d16ca9..5e100c5969b 100644 --- a/website/docs/r/backup_restore_testing_plan.html.markdown +++ b/website/docs/r/backup_restore_testing_plan.html.markdown @@ -50,14 +50,6 @@ This resource exports the following attributes in addition to the arguments abov * `arn` - ARN of the Restore Testing Plan. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). -## Timeouts - -[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): - -* `create` - (Default `5m`) -* `update` - (Default `5m`) -* `delete` - (Default `5m`) - ## Import In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Backup Restore Testing Plan using the `name`. For example: diff --git a/website/docs/r/backup_restore_testing_selection.html.markdown b/website/docs/r/backup_restore_testing_selection.html.markdown index c2619ab2b8a..06218fa3187 100644 --- a/website/docs/r/backup_restore_testing_selection.html.markdown +++ b/website/docs/r/backup_restore_testing_selection.html.markdown @@ -56,35 +56,26 @@ resource "aws_backup_restore_testing_selection" "example" { The following arguments are supported: * `name` - (Required) The name of the backup restore testing selection. - * `restore_testing_plan_name` - (Required) The name of the restore testing plan. - * `protected_resource_type` - (Required) The type of the protected resource. - * `iam_role_arn` - (Required) The ARN of the IAM role. - -* `protected_resource_conditions` - (Required) The conditions for the protected resource. - +* `protected_resource_arns` - (Optional) The ARNs for the protected resources. +* `protected_resource_conditions` - (Optional) The conditions for the protected resource. * `restore_metadata_overrides` - (Optional) Override certain restore metadata keys. See the complete list of [restore testing inferred metadata](https://docs.aws.amazon.com/aws-backup/latest/devguide/restore-testing-inferred-metadata.html) . The `protected_resource_conditions` block supports the following arguments: * `string_equals` - (Optional) The list of string equals conditions for resource tags. Filters the values of your tagged resources for only those resources that you tagged with the same value. Also called "exact matching.". See [the structure for details](#keyvalues) - * `string_not_equals` - (Optional) The list of string not equals conditions for resource tags. Filters the values of your tagged resources for only those resources that you tagged that do not have the same value. Also called "negated matching.". See [the structure for details](#keyvalues) ### KeyValues -* `key` - The Tag name, must start with one of the following prefixes: [aws:ResourceTag/] with a Minimum length of 1. Maximum length of 128, and can contain characters that are letters, white space, and numbers that can be represented in UTF-8 and the following characters: `+ - = . _ : /`. -* `value` - The value of the Tag. Maximum length of 256. - -## Timeouts +* `key` - (Required) The Tag name, must start with one of the following prefixes: [aws:ResourceTag/] with a Minimum length of 1. Maximum length of 128, and can contain characters that are letters, white space, and numbers that can be represented in UTF-8 and the following characters: `+ - = . _ : /`. +* `value` - (Required) The value of the Tag. Maximum length of 256. -The following timeouts are available for this resource: +## Attribute Reference -* `create` - (Default `5m`) -* `update` - (Default `5m`) -* `delete` - (Default `5m`) +This resource exports no additional attributes. ## Import From e0f945fd8e3bb415d40da265f22716853584493a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 08:31:37 -0400 Subject: [PATCH 06/17] r/aws_backup_restore_testing_selection: 'protected_resource_arns' and 'protected_resource_conditions' are Computed. --- internal/framework/types/mapof.go | 4 ++ internal/framework/types/setof.go | 9 +++ .../backup/restore_testing_selection.go | 69 +++++++------------ 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index 21a53b29b2b..daaf391044b 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -100,6 +100,10 @@ type MapValueOf[T attr.Value] struct { basetypes.MapValue } +type ( + MapOfString MapValueOf[basetypes.StringValue] +) + func (v MapValueOf[T]) Equal(o attr.Value) bool { other, ok := o.(MapValueOf[T]) diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go index 357f1f63f8b..00cc92b2722 100644 --- a/internal/framework/types/setof.go +++ b/internal/framework/types/setof.go @@ -25,7 +25,11 @@ type setTypeOf[T attr.Value] struct { } var ( + // SetOfStringType is a custom type used for defining a Set of strings. SetOfStringType = setTypeOf[basetypes.StringValue]{basetypes.SetType{ElemType: basetypes.StringType{}}} + + // SetOfARNType is a custom type used for defining a Set of ARNs. + SetOfARNType = setTypeOf[ARN]{basetypes.SetType{ElemType: ARNType}} ) func NewSetTypeOf[T attr.Value](ctx context.Context) setTypeOf[T] { @@ -97,6 +101,11 @@ type SetValueOf[T attr.Value] struct { basetypes.SetValue } +type ( + SetOfString SetValueOf[basetypes.StringValue] + SetOfARN SetValueOf[ARN] +) + func (v SetValueOf[T]) Equal(o attr.Value) bool { other, ok := o.(SetValueOf[T]) diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index 3d25e6593d7..93bbb86edd8 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -20,12 +20,13 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" sdkid "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/errs" @@ -69,10 +70,28 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9A-Za-z_]+$`), "must contain only alphanumeric characters, and underscores"), }, }, - "protected_resource_arns": schema.ListAttribute{ - CustomType: fwtypes.ListOfARNType, + "protected_resource_arns": schema.SetAttribute{ + CustomType: fwtypes.SetOfARNType, ElementType: fwtypes.ARNType, Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, + "protected_resource_conditions": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + ElementType: types.ObjectType{ + AttrTypes: fwtypes.AttributeTypesMust[protectedResourceConditionsModel](ctx), + }, }, "protected_resource_type": schema.StringAttribute{ Required: true, @@ -81,7 +100,7 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re }, }, "restore_metadata_overrides": schema.MapAttribute{ - CustomType: fwtypes.NewMapTypeOf[types.String](ctx), + CustomType: fwtypes.MapOfStringType, Optional: true, Computed: true, PlanModifiers: []planmodifier.Map{ @@ -105,44 +124,6 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re }, }, }, - Blocks: map[string]schema.Block{ - "protected_resource_conditions": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), - Validators: []validator.List{ - listvalidator.SizeAtMost(1), - }, - NestedObject: schema.NestedBlockObject{ - Blocks: map[string]schema.Block{ - "string_equals": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "key": schema.StringAttribute{ - Required: true, - }, - "value": schema.StringAttribute{ - Required: true, - }, - }, - }, - }, - "string_not_equals": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "key": schema.StringAttribute{ - Required: true, - }, - "value": schema.StringAttribute{ - Required: true, - }, - }, - }, - }, - }, - }, - }, - }, } } @@ -346,10 +327,10 @@ func findRestoreTestingSelection(ctx context.Context, conn *backup.Client, input type restoreTestingSelectionResourceModel struct { IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` - ProtectedResourceARNs fwtypes.ListValueOf[fwtypes.ARN] `tfsdk:"protected_resource_arns"` + ProtectedResourceARNs fwtypes.SetOfARN `tfsdk:"protected_resource_arns"` ProtectedResourceConditions fwtypes.ListNestedObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` ProtectedResourceType types.String `tfsdk:"protected_resource_type"` - RestoreMetadataOverrides fwtypes.MapValueOf[basetypes.StringValue] `tfsdk:"restore_metadata_overrides"` + RestoreMetadataOverrides fwtypes.MapOfString `tfsdk:"restore_metadata_overrides"` RestoreTestingSelectionName types.String `tfsdk:"name"` RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` From e39c1649a3d524e5e48e8800cb0037ec328de0e5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 08:39:56 -0400 Subject: [PATCH 07/17] Restore 'internal/framework/validators/ARN'. --- internal/framework/validators/arn.go | 41 +++++++++++++++ internal/framework/validators/arn_test.go | 62 +++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 internal/framework/validators/arn.go create mode 100644 internal/framework/validators/arn_test.go diff --git a/internal/framework/validators/arn.go b/internal/framework/validators/arn.go new file mode 100644 index 00000000000..0ea1add39e2 --- /dev/null +++ b/internal/framework/validators/arn.go @@ -0,0 +1,41 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +type arnValidator struct{} + +func (validator arnValidator) Description(_ context.Context) string { + return "An Amazon Resource Name" +} + +func (validator arnValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +func (validator arnValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + if !arn.IsARN(request.ConfigValue.ValueString()) { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.Path, + validator.Description(ctx), + "value must be a valid ARN", + )) + return + } +} + +func ARN() validator.String { + return arnValidator{} +} diff --git a/internal/framework/validators/arn_test.go b/internal/framework/validators/arn_test.go new file mode 100644 index 00000000000..0da5fe2ea33 --- /dev/null +++ b/internal/framework/validators/arn_test.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + fwvalidators "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" +) + +func TestARNValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val types.String + expectError bool + } + + tests := map[string]testCase{ + "unknown String": { + val: types.StringUnknown(), + }, + "null String": { + val: types.StringNull(), + }, + "valid arn": { + val: types.StringValue("arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess"), + }, + "invalid_arn": { + val: types.StringValue("arn"), + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + + request := validator.StringRequest{ + Path: path.Root("test"), + PathExpression: path.MatchRoot("test"), + ConfigValue: test.val, + } + response := validator.StringResponse{} + fwvalidators.ARN().ValidateString(context.Background(), request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + }) + } +} From 82041e42c6c845450e662b6581c1d6eb5215a5bd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 08:53:14 -0400 Subject: [PATCH 08/17] backup: New sweepers. --- internal/framework/types/mapof.go | 2 +- internal/framework/types/setof.go | 4 +- internal/service/backup/sweep.go | 111 ++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index daaf391044b..693727088c9 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -101,7 +101,7 @@ type MapValueOf[T attr.Value] struct { } type ( - MapOfString MapValueOf[basetypes.StringValue] + MapOfString = MapValueOf[basetypes.StringValue] ) func (v MapValueOf[T]) Equal(o attr.Value) bool { diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go index 00cc92b2722..4cf81f00b2a 100644 --- a/internal/framework/types/setof.go +++ b/internal/framework/types/setof.go @@ -102,8 +102,8 @@ type SetValueOf[T attr.Value] struct { } type ( - SetOfString SetValueOf[basetypes.StringValue] - SetOfARN SetValueOf[ARN] + SetOfString = SetValueOf[basetypes.StringValue] + SetOfARN = SetValueOf[ARN] ) func (v SetValueOf[T]) Equal(o attr.Value) bool { diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index 12ea9a415b6..c1f8e404570 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/sweep" "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/framework" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -26,6 +27,19 @@ func RegisterSweepers() { F: sweepReportPlan, }) + resource.AddTestSweepers("aws_backup_restore_testing_plan", &resource.Sweeper{ + Name: "aws_backup_restore_testing_plan", + F: sweepRestoreTestingPlans, + Dependencies: []string{ + "aws_backup_restore_testing_selection", + }, + }) + + resource.AddTestSweepers("aws_backup_restore_testing_selection", &resource.Sweeper{ + Name: "aws_backup_restore_testing_selection", + F: sweepRestoreTestingSelections, + }) + resource.AddTestSweepers("aws_backup_vault_lock_configuration", &resource.Sweeper{ Name: "aws_backup_vault_lock_configuration", F: sweepVaultLockConfiguration, @@ -106,7 +120,7 @@ func sweepReportPlan(region string) error { page, err := pages.NextPage(ctx) if awsv2.SkipSweepError(err) { - log.Printf("[WARN] Skipping Backup Report Plans sweep for %s: %s", region, err) + log.Printf("[WARN] Skipping Backup Report Plan sweep for %s: %s", region, err) return nil } @@ -130,6 +144,95 @@ func sweepReportPlan(region string) error { return nil } +func sweepRestoreTestingPlans(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.ListRestoreTestingPlansInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := backup.NewListRestoreTestingPlansPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Backup Restore Testing Plan sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Backup Restore Testing Plans for %s: %w", region, err) + } + + for _, v := range page.RestoreTestingPlans { + sweepResources = append(sweepResources, framework.NewSweepResource(newRestoreTestingPlanResource, client, + framework.NewAttribute(names.AttrName, aws.ToString(v.RestoreTestingPlanName)))) + } + } + + if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { + return fmt.Errorf("error sweeping Backup Restore Testing Plans for %s: %w", region, err) + } + + return nil +} + +func sweepRestoreTestingSelections(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.ListRestoreTestingPlansInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := backup.NewListRestoreTestingPlansPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Backup Restore Testing Plan sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Backup Restore Testing Plans for %s: %w", region, err) + } + + for _, v := range page.RestoreTestingPlans { + restoreTestingPlanName := aws.ToString(v.RestoreTestingPlanName) + input := &backup.ListRestoreTestingSelectionsInput{ + RestoreTestingPlanName: aws.String(restoreTestingPlanName), + } + + pages := backup.NewListRestoreTestingSelectionsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + continue + } + + for _, v := range page.RestoreTestingSelections { + sweepResources = append(sweepResources, framework.NewSweepResource(newRestoreTestingSelectionResource, client, + framework.NewAttribute(names.AttrName, aws.ToString(v.RestoreTestingSelectionName)), + framework.NewAttribute("restore_testing_plan_name", restoreTestingPlanName))) + } + } + } + } + + if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { + return fmt.Errorf("error sweeping Backup Restore Testing Selections for %s: %w", region, err) + } + + return nil +} + func sweepVaultLockConfiguration(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) @@ -163,7 +266,7 @@ func sweepVaultLockConfiguration(region string) error { } if err = sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - return fmt.Errorf("error sweeping Backup Vault Lock Configuration for %s: %w", region, err) + return fmt.Errorf("error sweeping Backup Vault Lock Configurations for %s: %w", region, err) } return nil @@ -223,7 +326,7 @@ func sweepVaultPolicies(region string) error { page, err := pages.NextPage(ctx) if awsv2.SkipSweepError(err) { - log.Printf("[WARN] Skipping Backup Vault Policies sweep for %s: %s", region, err) + log.Printf("[WARN] Skipping Backup Vault Policy sweep for %s: %s", region, err) return nil } @@ -262,7 +365,7 @@ func sweepVaults(region string) error { page, err := pages.NextPage(ctx) if awsv2.SkipSweepError(err) { - log.Printf("[WARN] Skipping Backup Vaults sweep for %s: %s", region, err) + log.Printf("[WARN] Skipping Backup Vault sweep for %s: %s", region, err) return nil } From da63e9fd6488145c6b8f5322930c0d3fe5415148 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 09:14:39 -0400 Subject: [PATCH 09/17] r/aws_backup_restore_testing_selection: 'protected_resource_arns' can contain non-ARN strings. --- internal/service/backup/restore_testing_plan.go | 15 +++++++++++---- .../service/backup/restore_testing_selection.go | 16 +++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/internal/service/backup/restore_testing_plan.go b/internal/service/backup/restore_testing_plan.go index af392419895..76f81f03696 100644 --- a/internal/service/backup/restore_testing_plan.go +++ b/internal/service/backup/restore_testing_plan.go @@ -31,6 +31,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/framework" fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -110,7 +111,10 @@ func (r *restoreTestingPlanResource) Schema(ctx context.Context, request resourc Computed: true, Validators: []validator.Set{ setvalidator.ValueStringsAre( - stringvalidator.RegexMatches(regexache.MustCompile(`^arn:aws:backup:\w+(?:-\w+)+:\d{12}:backup-vault:[A-Za-z0-9_\-\*]+|\*$`), "must be either an AWS ARN for a backup vault or a *"), + stringvalidator.Any( + validators.ARN(), + stringvalidator.OneOf("*"), + ), ), }, PlanModifiers: []planmodifier.Set{ @@ -123,7 +127,10 @@ func (r *restoreTestingPlanResource) Schema(ctx context.Context, request resourc Required: true, Validators: []validator.Set{ setvalidator.ValueStringsAre( - stringvalidator.RegexMatches(regexache.MustCompile(`^arn:aws:backup:\w+(?:-\w+)+:\d{12}:backup-vault:[A-Za-z0-9_\-\*]+|\*$`), "must be either an AWS ARN for a backup vault or a *"), + stringvalidator.Any( + validators.ARN(), + stringvalidator.OneOf("*"), + ), ), }, }, @@ -342,8 +349,8 @@ type restoreTestingPlanResourceModel struct { type restoreRecoveryPointSelectionModel struct { Algorithm fwtypes.StringEnum[awstypes.RestoreTestingRecoveryPointSelectionAlgorithm] `tfsdk:"algorithm"` - ExcludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"exclude_vaults"` - IncludeVaults fwtypes.SetValueOf[types.String] `tfsdk:"include_vaults"` + ExcludeVaults fwtypes.SetOfString `tfsdk:"exclude_vaults"` + IncludeVaults fwtypes.SetOfString `tfsdk:"include_vaults"` RecoveryPointTypes fwtypes.SetValueOf[fwtypes.StringEnum[awstypes.RestoreTestingRecoveryPointType]] `tfsdk:"recovery_point_types"` SelectionWindowDays types.Int64 `tfsdk:"selection_window_days"` } diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index 93bbb86edd8..fef5a5466d9 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -34,6 +35,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/framework" fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -71,10 +73,18 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re }, }, "protected_resource_arns": schema.SetAttribute{ - CustomType: fwtypes.SetOfARNType, - ElementType: fwtypes.ARNType, + CustomType: fwtypes.SetOfStringType, + ElementType: types.StringType, Optional: true, Computed: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.Any( + validators.ARN(), + stringvalidator.OneOf("*"), + ), + ), + }, PlanModifiers: []planmodifier.Set{ setplanmodifier.UseStateForUnknown(), }, @@ -327,7 +337,7 @@ func findRestoreTestingSelection(ctx context.Context, conn *backup.Client, input type restoreTestingSelectionResourceModel struct { IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` - ProtectedResourceARNs fwtypes.SetOfARN `tfsdk:"protected_resource_arns"` + ProtectedResourceARNs fwtypes.SetOfString `tfsdk:"protected_resource_arns"` ProtectedResourceConditions fwtypes.ListNestedObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` ProtectedResourceType types.String `tfsdk:"protected_resource_type"` RestoreMetadataOverrides fwtypes.MapOfString `tfsdk:"restore_metadata_overrides"` From 35c29aaa5594888e567a765c48fab5f86cc014dd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 09:23:23 -0400 Subject: [PATCH 10/17] r/aws_backup_restore_testing_selection: 'protected_resource_conditions' back to a Block. --- .../backup/restore_testing_selection.go | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index fef5a5466d9..fd5cf81ad2a 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -13,7 +13,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/backup" awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -23,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -89,20 +89,6 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re setplanmodifier.UseStateForUnknown(), }, }, - "protected_resource_conditions": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, - Validators: []validator.List{ - listvalidator.SizeAtMost(1), - }, - ElementType: types.ObjectType{ - AttrTypes: fwtypes.AttributeTypesMust[protectedResourceConditionsModel](ctx), - }, - }, "protected_resource_type": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -134,6 +120,38 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re }, }, }, + Blocks: map[string]schema.Block{ + "protected_resource_conditions": schema.SingleNestedBlock{ + CustomType: fwtypes.NewObjectTypeOf[protectedResourceConditionsModel](ctx), + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Attributes: map[string]schema.Attribute{ + "string_equals": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + ElementType: types.ObjectType{ + AttrTypes: fwtypes.AttributeTypesMust[keyValueModel](ctx), + }, + }, + "string_not_equals": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + ElementType: types.ObjectType{ + AttrTypes: fwtypes.AttributeTypesMust[keyValueModel](ctx), + }, + }, + }, + }, + }, } } @@ -336,14 +354,14 @@ func findRestoreTestingSelection(ctx context.Context, conn *backup.Client, input } type restoreTestingSelectionResourceModel struct { - IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` - ProtectedResourceARNs fwtypes.SetOfString `tfsdk:"protected_resource_arns"` - ProtectedResourceConditions fwtypes.ListNestedObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` - ProtectedResourceType types.String `tfsdk:"protected_resource_type"` - RestoreMetadataOverrides fwtypes.MapOfString `tfsdk:"restore_metadata_overrides"` - RestoreTestingSelectionName types.String `tfsdk:"name"` - RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` - ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` + IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` + ProtectedResourceARNs fwtypes.SetOfString `tfsdk:"protected_resource_arns"` + ProtectedResourceConditions fwtypes.ObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` + ProtectedResourceType types.String `tfsdk:"protected_resource_type"` + RestoreMetadataOverrides fwtypes.MapOfString `tfsdk:"restore_metadata_overrides"` + RestoreTestingSelectionName types.String `tfsdk:"name"` + RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` + ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` } type protectedResourceConditionsModel struct { From b928bb8a227d72e2cd307fa3565bf99ea8013d2e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 09:23:38 -0400 Subject: [PATCH 11/17] Revert "Update CHANGELOG.md for #39561" This reverts commit 652a182b4c51b6ed536a76ca1d11256d8f7e3bbe. --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d05768a00..32a0cefeea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ NOTES: FEATURES: -* **New Resource:** `aws_backup_logically_air_gapped_vault` ([#39098](https://github.com/hashicorp/terraform-provider-aws/issues/39098)) * **New Resource:** `aws_ec2_transit_gateway_default_route_table_association` ([#39496](https://github.com/hashicorp/terraform-provider-aws/issues/39496)) * **New Resource:** `aws_ec2_transit_gateway_default_route_table_propagation` ([#39517](https://github.com/hashicorp/terraform-provider-aws/issues/39517)) * **New Resource:** `aws_iam_group_policies_exclusive` ([#39554](https://github.com/hashicorp/terraform-provider-aws/issues/39554)) @@ -28,7 +27,6 @@ BUG FIXES: * resource/aws_bedrockagent_agent: Fix "Provider produced inconsistent result after apply" error on update due to `customer_encryption_key_arn` not being passed during update ([#39565](https://github.com/hashicorp/terraform-provider-aws/issues/39565)) * resource/aws_bedrockagent_agent: Fix "Provider produced inconsistent result after apply" error on update due to `prompt_override_configuration` not being passed when not modified ([#39565](https://github.com/hashicorp/terraform-provider-aws/issues/39565)) -* resource/aws_bedrockagent_knowledge_base: Change `knowledge_base_configuration` and `storage_configuration` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew) ([#39567](https://github.com/hashicorp/terraform-provider-aws/issues/39567)) * resource/aws_ec2_transit_gateway_vpc_attachment: Remove default value for `security_group_referencing_support` argument and mark as Computed. This suppresses the diffs shown for resources created with v5.68.0 (or earlier) ([#39519](https://github.com/hashicorp/terraform-provider-aws/issues/39519)) * resource/aws_opensearchserverless_lifecycle_policy: Fix "Provider produced inconsistent result after apply" error on update due to `policy_version` computed attribute changing ([#39528](https://github.com/hashicorp/terraform-provider-aws/issues/39528)) * resource/aws_opensearchserverless_security_policy: Fix "Provider produced inconsistent result after apply" error on update due to `policy_version` computed attribute changing ([#39528](https://github.com/hashicorp/terraform-provider-aws/issues/39528)) From e1f0b141a4f732f4bac24ca7398ab6b886b73f83 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 09:25:13 -0400 Subject: [PATCH 12/17] Revert "Revert "Update CHANGELOG.md for #39561"" This reverts commit b928bb8a227d72e2cd307fa3565bf99ea8013d2e. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a0cefeea1..04d05768a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ NOTES: FEATURES: +* **New Resource:** `aws_backup_logically_air_gapped_vault` ([#39098](https://github.com/hashicorp/terraform-provider-aws/issues/39098)) * **New Resource:** `aws_ec2_transit_gateway_default_route_table_association` ([#39496](https://github.com/hashicorp/terraform-provider-aws/issues/39496)) * **New Resource:** `aws_ec2_transit_gateway_default_route_table_propagation` ([#39517](https://github.com/hashicorp/terraform-provider-aws/issues/39517)) * **New Resource:** `aws_iam_group_policies_exclusive` ([#39554](https://github.com/hashicorp/terraform-provider-aws/issues/39554)) @@ -27,6 +28,7 @@ BUG FIXES: * resource/aws_bedrockagent_agent: Fix "Provider produced inconsistent result after apply" error on update due to `customer_encryption_key_arn` not being passed during update ([#39565](https://github.com/hashicorp/terraform-provider-aws/issues/39565)) * resource/aws_bedrockagent_agent: Fix "Provider produced inconsistent result after apply" error on update due to `prompt_override_configuration` not being passed when not modified ([#39565](https://github.com/hashicorp/terraform-provider-aws/issues/39565)) +* resource/aws_bedrockagent_knowledge_base: Change `knowledge_base_configuration` and `storage_configuration` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew) ([#39567](https://github.com/hashicorp/terraform-provider-aws/issues/39567)) * resource/aws_ec2_transit_gateway_vpc_attachment: Remove default value for `security_group_referencing_support` argument and mark as Computed. This suppresses the diffs shown for resources created with v5.68.0 (or earlier) ([#39519](https://github.com/hashicorp/terraform-provider-aws/issues/39519)) * resource/aws_opensearchserverless_lifecycle_policy: Fix "Provider produced inconsistent result after apply" error on update due to `policy_version` computed attribute changing ([#39528](https://github.com/hashicorp/terraform-provider-aws/issues/39528)) * resource/aws_opensearchserverless_security_policy: Fix "Provider produced inconsistent result after apply" error on update due to `policy_version` computed attribute changing ([#39528](https://github.com/hashicorp/terraform-provider-aws/issues/39528)) From eb0de7152f9af99eb44d040539810d026db9d01c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 09:25:25 -0400 Subject: [PATCH 13/17] Revert "r/aws_backup_restore_testing_selection: 'protected_resource_conditions' back to a Block." This reverts commit 35c29aaa5594888e567a765c48fab5f86cc014dd. --- .../backup/restore_testing_selection.go | 64 +++++++------------ 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index fd5cf81ad2a..fef5a5466d9 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/backup" awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -22,7 +23,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -89,6 +89,20 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re setplanmodifier.UseStateForUnknown(), }, }, + "protected_resource_conditions": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + ElementType: types.ObjectType{ + AttrTypes: fwtypes.AttributeTypesMust[protectedResourceConditionsModel](ctx), + }, + }, "protected_resource_type": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -120,38 +134,6 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re }, }, }, - Blocks: map[string]schema.Block{ - "protected_resource_conditions": schema.SingleNestedBlock{ - CustomType: fwtypes.NewObjectTypeOf[protectedResourceConditionsModel](ctx), - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "string_equals": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, - ElementType: types.ObjectType{ - AttrTypes: fwtypes.AttributeTypesMust[keyValueModel](ctx), - }, - }, - "string_not_equals": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, - ElementType: types.ObjectType{ - AttrTypes: fwtypes.AttributeTypesMust[keyValueModel](ctx), - }, - }, - }, - }, - }, } } @@ -354,14 +336,14 @@ func findRestoreTestingSelection(ctx context.Context, conn *backup.Client, input } type restoreTestingSelectionResourceModel struct { - IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` - ProtectedResourceARNs fwtypes.SetOfString `tfsdk:"protected_resource_arns"` - ProtectedResourceConditions fwtypes.ObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` - ProtectedResourceType types.String `tfsdk:"protected_resource_type"` - RestoreMetadataOverrides fwtypes.MapOfString `tfsdk:"restore_metadata_overrides"` - RestoreTestingSelectionName types.String `tfsdk:"name"` - RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` - ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` + IAMRoleARN fwtypes.ARN `tfsdk:"iam_role_arn"` + ProtectedResourceARNs fwtypes.SetOfString `tfsdk:"protected_resource_arns"` + ProtectedResourceConditions fwtypes.ListNestedObjectValueOf[protectedResourceConditionsModel] `tfsdk:"protected_resource_conditions"` + ProtectedResourceType types.String `tfsdk:"protected_resource_type"` + RestoreMetadataOverrides fwtypes.MapOfString `tfsdk:"restore_metadata_overrides"` + RestoreTestingSelectionName types.String `tfsdk:"name"` + RestoreTestingPlanName types.String `tfsdk:"restore_testing_plan_name"` + ValidationWindowHours types.Int64 `tfsdk:"validation_window_hours"` } type protectedResourceConditionsModel struct { From 516df6d0bea062e936ace18ce67e0a0663b5b99d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 14:30:02 -0400 Subject: [PATCH 14/17] r/aws_backup_restore_testing_selection: 'protected_resource_conditions' back to a Block and nil-out default value. --- .../backup/restore_testing_selection.go | 89 +++++++++++++++---- .../backup/restore_testing_selection_test.go | 31 +++---- ...up_restore_testing_selection.html.markdown | 16 ++-- 3 files changed, 93 insertions(+), 43 deletions(-) diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index fef5a5466d9..c34038064ff 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -21,7 +21,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" @@ -89,20 +88,6 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re setplanmodifier.UseStateForUnknown(), }, }, - "protected_resource_conditions": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, - Validators: []validator.List{ - listvalidator.SizeAtMost(1), - }, - ElementType: types.ObjectType{ - AttrTypes: fwtypes.AttributeTypesMust[protectedResourceConditionsModel](ctx), - }, - }, "protected_resource_type": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -134,6 +119,44 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re }, }, }, + Blocks: map[string]schema.Block{ + "protected_resource_conditions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[protectedResourceConditionsModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "string_equals": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Required: true, + }, + "value": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + "string_not_equals": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Required: true, + }, + "value": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, } } @@ -175,6 +198,24 @@ func (r *restoreTestingSelectionResource) Create(ctx context.Context, request re return } + if v := restoreTestingSelection.ProtectedResourceConditions; v != nil { + // The default is + // + // "ProtectedResourceConditions": { + // "StringEquals": [], + // "StringNotEquals": [] + // }, + if len(v.StringEquals) == 0 { + v.StringEquals = nil + } + if len(v.StringNotEquals) == 0 { + v.StringNotEquals = nil + } + if v.StringEquals == nil && v.StringNotEquals == nil { + restoreTestingSelection.ProtectedResourceConditions = nil + } + } + response.Diagnostics.Append(fwflex.Flatten(ctx, restoreTestingSelection, &data)...) if response.Diagnostics.HasError() { return @@ -209,6 +250,24 @@ func (r *restoreTestingSelectionResource) Read(ctx context.Context, request reso return } + if v := restoreTestingSelection.ProtectedResourceConditions; v != nil { + // The default is + // + // "ProtectedResourceConditions": { + // "StringEquals": [], + // "StringNotEquals": [] + // }, + if len(v.StringEquals) == 0 { + v.StringEquals = nil + } + if len(v.StringNotEquals) == 0 { + v.StringNotEquals = nil + } + if v.StringEquals == nil && v.StringNotEquals == nil { + restoreTestingSelection.ProtectedResourceConditions = nil + } + } + // Set attributes for import. response.Diagnostics.Append(fwflex.Flatten(ctx, restoreTestingSelection, &data)...) if response.Diagnostics.HasError() { diff --git a/internal/service/backup/restore_testing_selection_test.go b/internal/service/backup/restore_testing_selection_test.go index 3a2c0a3d304..0791ddb480a 100644 --- a/internal/service/backup/restore_testing_selection_test.go +++ b/internal/service/backup/restore_testing_selection_test.go @@ -33,14 +33,14 @@ func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckRestoreTestingPlanSelection(ctx), + CheckDestroy: testAccCheckRestoreTestingSelectionDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccRestoreTestingSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName), + resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName+"_plan"), resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), ), }, @@ -48,7 +48,7 @@ func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateId: fmt.Sprintf("%s:%s", rName, rName), + ImportStateId: fmt.Sprintf("%s:%s", rName, rName+"_plan"), ImportStateVerifyIdentifierAttribute: "name", ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, @@ -69,7 +69,7 @@ func TestAccBackupRestoreTestingSelection_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckRestoreTestingPlanDestroy(ctx), + CheckDestroy: testAccCheckRestoreTestingSelectionDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccRestoreTestingSelectionConfig_basic(rName), @@ -96,14 +96,14 @@ func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckRestoreTestingPlanSelection(ctx), + CheckDestroy: testAccCheckRestoreTestingSelectionDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccRestoreTestingSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName), + resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName+"_plan"), resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), ), }, @@ -111,7 +111,7 @@ func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateId: fmt.Sprintf("%s:%s", rName, rName), + ImportStateId: fmt.Sprintf("%s:%s", rName, rName+"_plan"), ImportStateVerifyIdentifierAttribute: "name", ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, }, @@ -120,7 +120,7 @@ func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName), + resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName+"_plan"), resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), ), }, @@ -128,7 +128,7 @@ func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { }) } -func testAccCheckRestoreTestingPlanSelection(ctx context.Context) resource.TestCheckFunc { +func testAccCheckRestoreTestingSelectionDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "aws_backup_restore_testing_selection" { @@ -236,8 +236,7 @@ resource "aws_backup_restore_testing_selection" "test" { protected_resource_type = "EC2" iam_role_arn = aws_iam_role.test.arn - protected_resource_conditions { - } + protected_resource_arns = ["*"] } `, rName)) } @@ -254,12 +253,10 @@ resource "aws_backup_restore_testing_selection" "test" { iam_role_arn = aws_iam_role.test.arn protected_resource_conditions { - string_equals = [ - { - key = "aws:ResourceTag/backup" - value = true - } - ] + string_equals { + key = "aws:ResourceTag/backup" + value = true + } } validation_window_hours = 10 diff --git a/website/docs/r/backup_restore_testing_selection.html.markdown b/website/docs/r/backup_restore_testing_selection.html.markdown index 06218fa3187..f5c7929af9b 100644 --- a/website/docs/r/backup_restore_testing_selection.html.markdown +++ b/website/docs/r/backup_restore_testing_selection.html.markdown @@ -22,10 +22,7 @@ resource "aws_backup_restore_testing_selection" "example" { protected_resource_type = "EC2" iam_role_arn = aws_iam_role.example.arn - protected_resource_conditions { - string_equals = [] - string_not_equals = [] - } + protected_resource_arns = ["*"] } ``` @@ -40,13 +37,10 @@ resource "aws_backup_restore_testing_selection" "example" { iam_role_arn = aws_iam_role.example.arn protected_resource_conditions { - string_equals = [ - { - key = "Backup Test", - value = true - } - ] - string_not_equals = [] + string_equals { + key = "aws:ResourceTag/backup" + value = true + } } } ``` From 3595a4ff0efccc5520437fd38c38cc307045b7ef Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 14:31:20 -0400 Subject: [PATCH 15/17] Run 'make fix-constants PKG=backup'. --- .../service/backup/restore_testing_plan.go | 2 +- .../backup/restore_testing_plan_test.go | 90 +++++++++---------- .../backup/restore_testing_selection.go | 10 +-- .../backup/restore_testing_selection_test.go | 20 ++--- 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/internal/service/backup/restore_testing_plan.go b/internal/service/backup/restore_testing_plan.go index 76f81f03696..b324c80f365 100644 --- a/internal/service/backup/restore_testing_plan.go +++ b/internal/service/backup/restore_testing_plan.go @@ -67,7 +67,7 @@ func (r *restoreTestingPlanResource) Schema(ctx context.Context, request resourc stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9A-Za-z_]+$`), "must contain only alphanumeric characters, and underscores"), }, }, - "schedule_expression": schema.StringAttribute{ + names.AttrScheduleExpression: schema.StringAttribute{ Required: true, }, "schedule_expression_timezone": schema.StringAttribute{ diff --git a/internal/service/backup/restore_testing_plan_test.go b/internal/service/backup/restore_testing_plan_test.go index 796d5884c30..55f4e570afb 100644 --- a/internal/service/backup/restore_testing_plan_test.go +++ b/internal/service/backup/restore_testing_plan_test.go @@ -39,13 +39,13 @@ func TestAccBackupRestoreTestingPlan_basic(t *testing.T) { Config: testAccRestoreTestingPlanConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), 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.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), // no tags + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, names.AttrScheduleExpression, "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), // no tags ), }, { @@ -53,7 +53,7 @@ func TestAccBackupRestoreTestingPlan_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: rName, - ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIdentifierAttribute: names.AttrName, }, }, }) @@ -113,7 +113,7 @@ func TestAccBackupRestoreTestingPlan_tags(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: rName, - ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIdentifierAttribute: names.AttrName, }, { Config: testAccRestoreTestingPlanConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), @@ -154,13 +154,13 @@ func TestAccBackupRestoreTestingPlan_includeVaults(t *testing.T) { Config: testAccRestoreTestingPlanConfig_includeVaults(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), 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.include_vaults.#", acctest.Ct1), acctest.CheckResourceAttrRegionalARN(resourceName, "recovery_point_selection.0.include_vaults.0", "backup", fmt.Sprintf("backup-vault:%s", rName)), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), - resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, names.AttrScheduleExpression, "cron(0 12 ? * * *)"), ), }, { @@ -168,7 +168,7 @@ func TestAccBackupRestoreTestingPlan_includeVaults(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: rName, - ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIdentifierAttribute: names.AttrName, }, }, }) @@ -192,13 +192,13 @@ func TestAccBackupRestoreTestingPlan_excludeVaults(t *testing.T) { Config: testAccRestoreTestingPlanConfig_excludeVaults(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.algorithm", "LATEST_WITHIN_WINDOW"), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", acctest.Ct1), acctest.CheckResourceAttrRegionalARN(resourceName, "recovery_point_selection.0.exclude_vaults.0", "backup", fmt.Sprintf("backup-vault:%s", rName)), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "1"), - resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, names.AttrScheduleExpression, "cron(0 12 ? * * *)"), ), }, { @@ -206,7 +206,7 @@ func TestAccBackupRestoreTestingPlan_excludeVaults(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: rName, - ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIdentifierAttribute: names.AttrName, }, }, }) @@ -230,14 +230,14 @@ func TestAccBackupRestoreTestingPlan_additionals(t *testing.T) { Config: testAccRestoreTestingPlanConfig_additionals("365", "cron(0 12 ? * * *)", rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), 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.exclude_vaults.#", "0"), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", acctest.Ct2), resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "365"), - resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, names.AttrScheduleExpression, "cron(0 12 ? * * *)"), resource.TestCheckResourceAttr(resourceName, "start_window_hours", "168"), ), }, @@ -246,7 +246,7 @@ func TestAccBackupRestoreTestingPlan_additionals(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: rName, - ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIdentifierAttribute: names.AttrName, }, }, }) @@ -270,14 +270,14 @@ func TestAccBackupRestoreTestingPlan_additionalsWithUpdate(t *testing.T) { Config: testAccRestoreTestingPlanConfig_additionals("365", "cron(0 1 ? * * *)", rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), 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.exclude_vaults.#", "0"), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", acctest.Ct2), resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "365"), - resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 1 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, names.AttrScheduleExpression, "cron(0 1 ? * * *)"), resource.TestCheckResourceAttr(resourceName, "start_window_hours", "168"), ), }, @@ -286,20 +286,20 @@ func TestAccBackupRestoreTestingPlan_additionalsWithUpdate(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: rName, - ImportStateVerifyIdentifierAttribute: "name", + ImportStateVerifyIdentifierAttribute: names.AttrName, }, { - Config: testAccRestoreTestingPlanConfig_additionals("1", "cron(0 12 ? * * *)", rName), + Config: testAccRestoreTestingPlanConfig_additionals(acctest.Ct1, "cron(0 12 ? * * *)", rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingPlanExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), 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.exclude_vaults.#", "0"), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", "2"), - resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", "1"), - resource.TestCheckResourceAttr(resourceName, "schedule_expression", "cron(0 12 ? * * *)"), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.include_vaults.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.exclude_vaults.#", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.recovery_point_types.#", acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, "recovery_point_selection.0.selection_window_days", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, names.AttrScheduleExpression, "cron(0 12 ? * * *)"), resource.TestCheckResourceAttr(resourceName, "start_window_hours", "168"), ), }, @@ -316,7 +316,7 @@ func testAccCheckRestoreTestingPlanDestroy(ctx context.Context) resource.TestChe continue } - _, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) + _, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes[names.AttrName]) if tfresource.NotFound(err) { continue @@ -326,7 +326,7 @@ func testAccCheckRestoreTestingPlanDestroy(ctx context.Context) resource.TestChe return err } - return fmt.Errorf("Backup Restore Testing Plan %s still exists", rs.Primary.Attributes["name"]) + return fmt.Errorf("Backup Restore Testing Plan %s still exists", rs.Primary.Attributes[names.AttrName]) } return nil @@ -342,7 +342,7 @@ func testAccCheckRestoreTestingPlanExists(ctx context.Context, n string, v *awst conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - output, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes["name"]) + output, err := tfbackup.FindRestoreTestingPlanByName(ctx, conn, rs.Primary.Attributes[names.AttrName]) if err != nil { return err diff --git a/internal/service/backup/restore_testing_selection.go b/internal/service/backup/restore_testing_selection.go index c34038064ff..a6e5969749d 100644 --- a/internal/service/backup/restore_testing_selection.go +++ b/internal/service/backup/restore_testing_selection.go @@ -57,7 +57,7 @@ func (*restoreTestingSelectionResource) Metadata(_ context.Context, request reso func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "iam_role_arn": schema.StringAttribute{ + names.AttrIAMRoleARN: schema.StringAttribute{ CustomType: fwtypes.ARNType, Required: true, }, @@ -131,10 +131,10 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "key": schema.StringAttribute{ + names.AttrKey: schema.StringAttribute{ Required: true, }, - "value": schema.StringAttribute{ + names.AttrValue: schema.StringAttribute{ Required: true, }, }, @@ -144,10 +144,10 @@ func (r *restoreTestingSelectionResource) Schema(ctx context.Context, request re CustomType: fwtypes.NewListNestedObjectTypeOf[keyValueModel](ctx), NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "key": schema.StringAttribute{ + names.AttrKey: schema.StringAttribute{ Required: true, }, - "value": schema.StringAttribute{ + names.AttrValue: schema.StringAttribute{ Required: true, }, }, diff --git a/internal/service/backup/restore_testing_selection_test.go b/internal/service/backup/restore_testing_selection_test.go index 0791ddb480a..150eec5d062 100644 --- a/internal/service/backup/restore_testing_selection_test.go +++ b/internal/service/backup/restore_testing_selection_test.go @@ -39,7 +39,7 @@ func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { Config: testAccRestoreTestingSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName+"_plan"), resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), ), @@ -49,8 +49,8 @@ func TestAccBackupRestoreTestingSelection_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: fmt.Sprintf("%s:%s", rName, rName+"_plan"), - ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + ImportStateVerifyIdentifierAttribute: names.AttrName, + ImportStateVerifyIgnore: []string{names.AttrApplyImmediately, "user"}, }, }, }) @@ -102,7 +102,7 @@ func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { Config: testAccRestoreTestingSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName+"_plan"), resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), ), @@ -112,14 +112,14 @@ func TestAccBackupRestoreTestingSelection_updates(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateId: fmt.Sprintf("%s:%s", rName, rName+"_plan"), - ImportStateVerifyIdentifierAttribute: "name", - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + ImportStateVerifyIdentifierAttribute: names.AttrName, + ImportStateVerifyIgnore: []string{names.AttrApplyImmediately, "user"}, }, { Config: testAccRestoreTestingSelectionConfig_updates(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRestoreTestingSelectionExists(ctx, resourceName, &restoretestingplan), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "restore_testing_plan_name", rName+"_plan"), resource.TestCheckResourceAttr(resourceName, "protected_resource_type", "EC2"), ), @@ -137,7 +137,7 @@ func testAccCheckRestoreTestingSelectionDestroy(ctx context.Context) resource.Te conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - _, err := tfbackup.FindRestoreTestingSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["restore_testing_plan_name"], rs.Primary.Attributes["name"]) + _, err := tfbackup.FindRestoreTestingSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["restore_testing_plan_name"], rs.Primary.Attributes[names.AttrName]) if tfresource.NotFound(err) { continue @@ -147,7 +147,7 @@ func testAccCheckRestoreTestingSelectionDestroy(ctx context.Context) resource.Te return err } - return fmt.Errorf("Backup Restore Testing Selection %s still exists", rs.Primary.Attributes["name"]) + return fmt.Errorf("Backup Restore Testing Selection %s still exists", rs.Primary.Attributes[names.AttrName]) } return nil @@ -163,7 +163,7 @@ func testAccCheckRestoreTestingSelectionExists(ctx context.Context, n string, v conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - output, err := tfbackup.FindRestoreTestingSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["restore_testing_plan_name"], rs.Primary.Attributes["name"]) + output, err := tfbackup.FindRestoreTestingSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["restore_testing_plan_name"], rs.Primary.Attributes[names.AttrName]) if err != nil { return err From ba9cc6c409e817071c3044e61eeeb573941f4598 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 15:01:02 -0400 Subject: [PATCH 16/17] Fix golangci-lint 'copyloopvar'. --- internal/framework/validators/arn_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/framework/validators/arn_test.go b/internal/framework/validators/arn_test.go index 0da5fe2ea33..c1acb45a455 100644 --- a/internal/framework/validators/arn_test.go +++ b/internal/framework/validators/arn_test.go @@ -38,7 +38,6 @@ func TestARNValidator(t *testing.T) { } for name, test := range tests { - name, test := name, test t.Run(name, func(t *testing.T) { t.Parallel() From 14eaa5da95463f1103e568a89ccfa6fb9f686bac Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 4 Oct 2024 15:03:12 -0400 Subject: [PATCH 17/17] Fix tfproviderdocs 'import section should conclude with'. --- .../docs/r/backup_restore_testing_selection.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/backup_restore_testing_selection.html.markdown b/website/docs/r/backup_restore_testing_selection.html.markdown index f5c7929af9b..454af639818 100644 --- a/website/docs/r/backup_restore_testing_selection.html.markdown +++ b/website/docs/r/backup_restore_testing_selection.html.markdown @@ -73,16 +73,16 @@ This resource exports no additional attributes. ## Import -To import an existing backup restore testing selection, use the following `import` block: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Backup Restore Testing Selection using `name:restore_testing_plan_name`. For example: -``` +```terraform import { to = aws_backup_restore_testing_selection.example id = "my_testing_selection:my_testing_plan" } ``` -Using `terraform import`, import Backup Restore Testing Selection using the `name:restore_testing_plan_name`. For example: +Using `terraform import`, import Backup Restore Testing Selection using `name:restore_testing_plan_name`. For example: ```console % terraform import aws_backup_restore_testing_selection.example restore_testing_selection_12345678:restore_testing_plan_12345678