From 2c0944f8983fc655c79811418a700494324a2100 Mon Sep 17 00:00:00 2001 From: aristosvo <8375124+aristosvo@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:45:16 +0200 Subject: [PATCH 01/27] [New Resource] aws_backup_logically_air_gapped_vault --- internal/service/backup/exports_test.go | 5 +- .../backup/logically_air_gapped_vault.go | 262 ++++++++++++++++++ .../backup/logically_air_gapped_vault_test.go | 237 ++++++++++++++++ .../service/backup/service_package_gen.go | 10 +- ...p_logically_air_gapped_vault.html.markdown | 73 +++++ 5 files changed, 584 insertions(+), 3 deletions(-) create mode 100644 internal/service/backup/logically_air_gapped_vault.go create mode 100644 internal/service/backup/logically_air_gapped_vault_test.go create mode 100644 website/docs/r/backup_logically_air_gapped_vault.html.markdown diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 4466e1cf367..e420624ccbd 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -5,6 +5,7 @@ package backup // Exports for use in tests only. var ( - FindVaultAccessPolicyByName = findVaultAccessPolicyByName - FindVaultByName = findVaultByName + FindVaultAccessPolicyByName = findVaultAccessPolicyByName + FindVaultByName = findVaultByName + ResourceLogicallyAirGappedVault = newResourceLogicallyAirGappedVault ) diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go new file mode 100644 index 00000000000..cd91e07c6e6 --- /dev/null +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -0,0 +1,262 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup + +import ( + "context" + "errors" + "time" + + "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/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/stringplanmodifier" + "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/enum" + "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" + 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("aws_backup_logically_air_gapped_vault", name="Logically Air Gapped Vault") +// @Tags(identifierAttribute="arn") +func newResourceLogicallyAirGappedVault(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceLogicallyAirGappedVault{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +const ( + ResNameLogicallyAirGappedVault = "Logically Air Gapped Vault" +) + +type resourceLogicallyAirGappedVault struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceLogicallyAirGappedVault) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_backup_logically_air_gapped_vault" +} + +func (r *resourceLogicallyAirGappedVault) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrID: framework.IDAttribute(), + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "max_retention_days": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "min_retention_days": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *resourceLogicallyAirGappedVault) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().BackupClient(ctx) + + var plan resourceLogicallyAirGappedVaultData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := &backup.CreateLogicallyAirGappedBackupVaultInput{ + BackupVaultName: plan.BackupVaultName.ValueStringPointer(), + MaxRetentionDays: plan.MaxRetentionDays.ValueInt64Pointer(), + MinRetentionDays: plan.MinRetentionDays.ValueInt64Pointer(), + BackupVaultTags: getTagsIn(ctx), + } + + out, err := conn.CreateLogicallyAirGappedBackupVault(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameLogicallyAirGappedVault, plan.BackupVaultName.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameLogicallyAirGappedVault, plan.BackupVaultName.String(), nil), + errors.New("empty output").Error(), + ) + return + } + + plan.ID = flex.StringToFramework(ctx, out.BackupVaultName) + plan.ARN = flex.StringToFramework(ctx, out.BackupVaultArn) + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + _, err = waitLogicallyAirGappedVaultCreated(ctx, conn, plan.ID.ValueString(), createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameLogicallyAirGappedVault, plan.BackupVaultName.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceLogicallyAirGappedVault) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().BackupClient(ctx) + + var state resourceLogicallyAirGappedVaultData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findVaultByName(ctx, conn, state.ID.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionSetting, ResNameLogicallyAirGappedVault, state.ID.String(), err), + err.Error(), + ) + return + } + + state.ARN = flex.StringToFramework(ctx, out.BackupVaultArn) + state.BackupVaultName = flex.StringToFramework(ctx, out.BackupVaultName) + state.MaxRetentionDays = flex.Int64ToFramework(ctx, out.MaxRetentionDays) + state.MinRetentionDays = flex.Int64ToFramework(ctx, out.MinRetentionDays) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceLogicallyAirGappedVault) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state resourceLogicallyAirGappedVaultData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // Tags only. + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceLogicallyAirGappedVault) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().BackupClient(ctx) + + var state resourceLogicallyAirGappedVaultData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &backup.DeleteBackupVaultInput{ + BackupVaultName: aws.String(state.ID.ValueString()), + } + + _, err := conn.DeleteBackupVault(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) || errs.MessageContains(err, "AccessDeniedException", "Insufficient privileges to perform this action") { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameLogicallyAirGappedVault, state.ID.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceLogicallyAirGappedVault) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func (r *resourceLogicallyAirGappedVault) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +type resourceLogicallyAirGappedVaultData struct { + ARN types.String `tfsdk:"arn"` + ID types.String `tfsdk:"id"` + MaxRetentionDays types.Int64 `tfsdk:"max_retention_days"` + MinRetentionDays types.Int64 `tfsdk:"min_retention_days"` + BackupVaultName types.String `tfsdk:"name"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` +} + +func waitLogicallyAirGappedVaultCreated(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeBackupVaultOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.VaultStateCreating), + Target: enum.Slice(awstypes.VaultStateAvailable), + Refresh: statusLogicallyAirGappedVault(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*backup.DescribeBackupVaultOutput); ok { + return out, err + } + + return nil, err +} + +func statusLogicallyAirGappedVault(ctx context.Context, conn *backup.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findVaultByName(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.VaultState), nil + } +} diff --git a/internal/service/backup/logically_air_gapped_vault_test.go b/internal/service/backup/logically_air_gapped_vault_test.go new file mode 100644 index 00000000000..47410303e3f --- /dev/null +++ b/internal/service/backup/logically_air_gapped_vault_test.go @@ -0,0 +1,237 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package backup_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "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 TestAccBackupLAGVault_basic(t *testing.T) { + ctx := acctest.Context(t) + + var logicallyairgappedvault backup.DescribeBackupVaultOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_backup_logically_air_gapped_vault.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.BackupEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckLAGVaultDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccLAGVaultConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLAGVaultExists(ctx, resourceName, &logicallyairgappedvault), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccBackupLAGVault_disappears(t *testing.T) { + ctx := acctest.Context(t) + + var logicallyairgappedvault backup.DescribeBackupVaultOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_backup_logically_air_gapped_vault.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.BackupEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckLAGVaultDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccLAGVaultConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLAGVaultExists(ctx, resourceName, &logicallyairgappedvault), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceLogicallyAirGappedVault, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccBackupLAGVault_tags(t *testing.T) { + ctx := acctest.Context(t) + + var logicallyairgappedvault backup.DescribeBackupVaultOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_backup_logically_air_gapped_vault.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ProfilesServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckLAGVaultDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccLAGVaultConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckLAGVaultExists(ctx, resourceName, &logicallyairgappedvault), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccLAGVaultConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckLAGVaultExists(ctx, resourceName, &logicallyairgappedvault), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccLAGVaultConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckLAGVaultExists(ctx, resourceName, &logicallyairgappedvault), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +func testAccCheckLAGVaultDestroy(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_logically_air_gapped_vault" { + continue + } + + _, err := conn.DescribeBackupVault(ctx, &backup.DescribeBackupVaultInput{ + BackupVaultName: aws.String(rs.Primary.ID), + }) + if errs.IsA[*types.ResourceNotFoundException](err) || errs.MessageContains(err, "AccessDeniedException", "Insufficient privileges to perform this action") { + return nil + } + if err != nil { + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameLogicallyAirGappedVault, rs.Primary.ID, err) + } + + return create.Error(names.Backup, create.ErrActionCheckingDestroyed, tfbackup.ResNameLogicallyAirGappedVault, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckLAGVaultExists(ctx context.Context, name string, logicallyairgappedvault *backup.DescribeBackupVaultOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameLogicallyAirGappedVault, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameLogicallyAirGappedVault, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + resp, err := conn.DescribeBackupVault(ctx, &backup.DescribeBackupVaultInput{ + BackupVaultName: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.Backup, create.ErrActionCheckingExistence, tfbackup.ResNameLogicallyAirGappedVault, rs.Primary.ID, err) + } + + *logicallyairgappedvault = *resp + + return nil + } +} + +func testAccCheckLAGVaultNotRecreated(before, after *backup.DescribeBackupVaultOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.ToString(before.BackupVaultName), aws.ToString(after.BackupVaultName); before != after { + return create.Error(names.Backup, create.ErrActionCheckingNotRecreated, tfbackup.ResNameLogicallyAirGappedVault, before, errors.New("recreated")) + } + + return nil + } +} + +func testAccLAGVaultConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_backup_logically_air_gapped_vault" "test" { + name = %[1]q + max_retention_days = 7 + min_retention_days = 7 +} +`, rName) +} + +func testAccLAGVaultConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_backup_logically_air_gapped_vault" "test" { + name = %[1]q + max_retention_days = 7 + min_retention_days = 7 + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccLAGVaultConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_backup_logically_air_gapped_vault" "test" { + name = %[1]q + max_retention_days = 7 + min_retention_days = 7 + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 4cdedfc56f4..7756d94e01f 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -19,7 +19,15 @@ 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: newResourceLogicallyAirGappedVault, + Name: "Logically Air Gapped Vault", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }, + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { diff --git a/website/docs/r/backup_logically_air_gapped_vault.html.markdown b/website/docs/r/backup_logically_air_gapped_vault.html.markdown new file mode 100644 index 00000000000..b0fb4b82c67 --- /dev/null +++ b/website/docs/r/backup_logically_air_gapped_vault.html.markdown @@ -0,0 +1,73 @@ +--- +subcategory: "Backup" +layout: "aws" +page_title: "AWS: aws_backup_logically_air_gapped_vault" +description: |- + Terraform resource for managing an AWS Backup Logically Air Gapped Vault. +--- +` +# Resource: aws_backup_logically_air_gapped_vault + +Terraform resource for managing an AWS Backup Logically Air Gapped Vault. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_backup_logically_air_gapped_vault" "example" { + name = "lag-example-vault" + max_retention_days = 7 + min_retention_days = 7 +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Name of the Logically Air Gapped Backup Vault to create. +* `max_retention_days` - (Required) Maximum retention period that the Logically Air Gapped Backup Vault retains recovery points. +* `min_retention_days` - (Required) Minimum retention period that the Logically Air Gapped Backup Vault retains recovery points. +* `tags` - (Optional) Metadata that you can assign to help organize the resources that you create. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - The name of the Logically Air Gapped Backup Vault. +* `arn` - The ARN of the Logically Air Gapped Backup Vault. +* `recovery_points` - The number of recovery points that are stored in a Logically Air Gapped Backup Vault. +* `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 `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Backup Logically Air Gapped Vault using the `id`. For example: + +```terraform +import { + to = aws_backup_logically_air_gapped_vault.example + id = "lag-example-vault" +} +``` + +Using `terraform import`, import Backup Logically Air Gapped Vault using the `id`. For example: + +```console +% terraform import aws_backup_logically_air_gapped_vault.example lag-example-vault +``` From 6634f644f0cef6006e71d1cd34a6c6eb76ea80ee Mon Sep 17 00:00:00 2001 From: aristosvo <8375124+aristosvo@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:30:55 +0200 Subject: [PATCH 02/27] docs fix --- .../r/backup_logically_air_gapped_vault.html.markdown | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/website/docs/r/backup_logically_air_gapped_vault.html.markdown b/website/docs/r/backup_logically_air_gapped_vault.html.markdown index b0fb4b82c67..271c7d7576c 100644 --- a/website/docs/r/backup_logically_air_gapped_vault.html.markdown +++ b/website/docs/r/backup_logically_air_gapped_vault.html.markdown @@ -5,14 +5,7 @@ page_title: "AWS: aws_backup_logically_air_gapped_vault" description: |- Terraform resource for managing an AWS Backup Logically Air Gapped Vault. --- -` + # Resource: aws_backup_logically_air_gapped_vault Terraform resource for managing an AWS Backup Logically Air Gapped Vault. From 83fa18b3ddf0259942df305f5dc23cb6420a7539 Mon Sep 17 00:00:00 2001 From: aristosvo <8375124+aristosvo@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:31:06 +0200 Subject: [PATCH 03/27] changelog --- .changelog/39098.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/39098.txt diff --git a/.changelog/39098.txt b/.changelog/39098.txt new file mode 100644 index 00000000000..264fa3967f6 --- /dev/null +++ b/.changelog/39098.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_backup_logically_air_gapped_vault +``` \ No newline at end of file From 2b3f9146401e82d10e3e1f7a19c8e0ce11421809 Mon Sep 17 00:00:00 2001 From: aristosvo <8375124+aristosvo@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:32:02 +0200 Subject: [PATCH 04/27] fix imports --- internal/service/backup/logically_air_gapped_vault.go | 1 - internal/service/backup/logically_air_gapped_vault_test.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go index cd91e07c6e6..3c582577de8 100644 --- a/internal/service/backup/logically_air_gapped_vault.go +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -20,7 +20,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "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/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" diff --git a/internal/service/backup/logically_air_gapped_vault_test.go b/internal/service/backup/logically_air_gapped_vault_test.go index 47410303e3f..d9238e19dc4 100644 --- a/internal/service/backup/logically_air_gapped_vault_test.go +++ b/internal/service/backup/logically_air_gapped_vault_test.go @@ -19,9 +19,8 @@ import ( "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/names" ) func TestAccBackupLAGVault_basic(t *testing.T) { From 01a3b1177087712b2a9ec345b084a5c84ded5f0a Mon Sep 17 00:00:00 2001 From: aristosvo <8375124+aristosvo@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:44:28 +0200 Subject: [PATCH 05/27] semgrep fixes --- .../service/backup/logically_air_gapped_vault.go | 4 ++-- .../backup/logically_air_gapped_vault_test.go | 12 +----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go index 3c582577de8..d47ffc80d69 100644 --- a/internal/service/backup/logically_air_gapped_vault.go +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -82,7 +82,7 @@ func (r *resourceLogicallyAirGappedVault) Schema(ctx context.Context, req resour names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), }, Blocks: map[string]schema.Block{ - "timeouts": timeouts.Block(ctx, timeouts.Opts{ + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ Create: true, Update: true, Delete: true, @@ -213,7 +213,7 @@ func (r *resourceLogicallyAirGappedVault) ModifyPlan(ctx context.Context, reques } func (r *resourceLogicallyAirGappedVault) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) } type resourceLogicallyAirGappedVaultData struct { diff --git a/internal/service/backup/logically_air_gapped_vault_test.go b/internal/service/backup/logically_air_gapped_vault_test.go index d9238e19dc4..c6da9d87959 100644 --- a/internal/service/backup/logically_air_gapped_vault_test.go +++ b/internal/service/backup/logically_air_gapped_vault_test.go @@ -50,7 +50,7 @@ func TestAccBackupLAGVault_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + ImportStateVerifyIgnore: []string{names.AttrApplyImmediately, "user"}, }, }, }) @@ -186,16 +186,6 @@ func testAccCheckLAGVaultExists(ctx context.Context, name string, logicallyairga } } -func testAccCheckLAGVaultNotRecreated(before, after *backup.DescribeBackupVaultOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - if before, after := aws.ToString(before.BackupVaultName), aws.ToString(after.BackupVaultName); before != after { - return create.Error(names.Backup, create.ErrActionCheckingNotRecreated, tfbackup.ResNameLogicallyAirGappedVault, before, errors.New("recreated")) - } - - return nil - } -} - func testAccLAGVaultConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_backup_logically_air_gapped_vault" "test" { From 2775ba851151aa93b02eacef62b070843bb46121 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 11:03:18 -0400 Subject: [PATCH 06/27] r/aws_backup_logically_air_gapped_vault: Tidy up. --- internal/service/backup/exports_test.go | 8 +- internal/service/backup/find.go | 25 -- .../backup/logically_air_gapped_vault.go | 243 +++++++++--------- .../backup/logically_air_gapped_vault_test.go | 140 +++++++--- .../service/backup/service_package_gen.go | 2 +- internal/service/backup/vault.go | 46 +++- .../backup/vault_lock_configuration.go | 2 +- .../backup/vault_lock_configuration_test.go | 16 +- internal/service/backup/vault_test.go | 4 +- ...p_logically_air_gapped_vault.html.markdown | 2 - 10 files changed, 278 insertions(+), 210 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index e420624ccbd..441d9e938fa 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -5,7 +5,9 @@ package backup // Exports for use in tests only. var ( - FindVaultAccessPolicyByName = findVaultAccessPolicyByName - FindVaultByName = findVaultByName - ResourceLogicallyAirGappedVault = newResourceLogicallyAirGappedVault + ResourceLogicallyAirGappedVault = newLogicallyAirGappedVaultResource + + FindBackupVaultByName = findBackupVaultByName + FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName + FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/find.go b/internal/service/backup/find.go index cdee7f09329..f981ab49376 100644 --- a/internal/service/backup/find.go +++ b/internal/service/backup/find.go @@ -91,31 +91,6 @@ func findVaultAccessPolicyByName(ctx context.Context, conn *backup.Client, name return output, nil } -func findVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { - input := &backup.DescribeBackupVaultInput{ - BackupVaultName: aws.String(name), - } - - output, err := conn.DescribeBackupVault(ctx, input) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output, nil -} - func findFrameworkByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeFrameworkOutput, error) { input := &backup.DescribeFrameworkInput{ FrameworkName: aws.String(name), diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go index e155d9e1132..d852e3278e4 100644 --- a/internal/service/backup/logically_air_gapped_vault.go +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -5,26 +5,32 @@ package backup import ( "context" - "errors" + "fmt" "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/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "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/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/enum" "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" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -32,40 +38,30 @@ import ( // @FrameworkResource("aws_backup_logically_air_gapped_vault", name="Logically Air Gapped Vault") // @Tags(identifierAttribute="arn") -func newResourceLogicallyAirGappedVault(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceLogicallyAirGappedVault{} +func newLogicallyAirGappedVaultResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &logicallyAirGappedVaultResource{} r.SetDefaultCreateTimeout(30 * time.Minute) - r.SetDefaultUpdateTimeout(30 * time.Minute) - r.SetDefaultDeleteTimeout(30 * time.Minute) return r, nil } -const ( - ResNameLogicallyAirGappedVault = "Logically Air Gapped Vault" -) - -type resourceLogicallyAirGappedVault struct { +type logicallyAirGappedVaultResource struct { framework.ResourceWithConfigure + framework.WithNoOpUpdate[logicallyAirGappedVaultResourceModel] framework.WithTimeouts + framework.WithImportByID } -func (r *resourceLogicallyAirGappedVault) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_backup_logically_air_gapped_vault" +func (*logicallyAirGappedVaultResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_backup_logically_air_gapped_vault" } -func (r *resourceLogicallyAirGappedVault) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *logicallyAirGappedVaultResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrARN: framework.ARNAttributeComputedOnly(), names.AttrID: framework.IDAttribute(), - names.AttrName: schema.StringAttribute{ - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, "max_retention_days": schema.Int64Attribute{ Required: true, PlanModifiers: []planmodifier.Int64{ @@ -74,182 +70,155 @@ func (r *resourceLogicallyAirGappedVault) Schema(ctx context.Context, req resour }, "min_retention_days": schema.Int64Attribute{ Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(7), + }, PlanModifiers: []planmodifier.Int64{ int64planmodifier.RequiresReplace(), }, }, + names.AttrName: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile(`^[a-zA-Z0-9\-\_]{2,50}$`), ""), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, names.AttrTags: tftags.TagsAttribute(), names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), }, Blocks: map[string]schema.Block{ names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ Create: true, - Update: true, - Delete: true, }), }, } } -func (r *resourceLogicallyAirGappedVault) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *logicallyAirGappedVaultResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data logicallyAirGappedVaultResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + conn := r.Meta().BackupClient(ctx) - var plan resourceLogicallyAirGappedVaultData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { + name := data.BackupVaultName.ValueString() + input := &backup.CreateLogicallyAirGappedBackupVaultInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + if response.Diagnostics.HasError() { return } - in := &backup.CreateLogicallyAirGappedBackupVaultInput{ - BackupVaultName: plan.BackupVaultName.ValueStringPointer(), - MaxRetentionDays: plan.MaxRetentionDays.ValueInt64Pointer(), - MinRetentionDays: plan.MinRetentionDays.ValueInt64Pointer(), - BackupVaultTags: getTagsIn(ctx), - } + // Additional fields. + input.BackupVaultTags = getTagsIn(ctx) + input.CreatorRequestId = aws.String(sdkid.UniqueId()) + + output, err := conn.CreateLogicallyAirGappedBackupVault(ctx, input) - out, err := conn.CreateLogicallyAirGappedBackupVault(ctx, in) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameLogicallyAirGappedVault, plan.BackupVaultName.String(), err), - err.Error(), - ) - return - } - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionCreating, ResNameLogicallyAirGappedVault, plan.BackupVaultName.String(), nil), - errors.New("empty output").Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("creating Backup Logically Air Gapped Vault (%s)", name), err.Error()) + return } - plan.ID = flex.StringToFramework(ctx, out.BackupVaultName) - plan.ARN = flex.StringToFramework(ctx, out.BackupVaultArn) + // Set values for unknowns. + data.BackupVaultARN = fwflex.StringToFramework(ctx, output.BackupVaultArn) + data.ID = fwflex.StringToFramework(ctx, output.BackupVaultName) + + if _, err := waitLogicallyAirGappedVaultCreated(ctx, conn, data.ID.ValueString(), r.CreateTimeout(ctx, data.Timeouts)); err != nil { + response.State.SetAttribute(ctx, path.Root(names.AttrID), data.ID) // Set 'id' so as to taint the resource. + response.Diagnostics.AddError(fmt.Sprintf("waiting for Backup Logically Air Gapped Vault (%s) create", data.ID.ValueString()), err.Error()) - createTimeout := r.CreateTimeout(ctx, plan.Timeouts) - _, err = waitLogicallyAirGappedVaultCreated(ctx, conn, plan.ID.ValueString(), createTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionWaitingForCreation, ResNameLogicallyAirGappedVault, plan.BackupVaultName.String(), err), - err.Error(), - ) return } - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *resourceLogicallyAirGappedVault) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().BackupClient(ctx) - - var state resourceLogicallyAirGappedVaultData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *logicallyAirGappedVaultResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data logicallyAirGappedVaultResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - out, err := findVaultByName(ctx, conn, state.ID.ValueString()) + conn := r.Meta().BackupClient(ctx) + + output, err := findLogicallyAirGappedBackupVaultByName(ctx, conn, data.ID.ValueString()) + 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, ResNameLogicallyAirGappedVault, state.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Backup Logically Air Gapped Vault (%s)", data.ID.ValueString()), err.Error()) + return } - state.ARN = flex.StringToFramework(ctx, out.BackupVaultArn) - state.BackupVaultName = flex.StringToFramework(ctx, out.BackupVaultName) - state.MaxRetentionDays = flex.Int64ToFramework(ctx, out.MaxRetentionDays) - state.MinRetentionDays = flex.Int64ToFramework(ctx, out.MinRetentionDays) - - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} - -func (r *resourceLogicallyAirGappedVault) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan, state resourceLogicallyAirGappedVaultData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { return } - // Tags only. - - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceLogicallyAirGappedVault) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().BackupClient(ctx) - - var state resourceLogicallyAirGappedVaultData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *logicallyAirGappedVaultResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data logicallyAirGappedVaultResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &backup.DeleteBackupVaultInput{ - BackupVaultName: aws.String(state.ID.ValueString()), - } + conn := r.Meta().BackupClient(ctx) - _, err := conn.DeleteBackupVault(ctx, in) + _, err := conn.DeleteBackupVault(ctx, &backup.DeleteBackupVaultInput{ + BackupVaultName: aws.String(data.ID.ValueString()), + }) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Backup, create.ErrActionDeleting, ResNameLogicallyAirGappedVault, state.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("deleting Backup Logically Air Gapped Vault (%s)", data.ID.ValueString()), err.Error()) + return } } -func (r *resourceLogicallyAirGappedVault) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { +func (r *logicallyAirGappedVaultResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { r.SetTagsAll(ctx, request, response) } -func (r *resourceLogicallyAirGappedVault) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) -} - -type resourceLogicallyAirGappedVaultData struct { - ARN types.String `tfsdk:"arn"` +type logicallyAirGappedVaultResourceModel struct { + BackupVaultARN types.String `tfsdk:"arn"` + BackupVaultName types.String `tfsdk:"name"` ID types.String `tfsdk:"id"` MaxRetentionDays types.Int64 `tfsdk:"max_retention_days"` MinRetentionDays types.Int64 `tfsdk:"min_retention_days"` - BackupVaultName types.String `tfsdk:"name"` Timeouts timeouts.Value `tfsdk:"timeouts"` - Tags types.Map `tfsdk:"tags"` - TagsAll types.Map `tfsdk:"tags_all"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` } -func waitLogicallyAirGappedVaultCreated(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeBackupVaultOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(awstypes.VaultStateCreating), - Target: enum.Slice(awstypes.VaultStateAvailable), - Refresh: statusLogicallyAirGappedVault(ctx, conn, id), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*backup.DescribeBackupVaultOutput); ok { - return out, err - } - - return nil, err +func findLogicallyAirGappedBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { + return findVaultByNameAndType(ctx, conn, name, awstypes.VaultTypeLogicallyAirGappedBackupVault) } -func statusLogicallyAirGappedVault(ctx context.Context, conn *backup.Client, id string) retry.StateRefreshFunc { +func statusLogicallyAirGappedVault(ctx context.Context, conn *backup.Client, name string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - out, err := findVaultByName(ctx, conn, id) + output, err := findLogicallyAirGappedBackupVaultByName(ctx, conn, name) + if tfresource.NotFound(err) { return nil, "", nil } @@ -258,6 +227,24 @@ func statusLogicallyAirGappedVault(ctx context.Context, conn *backup.Client, id return nil, "", err } - return out, string(out.VaultState), nil + return output, string(output.VaultState), nil + } +} + +func waitLogicallyAirGappedVaultCreated(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.DescribeBackupVaultOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.VaultStateCreating), + Target: enum.Slice(awstypes.VaultStateAvailable), + Refresh: statusLogicallyAirGappedVault(ctx, conn, name), + Timeout: timeout, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*backup.DescribeBackupVaultOutput); ok { + return output, err } + + return nil, err } diff --git a/internal/service/backup/logically_air_gapped_vault_test.go b/internal/service/backup/logically_air_gapped_vault_test.go index 476d3624b52..517123ae155 100644 --- a/internal/service/backup/logically_air_gapped_vault_test.go +++ b/internal/service/backup/logically_air_gapped_vault_test.go @@ -4,21 +4,27 @@ package backup_test import ( + "context" "fmt" "testing" "github.com/aws/aws-sdk-go-v2/service/backup" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccBackupLAGVault_basic(t *testing.T) { +func TestAccBackupLogicallyAirGappedVault_basic(t *testing.T) { ctx := acctest.Context(t) - - var logicallyairgappedvault backup.DescribeBackupVaultOutput + var v backup.DescribeBackupVaultOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_backup_logically_air_gapped_vault.test" @@ -30,28 +36,33 @@ func TestAccBackupLAGVault_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckVaultDestroy(ctx), + CheckDestroy: testAccCheckLogicallyAirGappedVaultDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccLAGVaultConfig_basic(rName), + Config: testAccLogicallyAirGappedVaultConfig_basic(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("max_retention_days"), knownvalue.Int64Exact(10)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("min_retention_days"), knownvalue.Int64Exact(7)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrName), knownvalue.StringExact(rName)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.Null()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTagsAll), knownvalue.MapExact(map[string]knownvalue.Check{})), + }, Check: resource.ComposeTestCheckFunc( - testAccCheckVaultExists(ctx, resourceName, &logicallyairgappedvault), + testAccCheckLogicallyAirGappedVaultExists(ctx, resourceName, &v), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{names.AttrApplyImmediately, "user"}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func TestAccBackupLAGVault_disappears(t *testing.T) { +func TestAccBackupLogicallyAirGappedVault_disappears(t *testing.T) { ctx := acctest.Context(t) - - var logicallyairgappedvault backup.DescribeBackupVaultOutput + var v backup.DescribeBackupVaultOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_backup_logically_air_gapped_vault.test" @@ -63,12 +74,12 @@ func TestAccBackupLAGVault_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckVaultDestroy(ctx), + CheckDestroy: testAccCheckLogicallyAirGappedVaultDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccLAGVaultConfig_basic(rName), + Config: testAccLogicallyAirGappedVaultConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVaultExists(ctx, resourceName, &logicallyairgappedvault), + testAccCheckLogicallyAirGappedVaultExists(ctx, resourceName, &v), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceLogicallyAirGappedVault, resourceName), ), ExpectNonEmptyPlan: true, @@ -77,10 +88,9 @@ func TestAccBackupLAGVault_disappears(t *testing.T) { }) } -func TestAccBackupLAGVault_tags(t *testing.T) { +func TestAccBackupLogicallyAirGappedVault_tags(t *testing.T) { ctx := acctest.Context(t) - - var logicallyairgappedvault backup.DescribeBackupVaultOutput + var v backup.DescribeBackupVaultOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_backup_logically_air_gapped_vault.test" @@ -90,14 +100,17 @@ func TestAccBackupLAGVault_tags(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.Route53ProfilesServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckVaultDestroy(ctx), + CheckDestroy: testAccCheckLogicallyAirGappedVaultDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccLAGVaultConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Config: testAccLogicallyAirGappedVaultConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1), + })), + }, Check: resource.ComposeTestCheckFunc( - testAccCheckVaultExists(ctx, resourceName, &logicallyairgappedvault), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + testAccCheckLogicallyAirGappedVaultExists(ctx, resourceName, &v), ), }, { @@ -106,37 +119,90 @@ func TestAccBackupLAGVault_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccLAGVaultConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1, acctest.CtKey2, acctest.CtValue2), + Config: testAccLogicallyAirGappedVaultConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated), + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, Check: resource.ComposeTestCheckFunc( - testAccCheckVaultExists(ctx, resourceName, &logicallyairgappedvault), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + testAccCheckLogicallyAirGappedVaultExists(ctx, resourceName, &v), ), }, { - Config: testAccLAGVaultConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Config: testAccLogicallyAirGappedVaultConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, Check: resource.ComposeTestCheckFunc( - testAccCheckVaultExists(ctx, resourceName, &logicallyairgappedvault), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + testAccCheckLogicallyAirGappedVaultExists(ctx, resourceName, &v), ), }, }, }) } -func testAccLAGVaultConfig_basic(rName string) string { +func testAccCheckLogicallyAirGappedVaultDestroy(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_logically_air_gapped_vault" { + continue + } + + _, err := tfbackup.FindLogicallyAirGappedBackupVaultByName(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Backup Logically Air Gapped Vault %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckLogicallyAirGappedVaultExists(ctx context.Context, n string, v *backup.DescribeBackupVaultOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + + output, err := tfbackup.FindLogicallyAirGappedBackupVaultByName(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccLogicallyAirGappedVaultConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_backup_logically_air_gapped_vault" "test" { name = %[1]q - max_retention_days = 7 + max_retention_days = 10 min_retention_days = 7 } `, rName) } -func testAccLAGVaultConfig_tags1(rName, tagKey1, tagValue1 string) string { +func testAccLogicallyAirGappedVaultConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_backup_logically_air_gapped_vault" "test" { name = %[1]q @@ -150,7 +216,7 @@ resource "aws_backup_logically_air_gapped_vault" "test" { `, rName, tagKey1, tagValue1) } -func testAccLAGVaultConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccLogicallyAirGappedVaultConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_backup_logically_air_gapped_vault" "test" { name = %[1]q diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 7756d94e01f..21a2c19c1f2 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -21,7 +21,7 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newResourceLogicallyAirGappedVault, + Factory: newLogicallyAirGappedVaultResource, Name: "Logically Air Gapped Vault", Tags: &types.ServicePackageResourceTags{ IdentifierAttribute: names.AttrARN, diff --git a/internal/service/backup/vault.go b/internal/service/backup/vault.go index 4c104c552fa..73f6ee6f145 100644 --- a/internal/service/backup/vault.go +++ b/internal/service/backup/vault.go @@ -16,6 +16,7 @@ import ( awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -111,7 +112,7 @@ func resourceVaultRead(ctx context.Context, d *schema.ResourceData, meta interfa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - output, err := findVaultByName(ctx, conn, d.Id()) + output, err := findBackupVaultByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Backup Vault (%s) not found, removing from state", d.Id()) @@ -199,3 +200,46 @@ func resourceVaultDelete(ctx context.Context, d *schema.ResourceData, meta inter return diags } + +func findBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { + return findVaultByNameAndType(ctx, conn, name, awstypes.VaultTypeBackupVault) +} + +func findVaultByNameAndType(ctx context.Context, conn *backup.Client, name string, vaultType awstypes.VaultType) (*backup.DescribeBackupVaultOutput, error) { + input := &backup.DescribeBackupVaultInput{ + BackupVaultName: aws.String(name), + } + + output, err := findVault(ctx, conn, input) + + if err != nil { + return nil, err + } + + if output.VaultType != vaultType { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func findVault(ctx context.Context, conn *backup.Client, input *backup.DescribeBackupVaultInput) (*backup.DescribeBackupVaultOutput, error) { + output, err := conn.DescribeBackupVault(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/backup/vault_lock_configuration.go b/internal/service/backup/vault_lock_configuration.go index 3a042a7d340..5dbbd9c2685 100644 --- a/internal/service/backup/vault_lock_configuration.go +++ b/internal/service/backup/vault_lock_configuration.go @@ -97,7 +97,7 @@ func resourceVaultLockConfigurationRead(ctx context.Context, d *schema.ResourceD var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - output, err := findVaultByName(ctx, conn, d.Id()) + output, err := findBackupVaultByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Backup Vault Lock Configuration (%s) not found, removing from state", d.Id()) diff --git a/internal/service/backup/vault_lock_configuration_test.go b/internal/service/backup/vault_lock_configuration_test.go index 31369fb900f..0664150225e 100644 --- a/internal/service/backup/vault_lock_configuration_test.go +++ b/internal/service/backup/vault_lock_configuration_test.go @@ -83,7 +83,7 @@ func testAccCheckVaultLockConfigurationDestroy(ctx context.Context) resource.Tes continue } - _, err := tfbackup.FindVaultByName(ctx, conn, rs.Primary.ID) + _, err := tfbackup.FindBackupVaultByName(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -100,26 +100,22 @@ func testAccCheckVaultLockConfigurationDestroy(ctx context.Context) resource.Tes } } -func testAccCheckVaultLockConfigurationExists(ctx context.Context, name string, vault *backup.DescribeBackupVaultOutput) resource.TestCheckFunc { +func testAccCheckVaultLockConfigurationExists(ctx context.Context, n string, v *backup.DescribeBackupVaultOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No Backup Vault Lock Configuration ID is set") + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - output, err := tfbackup.FindVaultByName(ctx, conn, rs.Primary.ID) + output, err := tfbackup.FindBackupVaultByName(ctx, conn, rs.Primary.ID) if err != nil { return err } - *vault = *output + *v = *output return nil } diff --git a/internal/service/backup/vault_test.go b/internal/service/backup/vault_test.go index eb2962d0d89..33d39c2d1f6 100644 --- a/internal/service/backup/vault_test.go +++ b/internal/service/backup/vault_test.go @@ -229,7 +229,7 @@ func testAccCheckVaultDestroy(ctx context.Context) resource.TestCheckFunc { continue } - _, err := tfbackup.FindVaultByName(ctx, conn, rs.Primary.ID) + _, err := tfbackup.FindBackupVaultByName(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -255,7 +255,7 @@ func testAccCheckVaultExists(ctx context.Context, n string, v *backup.DescribeBa conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - output, err := tfbackup.FindVaultByName(ctx, conn, rs.Primary.ID) + output, err := tfbackup.FindBackupVaultByName(ctx, conn, rs.Primary.ID) if err != nil { return err diff --git a/website/docs/r/backup_logically_air_gapped_vault.html.markdown b/website/docs/r/backup_logically_air_gapped_vault.html.markdown index 271c7d7576c..9ee492a9e7a 100644 --- a/website/docs/r/backup_logically_air_gapped_vault.html.markdown +++ b/website/docs/r/backup_logically_air_gapped_vault.html.markdown @@ -45,8 +45,6 @@ This resource exports the following attributes in addition to the arguments abov [Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): * `create` - (Default `30m`) -* `update` - (Default `30m`) -* `delete` - (Default `30m`) ## Import From 6956ad68df0f841f3e17522ab29b68459a47b93a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 11:32:56 -0400 Subject: [PATCH 07/27] Acceptance test output: % make testacc TESTARGS='-run=TestAccBackupLogicallyAirGappedVault_' PKG=backup ACCTEST_PARALLELISM=2 make: Verifying source code with gofmt... ==> Checking that code complies with gofmt requirements... TF_ACC=1 go1.23.1 test ./internal/service/backup/... -v -count 1 -parallel 2 -run=TestAccBackupLogicallyAirGappedVault_ -timeout 360m === RUN TestAccBackupLogicallyAirGappedVault_basic === PAUSE TestAccBackupLogicallyAirGappedVault_basic === RUN TestAccBackupLogicallyAirGappedVault_disappears === PAUSE TestAccBackupLogicallyAirGappedVault_disappears === RUN TestAccBackupLogicallyAirGappedVault_tags === PAUSE TestAccBackupLogicallyAirGappedVault_tags === CONT TestAccBackupLogicallyAirGappedVault_basic === CONT TestAccBackupLogicallyAirGappedVault_tags --- PASS: TestAccBackupLogicallyAirGappedVault_basic (158.36s) === CONT TestAccBackupLogicallyAirGappedVault_disappears --- PASS: TestAccBackupLogicallyAirGappedVault_tags (164.34s) --- PASS: TestAccBackupLogicallyAirGappedVault_disappears (145.31s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/backup 309.059s From af25a509731ded13d4194da183cf5340fdbbd77d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 11:51:54 -0400 Subject: [PATCH 08/27] r/aws_backup_framework: Reduce visibility. --- internal/service/backup/consts.go | 8 - internal/service/backup/exports_test.go | 2 + internal/service/backup/find.go | 21 -- internal/service/backup/framework.go | 280 ++++++++++++------ internal/service/backup/framework_test.go | 79 ++--- .../service/backup/service_package_gen.go | 2 +- internal/service/backup/status.go | 17 -- internal/service/backup/sweep.go | 41 +-- internal/service/backup/wait.go | 51 ---- 9 files changed, 242 insertions(+), 259 deletions(-) diff --git a/internal/service/backup/consts.go b/internal/service/backup/consts.go index 573f338a20d..3e412bf7ff1 100644 --- a/internal/service/backup/consts.go +++ b/internal/service/backup/consts.go @@ -9,14 +9,6 @@ const ( iamPropagationTimeout = 2 * time.Minute ) -const ( - frameworkStatusCompleted = "COMPLETED" - frameworkStatusCreationInProgress = "CREATE_IN_PROGRESS" - frameworkStatusDeletionInProgress = "DELETE_IN_PROGRESS" - frameworkStatusFailed = "FAILED" - frameworkStatusUpdateInProgress = "UPDATE_IN_PROGRESS" -) - const ( reportPlanDeploymentStatusCompleted = "COMPLETED" reportPlanDeploymentStatusCreateInProgress = "CREATE_IN_PROGRESS" diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 441d9e938fa..6bd95eb5ff1 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -5,9 +5,11 @@ package backup // Exports for use in tests only. var ( + ResourceFramework = resourceFramework ResourceLogicallyAirGappedVault = newLogicallyAirGappedVaultResource FindBackupVaultByName = findBackupVaultByName + FindFrameworkByName = findFrameworkByName FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/find.go b/internal/service/backup/find.go index f981ab49376..e7e383230f0 100644 --- a/internal/service/backup/find.go +++ b/internal/service/backup/find.go @@ -90,24 +90,3 @@ func findVaultAccessPolicyByName(ctx context.Context, conn *backup.Client, name return output, nil } - -func findFrameworkByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeFrameworkOutput, error) { - input := &backup.DescribeFrameworkInput{ - FrameworkName: aws.String(name), - } - - output, err := conn.DescribeFramework(ctx, input) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - return output, nil -} diff --git a/internal/service/backup/framework.go b/internal/service/backup/framework.go index c9b69a059e9..503082194ae 100644 --- a/internal/service/backup/framework.go +++ b/internal/service/backup/framework.go @@ -12,7 +12,8 @@ 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-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + sdkid "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -27,20 +28,23 @@ import ( // @SDKResource("aws_backup_framework", name="Framework") // @Tags(identifierAttribute="arn") -func ResourceFramework() *schema.Resource { +func resourceFramework() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceFrameworkCreate, ReadWithoutTimeout: resourceFrameworkRead, UpdateWithoutTimeout: resourceFrameworkUpdate, DeleteWithoutTimeout: resourceFrameworkDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(3 * time.Minute), Update: schema.DefaultTimeout(3 * time.Minute), Delete: schema.DefaultTimeout(3 * time.Minute), }, + Schema: map[string]*schema.Schema{ names.AttrARN: { Type: schema.TypeString, @@ -136,6 +140,7 @@ func ResourceFramework() *schema.Resource { names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, + CustomizeDiff: verify.SetTagsDiff, } } @@ -146,27 +151,26 @@ func resourceFrameworkCreate(ctx context.Context, d *schema.ResourceData, meta i name := d.Get(names.AttrName).(string) input := &backup.CreateFrameworkInput{ - IdempotencyToken: aws.String(id.UniqueId()), FrameworkControls: expandFrameworkControls(ctx, d.Get("control").(*schema.Set).List()), FrameworkName: aws.String(name), FrameworkTags: getTagsIn(ctx), + IdempotencyToken: aws.String(sdkid.UniqueId()), } if v, ok := d.GetOk(names.AttrDescription); ok { input.FrameworkDescription = aws.String(v.(string)) } - resp, err := conn.CreateFramework(ctx, input) + _, err := conn.CreateFramework(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Backup Framework: %s", err) + return sdkdiag.AppendErrorf(diags, "creating Backup Framework (%s): %s", name, err) } - // Set ID with the name since the name is unique for the framework - d.SetId(aws.ToString(resp.FrameworkName)) + d.SetId(name) - // waiter since the status changes from CREATE_IN_PROGRESS to either COMPLETED or FAILED if _, err := waitFrameworkCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Framework (%s) creation: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "waiting for Backup Framework (%s) create: %s", d.Id(), err) } return append(diags, resourceFrameworkRead(ctx, d, meta)...) @@ -176,30 +180,27 @@ func resourceFrameworkRead(ctx context.Context, d *schema.ResourceData, meta int var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - resp, err := findFrameworkByName(ctx, conn, d.Id()) + output, err := findFrameworkByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Backup Framework (%s) not found, removing from state", d.Id()) d.SetId("") return diags } + if err != nil { return sdkdiag.AppendErrorf(diags, "reading Backup Framework (%s): %s", d.Id(), err) } - d.Set(names.AttrARN, resp.FrameworkArn) - d.Set("deployment_status", resp.DeploymentStatus) - d.Set(names.AttrDescription, resp.FrameworkDescription) - d.Set(names.AttrName, resp.FrameworkName) - d.Set(names.AttrStatus, resp.FrameworkStatus) - - if err := d.Set(names.AttrCreationTime, resp.CreationTime.Format(time.RFC3339)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting creation_time: %s", err) - } - - if err := d.Set("control", flattenFrameworkControls(ctx, resp.FrameworkControls)); err != nil { + d.Set(names.AttrARN, output.FrameworkArn) + if err := d.Set("control", flattenFrameworkControls(ctx, output.FrameworkControls)); err != nil { return sdkdiag.AppendErrorf(diags, "setting control: %s", err) } + d.Set(names.AttrCreationTime, output.CreationTime.Format(time.RFC3339)) + d.Set("deployment_status", output.DeploymentStatus) + d.Set(names.AttrDescription, output.FrameworkDescription) + d.Set(names.AttrName, output.FrameworkName) + d.Set(names.AttrStatus, output.FrameworkStatus) return diags } @@ -208,16 +209,14 @@ func resourceFrameworkUpdate(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - if d.HasChanges(names.AttrDescription, "control") { + if d.HasChanges("control", names.AttrDescription) { input := &backup.UpdateFrameworkInput{ - IdempotencyToken: aws.String(id.UniqueId()), FrameworkControls: expandFrameworkControls(ctx, d.Get("control").(*schema.Set).List()), FrameworkDescription: aws.String(d.Get(names.AttrDescription).(string)), FrameworkName: aws.String(d.Id()), + IdempotencyToken: aws.String(sdkid.UniqueId()), } - log.Printf("[DEBUG] Updating Backup Framework: %#v", input) - _, err := tfresource.RetryWhenIsA[*awstypes.ConflictException](ctx, d.Timeout(schema.TimeoutUpdate), func() (interface{}, error) { return conn.UpdateFramework(ctx, input) }) @@ -227,7 +226,7 @@ func resourceFrameworkUpdate(ctx context.Context, d *schema.ResourceData, meta i } if _, err := waitFrameworkUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Framework (%s) update: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "waiting for Backup Framework (%s) update: %s", d.Id(), err) } } @@ -238,12 +237,11 @@ func resourceFrameworkDelete(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - input := &backup.DeleteFrameworkInput{ - FrameworkName: aws.String(d.Id()), - } - + log.Printf("[DEBUG] Deleting Backup Framework: %s", d.Id()) _, err := tfresource.RetryWhenIsA[*awstypes.ConflictException](ctx, d.Timeout(schema.TimeoutDelete), func() (interface{}, error) { - return conn.DeleteFramework(ctx, input) + return conn.DeleteFramework(ctx, &backup.DeleteFrameworkInput{ + FrameworkName: aws.String(d.Id()), + }) }) if errs.IsA[*awstypes.ResourceNotFoundException](err) { @@ -255,21 +253,125 @@ func resourceFrameworkDelete(ctx context.Context, d *schema.ResourceData, meta i } if _, err := waitFrameworkDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Framework (%s) deletion: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "waiting for Backup Framework (%s) delete: %s", d.Id(), err) } return diags } -func expandFrameworkControls(ctx context.Context, controls []interface{}) []awstypes.FrameworkControl { - if len(controls) == 0 { +func findFrameworkByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeFrameworkOutput, error) { + input := &backup.DescribeFrameworkInput{ + FrameworkName: aws.String(name), + } + + return findFramework(ctx, conn, input) +} + +func findFramework(ctx context.Context, conn *backup.Client, input *backup.DescribeFrameworkInput) (*backup.DescribeFrameworkOutput, error) { + output, err := conn.DescribeFramework(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusFramework(ctx context.Context, conn *backup.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findFrameworkByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.ToString(output.DeploymentStatus), nil + } +} + +const ( + frameworkStatusCompleted = "COMPLETED" + frameworkStatusCreationInProgress = "CREATE_IN_PROGRESS" + frameworkStatusDeletionInProgress = "DELETE_IN_PROGRESS" + frameworkStatusFailed = "FAILED" + frameworkStatusUpdateInProgress = "UPDATE_IN_PROGRESS" +) + +func waitFrameworkCreated(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.DescribeFrameworkOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{frameworkStatusCreationInProgress}, + Target: []string{frameworkStatusCompleted, frameworkStatusFailed}, + Refresh: statusFramework(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*backup.DescribeFrameworkOutput); ok { + return output, err + } + + return nil, err +} + +func waitFrameworkUpdated(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.DescribeFrameworkOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{frameworkStatusUpdateInProgress}, + Target: []string{frameworkStatusCompleted, frameworkStatusFailed}, + Refresh: statusFramework(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*backup.DescribeFrameworkOutput); ok { + return output, err + } + + return nil, err +} + +func waitFrameworkDeleted(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*backup.DescribeFrameworkOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{frameworkStatusDeletionInProgress}, + Target: []string{}, + Refresh: statusFramework(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*backup.DescribeFrameworkOutput); ok { + return output, err + } + + return nil, err +} + +func expandFrameworkControls(ctx context.Context, tfList []interface{}) []awstypes.FrameworkControl { + if len(tfList) == 0 { return nil } - frameworkControls := []awstypes.FrameworkControl{} + apiObjects := []awstypes.FrameworkControl{} - for _, control := range controls { - tfMap := control.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) // on some updates, there is an { ControlName: "" } element in Framework Controls. // this element must be skipped to avoid the "A control name is required." error @@ -278,119 +380,125 @@ func expandFrameworkControls(ctx context.Context, controls []interface{}) []awst continue } - frameworkControl := awstypes.FrameworkControl{ + apiObject := awstypes.FrameworkControl{ ControlName: aws.String(tfMap[names.AttrName].(string)), ControlScope: expandControlScope(ctx, tfMap[names.AttrScope].([]interface{})), } if v, ok := tfMap["input_parameter"]; ok && v.(*schema.Set).Len() > 0 { - frameworkControl.ControlInputParameters = expandInputParameters(tfMap["input_parameter"].(*schema.Set).List()) + apiObject.ControlInputParameters = expandControlInputParameters(tfMap["input_parameter"].(*schema.Set).List()) } - frameworkControls = append(frameworkControls, frameworkControl) + apiObjects = append(apiObjects, apiObject) } - return frameworkControls + return apiObjects } -func expandInputParameters(inputParams []interface{}) []awstypes.ControlInputParameter { - if len(inputParams) == 0 { +func expandControlInputParameters(tfList []interface{}) []awstypes.ControlInputParameter { + if len(tfList) == 0 { return nil } - controlInputParameters := []awstypes.ControlInputParameter{} + apiObjects := []awstypes.ControlInputParameter{} - for _, inputParam := range inputParams { - tfMap := inputParam.(map[string]interface{}) - controlInputParameter := awstypes.ControlInputParameter{} + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + + apiObject := awstypes.ControlInputParameter{} if v, ok := tfMap[names.AttrName].(string); ok && v != "" { - controlInputParameter.ParameterName = aws.String(v) + apiObject.ParameterName = aws.String(v) } if v, ok := tfMap[names.AttrValue].(string); ok && v != "" { - controlInputParameter.ParameterValue = aws.String(v) + apiObject.ParameterValue = aws.String(v) } - controlInputParameters = append(controlInputParameters, controlInputParameter) + apiObjects = append(apiObjects, apiObject) } - return controlInputParameters + return apiObjects } -func expandControlScope(ctx context.Context, scope []interface{}) *awstypes.ControlScope { - if len(scope) == 0 || scope[0] == nil { +func expandControlScope(ctx context.Context, tfList []interface{}) *awstypes.ControlScope { + if len(tfList) == 0 || tfList[0] == nil { return nil } - tfMap, ok := scope[0].(map[string]interface{}) + tfMap, ok := tfList[0].(map[string]interface{}) if !ok { return nil } - controlScope := &awstypes.ControlScope{} + apiObject := &awstypes.ControlScope{} if v, ok := tfMap["compliance_resource_ids"]; ok && v.(*schema.Set).Len() > 0 { - controlScope.ComplianceResourceIds = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.ComplianceResourceIds = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := tfMap["compliance_resource_types"]; ok && v.(*schema.Set).Len() > 0 { - controlScope.ComplianceResourceTypes = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.ComplianceResourceTypes = flex.ExpandStringValueSet(v.(*schema.Set)) } // A maximum of one key-value pair can be provided. // The tag value is optional, but it cannot be an empty string if v, ok := tfMap[names.AttrTags].(map[string]interface{}); ok && len(v) > 0 { - controlScope.Tags = Tags(tftags.New(ctx, v).IgnoreAWS()) + apiObject.Tags = Tags(tftags.New(ctx, v).IgnoreAWS()) } - return controlScope + return apiObject } -func flattenFrameworkControls(ctx context.Context, controls []awstypes.FrameworkControl) []interface{} { - if controls == nil { +func flattenFrameworkControls(ctx context.Context, apiObjects []awstypes.FrameworkControl) []interface{} { + if apiObjects == nil { return []interface{}{} } - frameworkControls := []interface{}{} - for _, control := range controls { - values := map[string]interface{}{} - values["input_parameter"] = flattenInputParameters(control.ControlInputParameters) - values[names.AttrName] = aws.ToString(control.ControlName) - values[names.AttrScope] = flattenScope(ctx, control.ControlScope) - frameworkControls = append(frameworkControls, values) + tfList := []interface{}{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{} + tfMap["input_parameter"] = flattenControlInputParameters(apiObject.ControlInputParameters) + tfMap[names.AttrName] = aws.ToString(apiObject.ControlName) + tfMap[names.AttrScope] = flattenControlScope(ctx, apiObject.ControlScope) + + tfList = append(tfList, tfMap) } - return frameworkControls + return tfList } -func flattenInputParameters(inputParams []awstypes.ControlInputParameter) []interface{} { - if inputParams == nil { +func flattenControlInputParameters(apiObjects []awstypes.ControlInputParameter) []interface{} { + if apiObjects == nil { return []interface{}{} } - controlInputParameters := []interface{}{} - for _, inputParam := range inputParams { - values := map[string]interface{}{} - values[names.AttrName] = aws.ToString(inputParam.ParameterName) - values[names.AttrValue] = aws.ToString(inputParam.ParameterValue) - controlInputParameters = append(controlInputParameters, values) + tfList := []interface{}{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{} + tfMap[names.AttrName] = aws.ToString(apiObject.ParameterName) + tfMap[names.AttrValue] = aws.ToString(apiObject.ParameterValue) + + tfList = append(tfList, tfMap) } - return controlInputParameters + + return tfList } -func flattenScope(ctx context.Context, scope *awstypes.ControlScope) []interface{} { - if scope == nil { +func flattenControlScope(ctx context.Context, apiObject *awstypes.ControlScope) []interface{} { + if apiObject == nil { return []interface{}{} } - controlScope := map[string]interface{}{ - "compliance_resource_ids": flex.FlattenStringValueList(scope.ComplianceResourceIds), - "compliance_resource_types": flex.FlattenStringValueList(scope.ComplianceResourceTypes), + tfMap := map[string]interface{}{ + "compliance_resource_ids": apiObject.ComplianceResourceIds, + "compliance_resource_types": apiObject.ComplianceResourceTypes, } - if v := scope.Tags; v != nil { - controlScope[names.AttrTags] = KeyValueTags(ctx, v).IgnoreAWS().Map() + if v := apiObject.Tags; v != nil { + tfMap[names.AttrTags] = KeyValueTags(ctx, v).IgnoreAWS().Map() } - return []interface{}{controlScope} + return []interface{}{tfMap} } diff --git a/internal/service/backup/framework_test.go b/internal/service/backup/framework_test.go index 1e2d92ebaee..de62b27fc77 100644 --- a/internal/service/backup/framework_test.go +++ b/internal/service/backup/framework_test.go @@ -8,7 +8,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/backup" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -16,6 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -43,7 +43,6 @@ func TestAccBackupFramework_serial(t *testing.T) { func testAccFramework_basic(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) originalDescription := "original description" updatedDescription := "updated description" @@ -57,7 +56,7 @@ func testAccFramework_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccFrameworkConfig_basic(rName, originalDescription), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckFrameworkExists(ctx, resourceName, &framework), resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), resource.TestCheckResourceAttr(resourceName, "control.#", acctest.Ct1), @@ -70,8 +69,7 @@ func testAccFramework_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrDescription, originalDescription), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), ), }, { @@ -81,7 +79,7 @@ func testAccFramework_basic(t *testing.T) { }, { Config: testAccFrameworkConfig_basic(rName, updatedDescription), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckFrameworkExists(ctx, resourceName, &framework), resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), resource.TestCheckResourceAttr(resourceName, "control.#", acctest.Ct1), @@ -94,8 +92,7 @@ func testAccFramework_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrDescription, updatedDescription), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), ), }, }, @@ -105,7 +102,6 @@ func testAccFramework_basic(t *testing.T) { func testAccFramework_updateTags(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) description := "example description" resourceName := "aws_backup_framework.test" @@ -193,7 +189,6 @@ func testAccFramework_updateTags(t *testing.T) { func testAccFramework_updateControlScope(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) description := "example description" originalControlScopeTagValue := "example" @@ -306,7 +301,6 @@ func testAccFramework_updateControlScope(t *testing.T) { func testAccFramework_updateControlInputParameters(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) description := "example description" originalRequiredRetentionDays := "35" @@ -389,7 +383,6 @@ func testAccFramework_updateControlInputParameters(t *testing.T) { func testAccFramework_updateControls(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) description := "example description" resourceName := "aws_backup_framework.test" @@ -468,7 +461,6 @@ func testAccFramework_updateControls(t *testing.T) { func testAccFramework_disappears(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) resourceName := "aws_backup_framework.test" @@ -507,47 +499,45 @@ func testAccFrameworkPreCheck(ctx context.Context, t *testing.T) { func testAccCheckFrameworkDestroy(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_framework" { continue } - input := &backup.DescribeFrameworkInput{ - FrameworkName: aws.String(rs.Primary.ID), - } + _, err := tfbackup.FindFrameworkByName(ctx, conn, rs.Primary.ID) - resp, err := conn.DescribeFramework(ctx, input) + if tfresource.NotFound(err) { + continue + } - if err == nil { - if aws.ToString(resp.FrameworkName) == rs.Primary.ID { - return fmt.Errorf("Backup Framework '%s' was not deleted properly", rs.Primary.ID) - } + if err != nil { + return err } + + return fmt.Errorf("Backup Framework %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckFrameworkExists(ctx context.Context, name string, framework *backup.DescribeFrameworkOutput) resource.TestCheckFunc { +func testAccCheckFrameworkExists(ctx context.Context, n string, v *backup.DescribeFrameworkOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - input := &backup.DescribeFrameworkInput{ - FrameworkName: aws.String(rs.Primary.ID), - } - resp, err := conn.DescribeFramework(ctx, input) + + output, err := tfbackup.FindFrameworkByName(ctx, conn, rs.Primary.ID) if err != nil { return err } - *framework = *resp + *v = *output return nil } @@ -568,10 +558,6 @@ resource "aws_backup_framework" "test" { ] } } - - tags = { - "Name" = "Test Framework" - } } `, rName, label) } @@ -626,20 +612,15 @@ resource "aws_backup_framework" "test" { } func testAccFrameworkConfig_controlScopeComplianceResourceID(rName, label string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] type = "gp2" size = 1 + + tags = { + Name = %[1]q + } } resource "aws_backup_framework" "test" { @@ -661,10 +642,10 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" + Name = %[1]q } } -`, rName, label) +`, rName, label)) } func testAccFrameworkConfig_controlScopeTag(rName, label, controlScopeTagValue string) string { @@ -684,7 +665,7 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" + Name = %[1]q } } `, rName, label, controlScopeTagValue) @@ -716,7 +697,7 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" + Name = %[1]q } } `, rName, label, requiredRetentionDaysValue) @@ -775,7 +756,7 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" + Name = %[1]q } } `, rName, label) diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 21a2c19c1f2..f72877d36c8 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -58,7 +58,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceFramework, + Factory: resourceFramework, TypeName: "aws_backup_framework", Name: "Framework", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/service/backup/status.go b/internal/service/backup/status.go index 0cb4f27e4f1..e322d0f2708 100644 --- a/internal/service/backup/status.go +++ b/internal/service/backup/status.go @@ -6,7 +6,6 @@ package backup import ( "context" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -28,22 +27,6 @@ func statusJobState(ctx context.Context, conn *backup.Client, id string) retry.S } } -func statusFramework(ctx context.Context, conn *backup.Client, id string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := findFrameworkByName(ctx, conn, id) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return output, aws.ToString(output.DeploymentStatus), nil - } -} - func statusRecoveryPoint(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string) retry.StateRefreshFunc { return func() (interface{}, string, error) { output, err := findRecoveryPointByTwoPartKey(ctx, conn, backupVaultName, recoveryPointARN) diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index df876b82307..b5913dce0ea 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -63,7 +63,6 @@ func sweepFramework(region string) error { sweepResources := make([]sweep.Sweepable, 0) pages := backup.NewListFrameworksPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -76,10 +75,10 @@ func sweepFramework(region string) error { return fmt.Errorf("error listing Backup Frameworks (%s): %w", region, err) } - for _, framework := range page.Frameworks { - r := ResourceFramework() + for _, v := range page.Frameworks { + r := resourceFramework() d := r.Data(nil) - d.SetId(aws.ToString(framework.FrameworkName)) + d.SetId(aws.ToString(v.FrameworkName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -103,7 +102,6 @@ func sweepReportPlan(region string) error { sweepResources := make([]sweep.Sweepable, 0) pages := backup.NewListReportPlansPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -116,10 +114,10 @@ func sweepReportPlan(region string) error { return fmt.Errorf("error listing Backup Report Plans for %s: %w", region, err) } - for _, reportPlan := range page.ReportPlans { + for _, v := range page.ReportPlans { r := ResourceReportPlan() d := r.Data(nil) - d.SetId(aws.ToString(reportPlan.ReportPlanName)) + d.SetId(aws.ToString(v.ReportPlanName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -135,17 +133,14 @@ func sweepReportPlan(region string) error { func sweepVaultLockConfiguration(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) - sweepResources := make([]sweep.Sweepable, 0) input := &backup.ListBackupVaultsInput{} + sweepResources := make([]sweep.Sweepable, 0) pages := backup.NewListBackupVaultsPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -158,10 +153,10 @@ func sweepVaultLockConfiguration(region string) error { return fmt.Errorf("error listing Backup Vaults for %s: %w", region, err) } - for _, vault := range page.BackupVaultList { + for _, v := range page.BackupVaultList { r := ResourceVaultLockConfiguration() d := r.Data(nil) - d.SetId(aws.ToString(vault.BackupVaultName)) + d.SetId(aws.ToString(v.BackupVaultName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -177,17 +172,14 @@ func sweepVaultLockConfiguration(region string) error { func sweepVaultNotifications(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) - sweepResources := make([]sweep.Sweepable, 0) input := &backup.ListBackupVaultsInput{} + sweepResources := make([]sweep.Sweepable, 0) pages := backup.NewListBackupVaultsPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -200,10 +192,10 @@ func sweepVaultNotifications(region string) error { return fmt.Errorf("error listing Backup Vaults for %s: %w", region, err) } - for _, vault := range page.BackupVaultList { + for _, v := range page.BackupVaultList { r := ResourceVaultNotifications() d := r.Data(nil) - d.SetId(aws.ToString(vault.BackupVaultName)) + d.SetId(aws.ToString(v.BackupVaultName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -227,7 +219,6 @@ func sweepVaultPolicies(region string) error { sweepResources := make([]sweep.Sweepable, 0) pages := backup.NewListBackupVaultsPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -240,10 +231,10 @@ func sweepVaultPolicies(region string) error { return fmt.Errorf("error listing Backup Vaults for %s: %w", region, err) } - for _, vault := range page.BackupVaultList { + for _, v := range page.BackupVaultList { r := ResourceVaultPolicy() d := r.Data(nil) - d.SetId(aws.ToString(vault.BackupVaultName)) + d.SetId(aws.ToString(v.BackupVaultName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -259,7 +250,6 @@ func sweepVaultPolicies(region string) error { func sweepVaults(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("Error getting client: %w", err) } @@ -268,7 +258,6 @@ func sweepVaults(region string) error { sweepResources := make([]sweep.Sweepable, 0) pages := backup.NewListBackupVaultsPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -281,8 +270,8 @@ func sweepVaults(region string) error { return fmt.Errorf("error listing Backup Vaults for %s: %w", region, err) } - for _, vault := range page.BackupVaultList { - name := aws.ToString(vault.BackupVaultName) + for _, v := range page.BackupVaultList { + name := aws.ToString(v.BackupVaultName) // Ignore Default and Automatic EFS Backup Vaults in region (cannot be deleted) if name == "Default" || name == "aws/efs/automatic-backup-vault" { diff --git a/internal/service/backup/wait.go b/internal/service/backup/wait.go index 79d7652ec28..dd810d193dc 100644 --- a/internal/service/backup/wait.go +++ b/internal/service/backup/wait.go @@ -40,57 +40,6 @@ func WaitJobCompleted(ctx context.Context, conn *backup.Client, id string, timeo return nil, err } -func waitFrameworkCreated(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeFrameworkOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{frameworkStatusCreationInProgress}, - Target: []string{frameworkStatusCompleted, frameworkStatusFailed}, - Refresh: statusFramework(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*backup.DescribeFrameworkOutput); ok { - return output, err - } - - return nil, err -} - -func waitFrameworkUpdated(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeFrameworkOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{frameworkStatusUpdateInProgress}, - Target: []string{frameworkStatusCompleted, frameworkStatusFailed}, - Refresh: statusFramework(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*backup.DescribeFrameworkOutput); ok { - return output, err - } - - return nil, err -} - -func waitFrameworkDeleted(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeFrameworkOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{frameworkStatusDeletionInProgress}, - Target: []string{}, - Refresh: statusFramework(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*backup.DescribeFrameworkOutput); ok { - return output, err - } - - return nil, err -} - func waitRecoveryPointDeleted(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string, timeout time.Duration) (*backup.DescribeRecoveryPointOutput, error) { stateConf := &retry.StateChangeConf{ Pending: enum.Slice(awstypes.RecoveryPointStatusDeleting), From 5bd9db4ab1cddc478815006493b741f2f38f6ca5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 12:01:40 -0400 Subject: [PATCH 09/27] d/aws_backup_framework: Reduce visibility. --- .../service/backup/framework_data_source.go | 35 +++++++------------ .../backup/framework_data_source_test.go | 21 +++++------ .../service/backup/service_package_gen.go | 3 +- 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/internal/service/backup/framework_data_source.go b/internal/service/backup/framework_data_source.go index 3e59769d175..d109d6e5033 100644 --- a/internal/service/backup/framework_data_source.go +++ b/internal/service/backup/framework_data_source.go @@ -7,8 +7,6 @@ import ( "context" "time" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -17,8 +15,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_backup_framework") -func DataSourceFramework() *schema.Resource { +// @SDKDataSource("aws_backup_framework", name="Framework") +func dataSourceFramework() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceFrameworkRead, @@ -109,31 +107,24 @@ func dataSourceFrameworkRead(ctx context.Context, d *schema.ResourceData, meta i ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig name := d.Get(names.AttrName).(string) + output, err := findFrameworkByName(ctx, conn, name) - resp, err := conn.DescribeFramework(ctx, &backup.DescribeFrameworkInput{ - FrameworkName: aws.String(name), - }) if err != nil { - return sdkdiag.AppendErrorf(diags, "getting Backup Framework: %s", err) + return sdkdiag.AppendErrorf(diags, "reading Backup Framework (%s): %s", name, err) } - d.SetId(aws.ToString(resp.FrameworkName)) - - d.Set(names.AttrARN, resp.FrameworkArn) - d.Set("deployment_status", resp.DeploymentStatus) - d.Set(names.AttrDescription, resp.FrameworkDescription) - d.Set(names.AttrName, resp.FrameworkName) - d.Set(names.AttrStatus, resp.FrameworkStatus) - - if err := d.Set(names.AttrCreationTime, resp.CreationTime.Format(time.RFC3339)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting creation_time: %s", err) - } - - if err := d.Set("control", flattenFrameworkControls(ctx, resp.FrameworkControls)); err != nil { + d.SetId(name) + d.Set(names.AttrARN, output.FrameworkArn) + if err := d.Set("control", flattenFrameworkControls(ctx, output.FrameworkControls)); err != nil { return sdkdiag.AppendErrorf(diags, "setting control: %s", err) } + d.Set(names.AttrCreationTime, output.CreationTime.Format(time.RFC3339)) + d.Set("deployment_status", output.DeploymentStatus) + d.Set(names.AttrDescription, output.FrameworkDescription) + d.Set(names.AttrName, output.FrameworkName) + d.Set(names.AttrStatus, output.FrameworkStatus) - tags, err := listTags(ctx, conn, aws.ToString(resp.FrameworkArn)) + tags, err := listTags(ctx, conn, d.Get(names.AttrARN).(string)) if err != nil { return sdkdiag.AppendErrorf(diags, "listing tags for Backup Framework (%s): %s", d.Id(), err) diff --git a/internal/service/backup/framework_data_source_test.go b/internal/service/backup/framework_data_source_test.go index 5bb885be945..95bead825a1 100644 --- a/internal/service/backup/framework_data_source_test.go +++ b/internal/service/backup/framework_data_source_test.go @@ -100,20 +100,15 @@ func testAccFrameworkDataSource_controlScopeTag(t *testing.T) { } func testAccFrameworkDataSourceConfig_basic(rName string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] type = "gp2" size = 1 + + tags = { + Name = %[1]q + } } resource "aws_backup_framework" "test" { @@ -171,14 +166,14 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" + Name = %[1]q } } data "aws_backup_framework" "test" { name = aws_backup_framework.test.name } -`, rName) +`, rName)) } func testAccFrameworkDataSourceConfig_controlScopeTag(rName string) string { @@ -198,7 +193,7 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" + Name = %[1]q } } diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index f72877d36c8..4a0ba86c663 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -33,8 +33,9 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { return []*types.ServicePackageSDKDataSource{ { - Factory: DataSourceFramework, + Factory: dataSourceFramework, TypeName: "aws_backup_framework", + Name: "Framework", }, { Factory: DataSourcePlan, From 77ba80534f41b776811faa38a5371e0901073956 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 12:14:56 -0400 Subject: [PATCH 10/27] 'testAccFramework_updateTags' -> 'testAccFramework_tags'. --- internal/service/backup/framework_test.go | 89 +++++++---------------- 1 file changed, 25 insertions(+), 64 deletions(-) diff --git a/internal/service/backup/framework_test.go b/internal/service/backup/framework_test.go index de62b27fc77..e78c99aa2cc 100644 --- a/internal/service/backup/framework_test.go +++ b/internal/service/backup/framework_test.go @@ -26,7 +26,7 @@ func TestAccBackupFramework_serial(t *testing.T) { "Resource": { acctest.CtBasic: testAccFramework_basic, acctest.CtDisappears: testAccFramework_disappears, - "UpdateTags": testAccFramework_updateTags, + "tags": testAccFramework_tags, "UpdateControlScope": testAccFramework_updateControlScope, "UpdateControlInputParameters": testAccFramework_updateControlInputParameters, "UpdateControls": testAccFramework_updateControls, @@ -99,7 +99,7 @@ func testAccFramework_basic(t *testing.T) { }) } -func testAccFramework_updateTags(t *testing.T) { +func testAccFramework_tags(t *testing.T) { ctx := acctest.Context(t) var framework backup.DescribeFrameworkOutput rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) @@ -113,22 +113,11 @@ func testAccFramework_updateTags(t *testing.T) { CheckDestroy: testAccCheckFrameworkDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccFrameworkConfig_basic(rName, description), + Config: testAccFrameworkConfig_tags1(rName, description, acctest.CtKey1, acctest.CtValue1), Check: resource.ComposeTestCheckFunc( testAccCheckFrameworkExists(ctx, resourceName, &framework), - resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), - resource.TestCheckResourceAttr(resourceName, "control.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.name", "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN"), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.0.compliance_resource_types.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.0.compliance_resource_types.0", "EBS"), - resource.TestCheckResourceAttrSet(resourceName, names.AttrCreationTime), - resource.TestCheckResourceAttrSet(resourceName, "deployment_status"), - resource.TestCheckResourceAttr(resourceName, names.AttrDescription, description), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), ), }, { @@ -137,23 +126,12 @@ func testAccFramework_updateTags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccFrameworkConfig_tags(rName, description), + Config: testAccFrameworkConfig_tags2(rName, description, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( testAccCheckFrameworkExists(ctx, resourceName, &framework), - resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), - resource.TestCheckResourceAttr(resourceName, "control.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.name", "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN"), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.0.compliance_resource_types.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.0.compliance_resource_types.0", "EBS"), - resource.TestCheckResourceAttrSet(resourceName, names.AttrCreationTime), - resource.TestCheckResourceAttrSet(resourceName, "deployment_status"), - resource.TestCheckResourceAttr(resourceName, names.AttrDescription, description), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), - resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2a"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), ), }, { @@ -162,24 +140,11 @@ func testAccFramework_updateTags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccFrameworkConfig_tagsUpdated(rName, description), + Config: testAccFrameworkConfig_tags1(rName, description, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( testAccCheckFrameworkExists(ctx, resourceName, &framework), - resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), - resource.TestCheckResourceAttr(resourceName, "control.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.name", "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN"), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.0.compliance_resource_types.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "control.0.scope.0.compliance_resource_types.0", "EBS"), - resource.TestCheckResourceAttrSet(resourceName, names.AttrCreationTime), - resource.TestCheckResourceAttrSet(resourceName, "deployment_status"), - resource.TestCheckResourceAttr(resourceName, names.AttrDescription, description), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct3), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), - resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2b"), - resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), ), }, }, @@ -216,8 +181,7 @@ func testAccFramework_updateControlScope(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrDescription, description), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), ), }, { @@ -243,7 +207,7 @@ func testAccFramework_updateControlScope(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -267,7 +231,7 @@ func testAccFramework_updateControlScope(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -291,7 +255,7 @@ func testAccFramework_updateControlScope(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, }, @@ -339,7 +303,7 @@ func testAccFramework_updateControlInputParameters(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -373,7 +337,7 @@ func testAccFramework_updateControlInputParameters(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, }, @@ -408,8 +372,7 @@ func testAccFramework_updateControls(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrDescription, description), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), ), }, { @@ -451,7 +414,7 @@ func testAccFramework_updateControls(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, names.AttrStatus), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "tags.Name", "Test Framework"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, }, @@ -562,7 +525,7 @@ resource "aws_backup_framework" "test" { `, rName, label) } -func testAccFrameworkConfig_tags(rName, label string) string { +func testAccFrameworkConfig_tags1(rName, label, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_backup_framework" "test" { name = %[1]q @@ -579,14 +542,13 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" - "Key2" = "Value2a" + %[3]q = %[4]q } } -`, rName, label) +`, rName, label, tagKey1, tagValue1) } -func testAccFrameworkConfig_tagsUpdated(rName, label string) string { +func testAccFrameworkConfig_tags2(rName, label, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_backup_framework" "test" { name = %[1]q @@ -603,12 +565,11 @@ resource "aws_backup_framework" "test" { } tags = { - "Name" = "Test Framework" - "Key2" = "Value2b" - "Key3" = "Value3" + %[3]q = %[4]q + %[5]q = %[6]q } } -`, rName, label) +`, rName, label, tagKey1, tagValue1, tagKey2, tagValue2) } func testAccFrameworkConfig_controlScopeComplianceResourceID(rName, label string) string { From 480be4e72f5a8717e03ec2508129d8a3342f8c89 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 12:17:17 -0400 Subject: [PATCH 11/27] Remove 'testAccFrameworkDataSource_controlScopeTag'. --- internal/service/backup/framework.go | 1 + .../backup/framework_data_source_test.go | 60 ------------------- internal/service/backup/framework_test.go | 3 +- 3 files changed, 2 insertions(+), 62 deletions(-) diff --git a/internal/service/backup/framework.go b/internal/service/backup/framework.go index 503082194ae..2cfb3cb7aae 100644 --- a/internal/service/backup/framework.go +++ b/internal/service/backup/framework.go @@ -465,6 +465,7 @@ func flattenFrameworkControls(ctx context.Context, apiObjects []awstypes.Framewo tfList = append(tfList, tfMap) } + return tfList } diff --git a/internal/service/backup/framework_data_source_test.go b/internal/service/backup/framework_data_source_test.go index 95bead825a1..4ff6ccd2092 100644 --- a/internal/service/backup/framework_data_source_test.go +++ b/internal/service/backup/framework_data_source_test.go @@ -66,39 +66,6 @@ func testAccFrameworkDataSource_basic(t *testing.T) { }) } -func testAccFrameworkDataSource_controlScopeTag(t *testing.T) { - ctx := acctest.Context(t) - datasourceName := "data.aws_backup_framework.test" - resourceName := "aws_backup_framework.test" - rName := fmt.Sprintf("tf_acc_test_%s", sdkacctest.RandString(7)) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); testAccFrameworkPreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccFrameworkDataSourceConfig_controlScopeTag(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(datasourceName, names.AttrARN, resourceName, names.AttrARN), - resource.TestCheckResourceAttrPair(datasourceName, "control.#", resourceName, "control.#"), - resource.TestCheckResourceAttrPair(datasourceName, "control.0.name", resourceName, "control.0.name"), - resource.TestCheckResourceAttrPair(datasourceName, "control.0.scope.#", resourceName, "control.0.scope.#"), - resource.TestCheckResourceAttrPair(datasourceName, "control.0.scope.0.tags.%", resourceName, "control.0.scope.0.tags.%"), - resource.TestCheckResourceAttrPair(datasourceName, "control.0.scope.0.tags.Name", resourceName, "control.0.scope.0.tags.Name"), - resource.TestCheckResourceAttrPair(datasourceName, names.AttrCreationTime, resourceName, names.AttrCreationTime), - resource.TestCheckResourceAttrPair(datasourceName, "deployment_status", resourceName, "deployment_status"), - resource.TestCheckResourceAttrPair(datasourceName, names.AttrID, resourceName, names.AttrID), - resource.TestCheckResourceAttrPair(datasourceName, names.AttrName, resourceName, names.AttrName), - resource.TestCheckResourceAttrPair(datasourceName, names.AttrStatus, resourceName, names.AttrStatus), - resource.TestCheckResourceAttrPair(datasourceName, acctest.CtTagsPercent, resourceName, acctest.CtTagsPercent), - resource.TestCheckResourceAttrPair(datasourceName, "tags.Name", resourceName, "tags.Name"), - ), - }, - }, - }) -} - func testAccFrameworkDataSourceConfig_basic(rName string) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { @@ -175,30 +142,3 @@ data "aws_backup_framework" "test" { } `, rName)) } - -func testAccFrameworkDataSourceConfig_controlScopeTag(rName string) string { - return fmt.Sprintf(` -resource "aws_backup_framework" "test" { - name = %[1]q - description = "Example framework" - - control { - name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN" - - scope { - tags = { - "Name" = "Example" - } - } - } - - tags = { - Name = %[1]q - } -} - -data "aws_backup_framework" "test" { - name = aws_backup_framework.test.name -} -`, rName) -} diff --git a/internal/service/backup/framework_test.go b/internal/service/backup/framework_test.go index e78c99aa2cc..6b394276df7 100644 --- a/internal/service/backup/framework_test.go +++ b/internal/service/backup/framework_test.go @@ -32,8 +32,7 @@ func TestAccBackupFramework_serial(t *testing.T) { "UpdateControls": testAccFramework_updateControls, }, "DataSource": { - acctest.CtBasic: testAccFrameworkDataSource_basic, - "ControlScopeTag": testAccFrameworkDataSource_controlScopeTag, + acctest.CtBasic: testAccFrameworkDataSource_basic, }, } From 595d6ec691799078583bf003d5dbac53c8bb539b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 14:39:52 -0400 Subject: [PATCH 12/27] r/aws_backup_global_settings: Reduce visibility. --- internal/service/backup/exports_test.go | 2 + internal/service/backup/global_settings.go | 40 +++++++++++++++--- .../service/backup/global_settings_test.go | 41 +++++++++++++------ .../service/backup/service_package_gen.go | 3 +- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 6bd95eb5ff1..f163720e192 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -6,10 +6,12 @@ package backup // Exports for use in tests only. var ( ResourceFramework = resourceFramework + ResourceGlobalSettings = resourceGlobalSettings ResourceLogicallyAirGappedVault = newLogicallyAirGappedVaultResource FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName + FindGlobalSettings = findGlobalSettings FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/global_settings.go b/internal/service/backup/global_settings.go index 377ee82adac..8841be1cbb0 100644 --- a/internal/service/backup/global_settings.go +++ b/internal/service/backup/global_settings.go @@ -5,6 +5,7 @@ package backup import ( "context" + "log" "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -12,15 +13,17 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_backup_global_settings") -func ResourceGlobalSettings() *schema.Resource { +// @SDKResource("aws_backup_global_settings", name="Global Settings") +func resourceGlobalSettings() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceGlobalSettingsUpdate, UpdateWithoutTimeout: resourceGlobalSettingsUpdate, ReadWithoutTimeout: resourceGlobalSettingsRead, DeleteWithoutTimeout: schema.NoopContext, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -44,11 +47,14 @@ func resourceGlobalSettingsUpdate(ctx context.Context, d *schema.ResourceData, m } _, err := conn.UpdateGlobalSettings(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "setting Backup Global Settings (%s): %s", meta.(*conns.AWSClient).AccountID, err) + return sdkdiag.AppendErrorf(diags, "updating Backup Global Settings: %s", err) } - d.SetId(meta.(*conns.AWSClient).AccountID) + if d.IsNewResource() { + d.SetId(meta.(*conns.AWSClient).AccountID) + } return append(diags, resourceGlobalSettingsRead(ctx, d, meta)...) } @@ -57,14 +63,36 @@ func resourceGlobalSettingsRead(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - resp, err := conn.DescribeGlobalSettings(ctx, &backup.DescribeGlobalSettingsInput{}) + output, err := findGlobalSettings(ctx, conn) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Backup Global Settings (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + if err != nil { return sdkdiag.AppendErrorf(diags, "reading Backup Global Settings (%s): %s", d.Id(), err) } - if err := d.Set("global_settings", resp.GlobalSettings); err != nil { + if err := d.Set("global_settings", output); err != nil { return sdkdiag.AppendErrorf(diags, "setting global_settings: %s", err) } return diags } + +func findGlobalSettings(ctx context.Context, conn *backup.Client) (map[string]string, error) { + input := &backup.DescribeGlobalSettingsInput{} + output, err := conn.DescribeGlobalSettings(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil || output.GlobalSettings == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.GlobalSettings, nil +} diff --git a/internal/service/backup/global_settings_test.go b/internal/service/backup/global_settings_test.go index c4defa1cc27..6dc997e0b1f 100644 --- a/internal/service/backup/global_settings_test.go +++ b/internal/service/backup/global_settings_test.go @@ -8,20 +8,30 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccBackupGlobalSettings_basic(t *testing.T) { - ctx := acctest.Context(t) - var settings backup.DescribeGlobalSettingsOutput +func TestAccBackupGlobalSettings_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]func(t *testing.T){ + acctest.CtBasic: testAccGlobalSettings_basic, + } + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccGlobalSettings_basic(t *testing.T) { + ctx := acctest.Context(t) + var settings map[string]string resourceName := "aws_backup_global_settings.test" - resource.ParallelTest(t, resource.TestCase{ + + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationManagementAccount(ctx, t) @@ -29,12 +39,12 @@ func TestAccBackupGlobalSettings_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: nil, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccGlobalSettingsConfig_basic(acctest.CtTrue), Check: resource.ComposeTestCheckFunc( - testAccCheckGlobalSettingsExists(ctx, &settings), + testAccCheckGlobalSettingsExists(ctx, resourceName, &settings), resource.TestCheckResourceAttr(resourceName, "global_settings.%", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "global_settings.isCrossAccountBackupEnabled", acctest.CtTrue), ), @@ -47,7 +57,7 @@ func TestAccBackupGlobalSettings_basic(t *testing.T) { { Config: testAccGlobalSettingsConfig_basic(acctest.CtFalse), Check: resource.ComposeTestCheckFunc( - testAccCheckGlobalSettingsExists(ctx, &settings), + testAccCheckGlobalSettingsExists(ctx, resourceName, &settings), resource.TestCheckResourceAttr(resourceName, "global_settings.%", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "global_settings.isCrossAccountBackupEnabled", acctest.CtFalse), ), @@ -55,7 +65,7 @@ func TestAccBackupGlobalSettings_basic(t *testing.T) { { Config: testAccGlobalSettingsConfig_basic(acctest.CtTrue), Check: resource.ComposeTestCheckFunc( - testAccCheckGlobalSettingsExists(ctx, &settings), + testAccCheckGlobalSettingsExists(ctx, resourceName, &settings), resource.TestCheckResourceAttr(resourceName, "global_settings.%", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "global_settings.isCrossAccountBackupEnabled", acctest.CtTrue), ), @@ -64,15 +74,22 @@ func TestAccBackupGlobalSettings_basic(t *testing.T) { }) } -func testAccCheckGlobalSettingsExists(ctx context.Context, settings *backup.DescribeGlobalSettingsOutput) resource.TestCheckFunc { +func testAccCheckGlobalSettingsExists(ctx context.Context, n string, v *map[string]string) resource.TestCheckFunc { return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - resp, err := conn.DescribeGlobalSettings(ctx, &backup.DescribeGlobalSettingsInput{}) + + output, err := tfbackup.FindGlobalSettings(ctx, conn) + if err != nil { return err } - *settings = *resp + *v = output return nil } diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 4a0ba86c663..6cb9997ef03 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -67,8 +67,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceGlobalSettings, + Factory: resourceGlobalSettings, TypeName: "aws_backup_global_settings", + Name: "Global Settings", }, { Factory: ResourcePlan, From ecd4d09485142b35c46c04cb80d3a020faa1048a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 14:51:15 -0400 Subject: [PATCH 13/27] r/aws_backup_region_settings: Reduce visibility. --- internal/service/backup/exports_test.go | 2 ++ internal/service/backup/region_settings.go | 36 ++++++++++++++++--- .../service/backup/region_settings_test.go | 19 +++++++--- .../service/backup/service_package_gen.go | 3 +- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index f163720e192..9352db9dabe 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -8,10 +8,12 @@ var ( ResourceFramework = resourceFramework ResourceGlobalSettings = resourceGlobalSettings ResourceLogicallyAirGappedVault = newLogicallyAirGappedVaultResource + ResourceRegionSettings = resourceRegionSettings FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName FindGlobalSettings = findGlobalSettings FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName + FindRegionSettings = findRegionSettings FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/region_settings.go b/internal/service/backup/region_settings.go index 8c2ef75f7b9..181fb63eae7 100644 --- a/internal/service/backup/region_settings.go +++ b/internal/service/backup/region_settings.go @@ -5,6 +5,7 @@ package backup import ( "context" + "log" "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -12,15 +13,17 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_backup_region_settings") -func ResourceRegionSettings() *schema.Resource { +// @SDKResource("aws_backup_region_settings", name="Region Settings") +func resourceRegionSettings() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceRegionSettingsUpdate, UpdateWithoutTimeout: resourceRegionSettingsUpdate, ReadWithoutTimeout: resourceRegionSettingsRead, DeleteWithoutTimeout: schema.NoopContext, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -58,10 +61,12 @@ func resourceRegionSettingsUpdate(ctx context.Context, d *schema.ResourceData, m _, err := conn.UpdateRegionSettings(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "updating Backup Region Settings (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Backup Region Settings: %s", err) } - d.SetId(meta.(*conns.AWSClient).Region) + if d.IsNewResource() { + d.SetId(meta.(*conns.AWSClient).Region) + } return append(diags, resourceRegionSettingsRead(ctx, d, meta)...) } @@ -70,7 +75,13 @@ func resourceRegionSettingsRead(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - output, err := conn.DescribeRegionSettings(ctx, &backup.DescribeRegionSettingsInput{}) + output, err := findRegionSettings(ctx, conn) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Backup Region Settings (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } if err != nil { return sdkdiag.AppendErrorf(diags, "reading Backup Region Settings (%s): %s", d.Id(), err) @@ -81,3 +92,18 @@ func resourceRegionSettingsRead(ctx context.Context, d *schema.ResourceData, met return diags } + +func findRegionSettings(ctx context.Context, conn *backup.Client) (*backup.DescribeRegionSettingsOutput, error) { + input := &backup.DescribeRegionSettingsInput{} + output, err := conn.DescribeRegionSettings(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/backup/region_settings_test.go b/internal/service/backup/region_settings_test.go index b24f811c790..681acfa9d76 100644 --- a/internal/service/backup/region_settings_test.go +++ b/internal/service/backup/region_settings_test.go @@ -12,15 +12,26 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccBackupRegionSettings_basic(t *testing.T) { +func TestAccBackupRegionSettings_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]func(t *testing.T){ + acctest.CtBasic: testAccRegionSettings_basic, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccRegionSettings_basic(t *testing.T) { ctx := acctest.Context(t) var settings backup.DescribeRegionSettingsOutput resourceName := "aws_backup_region_settings.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.FSxEndpointID) @@ -28,7 +39,7 @@ func TestAccBackupRegionSettings_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: nil, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccRegionSettingsConfig_1(), @@ -109,7 +120,7 @@ func testAccCheckRegionSettingsExists(ctx context.Context, v *backup.DescribeReg return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - output, err := conn.DescribeRegionSettings(ctx, &backup.DescribeRegionSettingsInput{}) + output, err := tfbackup.FindRegionSettings(ctx, conn) if err != nil { return err diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 6cb9997ef03..ceed68648a6 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -80,8 +80,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceRegionSettings, + Factory: resourceRegionSettings, TypeName: "aws_backup_region_settings", + Name: "Region Settings", }, { Factory: ResourceReportPlan, From 334c67661c2fd88e9f36897acbb23ea4ca203023 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 15:05:45 -0400 Subject: [PATCH 14/27] r/aws_backup_plan: Reduce visibility. --- internal/service/backup/exports_test.go | 2 ++ internal/service/backup/plan.go | 12 ++++++++---- internal/service/backup/plan_test.go | 4 ++-- internal/service/backup/service_package_gen.go | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 9352db9dabe..6b75b254979 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -8,12 +8,14 @@ var ( ResourceFramework = resourceFramework ResourceGlobalSettings = resourceGlobalSettings ResourceLogicallyAirGappedVault = newLogicallyAirGappedVaultResource + ResourcePlan = resourcePlan ResourceRegionSettings = resourceRegionSettings FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName FindGlobalSettings = findGlobalSettings FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName + FindPlanByID = findPlanByID FindRegionSettings = findRegionSettings FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/plan.go b/internal/service/backup/plan.go index 1bf7d74e37f..7cfca497c9a 100644 --- a/internal/service/backup/plan.go +++ b/internal/service/backup/plan.go @@ -31,7 +31,7 @@ import ( // @SDKResource("aws_backup_plan", name="Plan") // @Tags(identifierAttribute="arn") -func ResourcePlan() *schema.Resource { +func resourcePlan() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourcePlanCreate, ReadWithoutTimeout: resourcePlanRead, @@ -215,7 +215,7 @@ func resourcePlanRead(ctx context.Context, d *schema.ResourceData, meta interfac var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - output, err := FindPlanByID(ctx, conn, d.Id()) + output, err := findPlanByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Backup Plan (%s) not found, removing from state", d.Id()) @@ -248,12 +248,12 @@ func resourcePlanUpdate(ctx context.Context, d *schema.ResourceData, meta interf if d.HasChanges(names.AttrRule, "advanced_backup_setting") { input := &backup.UpdateBackupPlanInput{ - BackupPlanId: aws.String(d.Id()), BackupPlan: &awstypes.BackupPlanInput{ AdvancedBackupSettings: expandPlanAdvancedSettings(d.Get("advanced_backup_setting").(*schema.Set)), BackupPlanName: aws.String(d.Get(names.AttrName).(string)), Rules: expandPlanRules(ctx, d.Get(names.AttrRule).(*schema.Set)), }, + BackupPlanId: aws.String(d.Id()), } _, err := conn.UpdateBackupPlan(ctx, input) @@ -291,11 +291,15 @@ func resourcePlanDelete(ctx context.Context, d *schema.ResourceData, meta interf return diags } -func FindPlanByID(ctx context.Context, conn *backup.Client, id string) (*backup.GetBackupPlanOutput, error) { +func findPlanByID(ctx context.Context, conn *backup.Client, id string) (*backup.GetBackupPlanOutput, error) { input := &backup.GetBackupPlanInput{ BackupPlanId: aws.String(id), } + return findPlan(ctx, conn, input) +} + +func findPlan(ctx context.Context, conn *backup.Client, input *backup.GetBackupPlanInput) (*backup.GetBackupPlanOutput, error) { output, err := conn.GetBackupPlan(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { diff --git a/internal/service/backup/plan_test.go b/internal/service/backup/plan_test.go index 7b6989e4980..8de246aef41 100644 --- a/internal/service/backup/plan_test.go +++ b/internal/service/backup/plan_test.go @@ -702,13 +702,13 @@ func testAccCheckPlanDestroy(ctx context.Context) resource.TestCheckFunc { func testAccCheckPlanExists(ctx context.Context, n string, v *backup.GetBackupPlanOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } + conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) + output, err := tfbackup.FindPlanByID(ctx, conn, rs.Primary.ID) if err != nil { diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index ceed68648a6..9ea99597f63 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -72,7 +72,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Global Settings", }, { - Factory: ResourcePlan, + Factory: resourcePlan, TypeName: "aws_backup_plan", Name: "Plan", Tags: &types.ServicePackageResourceTags{ From f72436718714cc6336be0a8c7ec3df7754e32ea6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 15:09:28 -0400 Subject: [PATCH 15/27] d/aws_backup_plan: Reduce visibility. --- internal/service/backup/plan_data_source.go | 36 +++++++++---------- .../service/backup/service_package_gen.go | 3 +- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/service/backup/plan_data_source.go b/internal/service/backup/plan_data_source.go index f90be0ef188..ba32f7f9dd4 100644 --- a/internal/service/backup/plan_data_source.go +++ b/internal/service/backup/plan_data_source.go @@ -7,7 +7,6 @@ import ( "context" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -16,16 +15,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_backup_plan") -func DataSourcePlan() *schema.Resource { +// @SDKDataSource("aws_backup_plan", name="Plan") +func dataSourcePlan() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourcePlanRead, Schema: map[string]*schema.Schema{ - "plan_id": { - Type: schema.TypeString, - Required: true, - }, names.AttrARN: { Type: schema.TypeString, Computed: true, @@ -34,6 +29,10 @@ func DataSourcePlan() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "plan_id": { + Type: schema.TypeString, + Required: true, + }, names.AttrRule: { Type: schema.TypeSet, Computed: true, @@ -118,7 +117,6 @@ func DataSourcePlan() *schema.Resource { }, }, }, - Set: planHash, }, names.AttrTags: tftags.TagsSchemaComputed(), names.AttrVersion: { @@ -135,26 +133,26 @@ func dataSourcePlanRead(ctx context.Context, d *schema.ResourceData, meta interf ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig id := d.Get("plan_id").(string) + output, err := findPlanByID(ctx, conn, id) - resp, err := conn.GetBackupPlan(ctx, &backup.GetBackupPlanInput{ - BackupPlanId: aws.String(id), - }) if err != nil { - return sdkdiag.AppendErrorf(diags, "getting Backup Plan: %s", err) + return sdkdiag.AppendErrorf(diags, "reading Backup Plan (%s): %s", id, err) } - d.SetId(aws.ToString(resp.BackupPlanId)) - d.Set(names.AttrARN, resp.BackupPlanArn) - d.Set(names.AttrName, resp.BackupPlan.BackupPlanName) - d.Set(names.AttrVersion, resp.VersionId) - if err := d.Set(names.AttrRule, flattenPlanRules(ctx, resp.BackupPlan.Rules)); err != nil { + d.SetId(aws.ToString(output.BackupPlanId)) + d.Set(names.AttrARN, output.BackupPlanArn) + d.Set(names.AttrName, output.BackupPlan.BackupPlanName) + if err := d.Set(names.AttrRule, flattenPlanRules(ctx, output.BackupPlan.Rules)); err != nil { return sdkdiag.AppendErrorf(diags, "setting rule: %s", err) } + d.Set(names.AttrVersion, output.VersionId) + + tags, err := listTags(ctx, conn, d.Get(names.AttrARN).(string)) - tags, err := listTags(ctx, conn, aws.ToString(resp.BackupPlanArn)) if err != nil { - return sdkdiag.AppendErrorf(diags, "listing tags for Backup Plan (%s): %s", id, err) + return sdkdiag.AppendErrorf(diags, "listing tags for Backup Plan (%s): %s", d.Id(), err) } + if err := d.Set(names.AttrTags, tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) } diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 9ea99597f63..927f0ef2dd1 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -38,8 +38,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Framework", }, { - Factory: DataSourcePlan, + Factory: dataSourcePlan, TypeName: "aws_backup_plan", + Name: "Plan", }, { Factory: DataSourceReportPlan, From 68e2b5fa4495922581bad1ad071840142d902982 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 15:17:30 -0400 Subject: [PATCH 16/27] r/aws_backup_report_plan: Reduce visibility. --- internal/service/backup/exports_test.go | 2 + internal/service/backup/report_plan.go | 126 +++++++++--------- .../service/backup/report_plan_data_source.go | 2 +- .../backup/report_plan_data_source_test.go | 2 +- internal/service/backup/report_plan_test.go | 16 +-- .../service/backup/service_package_gen.go | 2 +- internal/service/backup/sweep.go | 2 +- 7 files changed, 75 insertions(+), 77 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 6b75b254979..dfdd80c29a5 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -10,6 +10,7 @@ var ( ResourceLogicallyAirGappedVault = newLogicallyAirGappedVaultResource ResourcePlan = resourcePlan ResourceRegionSettings = resourceRegionSettings + ResourceReportPlan = resourceReportPlan FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName @@ -17,5 +18,6 @@ var ( FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName FindPlanByID = findPlanByID FindRegionSettings = findRegionSettings + FindReportPlanByName = findReportPlanByName FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/report_plan.go b/internal/service/backup/report_plan.go index 3a25670d2e8..4f54108ac9c 100644 --- a/internal/service/backup/report_plan.go +++ b/internal/service/backup/report_plan.go @@ -12,7 +12,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-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + sdkid "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -28,7 +28,7 @@ import ( // @SDKResource("aws_backup_report_plan", name="Report Plan") // @Tags(identifierAttribute="arn") -func ResourceReportPlan() *schema.Resource { +func resourceReportPlan() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceReportPlanCreate, ReadWithoutTimeout: resourceReportPlanRead, @@ -150,7 +150,7 @@ func resourceReportPlanCreate(ctx context.Context, d *schema.ResourceData, meta name := d.Get(names.AttrName).(string) input := &backup.CreateReportPlanInput{ - IdempotencyToken: aws.String(id.UniqueId()), + IdempotencyToken: aws.String(sdkid.UniqueId()), ReportDeliveryChannel: expandReportDeliveryChannel(d.Get("report_delivery_channel").([]interface{})), ReportPlanName: aws.String(name), ReportPlanTags: getTagsIn(ctx), @@ -161,14 +161,13 @@ func resourceReportPlanCreate(ctx context.Context, d *schema.ResourceData, meta input.ReportPlanDescription = aws.String(v.(string)) } - output, err := conn.CreateReportPlan(ctx, input) + _, err := conn.CreateReportPlan(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "creating Backup Report Plan (%s): %s", name, err) } - // Set ID with the name since the name is unique for the report plan. - d.SetId(aws.ToString(output.ReportPlanName)) + d.SetId(name) if _, err := waitReportPlanCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for Backup Report Plan (%s) create: %s", d.Id(), err) @@ -181,7 +180,7 @@ func resourceReportPlanRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - reportPlan, err := FindReportPlanByName(ctx, conn, d.Id()) + reportPlan, err := findReportPlanByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Backup Report Plan %s not found, removing from state", d.Id()) @@ -198,11 +197,9 @@ func resourceReportPlanRead(ctx context.Context, d *schema.ResourceData, meta in d.Set("deployment_status", reportPlan.DeploymentStatus) d.Set(names.AttrDescription, reportPlan.ReportPlanDescription) d.Set(names.AttrName, reportPlan.ReportPlanName) - if err := d.Set("report_delivery_channel", flattenReportDeliveryChannel(reportPlan.ReportDeliveryChannel)); err != nil { return sdkdiag.AppendErrorf(diags, "setting report_delivery_channel: %s", err) } - if err := d.Set("report_setting", flattenReportSetting(reportPlan.ReportSetting)); err != nil { return sdkdiag.AppendErrorf(diags, "setting report_setting: %s", err) } @@ -216,14 +213,13 @@ func resourceReportPlanUpdate(ctx context.Context, d *schema.ResourceData, meta if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) { input := &backup.UpdateReportPlanInput{ - IdempotencyToken: aws.String(id.UniqueId()), + IdempotencyToken: aws.String(sdkid.UniqueId()), ReportDeliveryChannel: expandReportDeliveryChannel(d.Get("report_delivery_channel").([]interface{})), ReportPlanDescription: aws.String(d.Get(names.AttrDescription).(string)), ReportPlanName: aws.String(d.Id()), ReportSetting: expandReportSetting(d.Get("report_setting").([]interface{})), } - log.Printf("[DEBUG] Updating Backup Report Plan: %+v", input) _, err := conn.UpdateReportPlan(ctx, input) if err != nil { @@ -262,123 +258,127 @@ func resourceReportPlanDelete(ctx context.Context, d *schema.ResourceData, meta return diags } -func expandReportDeliveryChannel(reportDeliveryChannel []interface{}) *awstypes.ReportDeliveryChannel { - if len(reportDeliveryChannel) == 0 || reportDeliveryChannel[0] == nil { +func expandReportDeliveryChannel(tfList []interface{}) *awstypes.ReportDeliveryChannel { + if len(tfList) == 0 || tfList[0] == nil { return nil } - tfMap, ok := reportDeliveryChannel[0].(map[string]interface{}) + tfMap, ok := tfList[0].(map[string]interface{}) if !ok { return nil } - result := &awstypes.ReportDeliveryChannel{ + apiObject := &awstypes.ReportDeliveryChannel{ S3BucketName: aws.String(tfMap[names.AttrS3BucketName].(string)), } if v, ok := tfMap["formats"]; ok && v.(*schema.Set).Len() > 0 { - result.Formats = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.Formats = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := tfMap[names.AttrS3KeyPrefix].(string); ok && v != "" { - result.S3KeyPrefix = aws.String(v) + apiObject.S3KeyPrefix = aws.String(v) } - return result + return apiObject } -func expandReportSetting(reportSetting []interface{}) *awstypes.ReportSetting { - if len(reportSetting) == 0 || reportSetting[0] == nil { +func expandReportSetting(tfList []interface{}) *awstypes.ReportSetting { + if len(tfList) == 0 || tfList[0] == nil { return nil } - tfMap, ok := reportSetting[0].(map[string]interface{}) + tfMap, ok := tfList[0].(map[string]interface{}) if !ok { return nil } - result := &awstypes.ReportSetting{ + apiObject := &awstypes.ReportSetting{ ReportTemplate: aws.String(tfMap["report_template"].(string)), } if v, ok := tfMap["accounts"]; ok && v.(*schema.Set).Len() > 0 { - result.Accounts = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.Accounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := tfMap["framework_arns"]; ok && v.(*schema.Set).Len() > 0 { - result.FrameworkArns = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.FrameworkArns = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := tfMap["number_of_frameworks"].(int); ok && v > 0 { - result.NumberOfFrameworks = int32(v) + apiObject.NumberOfFrameworks = int32(v) } if v, ok := tfMap["organization_units"]; ok && v.(*schema.Set).Len() > 0 { - result.OrganizationUnits = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.OrganizationUnits = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := tfMap["regions"]; ok && v.(*schema.Set).Len() > 0 { - result.Regions = flex.ExpandStringValueSet(v.(*schema.Set)) + apiObject.Regions = flex.ExpandStringValueSet(v.(*schema.Set)) } - return result + return apiObject } -func flattenReportDeliveryChannel(reportDeliveryChannel *awstypes.ReportDeliveryChannel) []interface{} { - if reportDeliveryChannel == nil { +func flattenReportDeliveryChannel(apiObject *awstypes.ReportDeliveryChannel) []interface{} { + if apiObject == nil { return []interface{}{} } - values := map[string]interface{}{ - names.AttrS3BucketName: aws.ToString(reportDeliveryChannel.S3BucketName), + tfMap := map[string]interface{}{ + names.AttrS3BucketName: aws.ToString(apiObject.S3BucketName), } - if len(reportDeliveryChannel.Formats) > 0 { - values["formats"] = flex.FlattenStringValueSet(reportDeliveryChannel.Formats) + if len(apiObject.Formats) > 0 { + tfMap["formats"] = apiObject.Formats } - if v := reportDeliveryChannel.S3KeyPrefix; v != nil { - values[names.AttrS3KeyPrefix] = aws.ToString(v) + if v := apiObject.S3KeyPrefix; v != nil { + tfMap[names.AttrS3KeyPrefix] = aws.ToString(v) } - return []interface{}{values} + return []interface{}{tfMap} } -func flattenReportSetting(reportSetting *awstypes.ReportSetting) []interface{} { - if reportSetting == nil { +func flattenReportSetting(apiObject *awstypes.ReportSetting) []interface{} { + if apiObject == nil { return []interface{}{} } - values := map[string]interface{}{ - "report_template": aws.ToString(reportSetting.ReportTemplate), + tfMap := map[string]interface{}{ + "report_template": aws.ToString(apiObject.ReportTemplate), } - if len(reportSetting.Accounts) > 0 { - values["accounts"] = flex.FlattenStringValueSet(reportSetting.Accounts) + if len(apiObject.Accounts) > 0 { + tfMap["accounts"] = apiObject.Accounts } - if len(reportSetting.FrameworkArns) > 0 { - values["framework_arns"] = flex.FlattenStringValueSet(reportSetting.FrameworkArns) + if len(apiObject.FrameworkArns) > 0 { + tfMap["framework_arns"] = apiObject.FrameworkArns } - values["number_of_frameworks"] = reportSetting.NumberOfFrameworks + tfMap["number_of_frameworks"] = apiObject.NumberOfFrameworks - if len(reportSetting.OrganizationUnits) > 0 { - values["organization_units"] = flex.FlattenStringValueSet(reportSetting.OrganizationUnits) + if len(apiObject.OrganizationUnits) > 0 { + tfMap["organization_units"] = apiObject.OrganizationUnits } - if len(reportSetting.Regions) > 0 { - values["regions"] = flex.FlattenStringValueSet(reportSetting.Regions) + if len(apiObject.Regions) > 0 { + tfMap["regions"] = apiObject.Regions } - return []interface{}{values} + return []interface{}{tfMap} } -func FindReportPlanByName(ctx context.Context, conn *backup.Client, name string) (*awstypes.ReportPlan, error) { +func findReportPlanByName(ctx context.Context, conn *backup.Client, name string) (*awstypes.ReportPlan, error) { input := &backup.DescribeReportPlanInput{ ReportPlanName: aws.String(name), } + return findReportPlan(ctx, conn, input) +} + +func findReportPlan(ctx context.Context, conn *backup.Client, input *backup.DescribeReportPlanInput) (*awstypes.ReportPlan, error) { output, err := conn.DescribeReportPlan(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { @@ -399,9 +399,9 @@ func FindReportPlanByName(ctx context.Context, conn *backup.Client, name string) return output.ReportPlan, nil } -func statusReportPlanDeployment(ctx context.Context, conn *backup.Client, name string) retry.StateRefreshFunc { +func statusReportPlan(ctx context.Context, conn *backup.Client, name string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindReportPlanByName(ctx, conn, name) + output, err := findReportPlanByName(ctx, conn, name) if tfresource.NotFound(err) { return nil, "", nil @@ -420,7 +420,7 @@ func waitReportPlanCreated(ctx context.Context, conn *backup.Client, name string Pending: []string{reportPlanDeploymentStatusCreateInProgress}, Target: []string{reportPlanDeploymentStatusCompleted}, Timeout: timeout, - Refresh: statusReportPlanDeployment(ctx, conn, name), + Refresh: statusReportPlan(ctx, conn, name), } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -432,12 +432,12 @@ func waitReportPlanCreated(ctx context.Context, conn *backup.Client, name string return nil, err } -func waitReportPlanDeleted(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*awstypes.ReportPlan, error) { +func waitReportPlanUpdated(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*awstypes.ReportPlan, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{reportPlanDeploymentStatusDeleteInProgress}, - Target: []string{}, + Pending: []string{reportPlanDeploymentStatusUpdateInProgress}, + Target: []string{reportPlanDeploymentStatusCompleted}, Timeout: timeout, - Refresh: statusReportPlanDeployment(ctx, conn, name), + Refresh: statusReportPlan(ctx, conn, name), } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -449,12 +449,12 @@ func waitReportPlanDeleted(ctx context.Context, conn *backup.Client, name string return nil, err } -func waitReportPlanUpdated(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*awstypes.ReportPlan, error) { +func waitReportPlanDeleted(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*awstypes.ReportPlan, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{reportPlanDeploymentStatusUpdateInProgress}, - Target: []string{reportPlanDeploymentStatusCompleted}, + Pending: []string{reportPlanDeploymentStatusDeleteInProgress}, + Target: []string{}, Timeout: timeout, - Refresh: statusReportPlanDeployment(ctx, conn, name), + Refresh: statusReportPlan(ctx, conn, name), } outputRaw, err := stateConf.WaitForStateContext(ctx) diff --git a/internal/service/backup/report_plan_data_source.go b/internal/service/backup/report_plan_data_source.go index 3bd9d71e8ef..2df92bb2f6b 100644 --- a/internal/service/backup/report_plan_data_source.go +++ b/internal/service/backup/report_plan_data_source.go @@ -120,7 +120,7 @@ func dataSourceReportPlanRead(ctx context.Context, d *schema.ResourceData, meta ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig name := d.Get(names.AttrName).(string) - reportPlan, err := FindReportPlanByName(ctx, conn, name) + reportPlan, err := findReportPlanByName(ctx, conn, name) if err != nil { return sdkdiag.AppendErrorf(diags, "reading Backup Report Plan (%s): %s", name, err) diff --git a/internal/service/backup/report_plan_data_source_test.go b/internal/service/backup/report_plan_data_source_test.go index c9a00f66ef7..0fe51eb117d 100644 --- a/internal/service/backup/report_plan_data_source_test.go +++ b/internal/service/backup/report_plan_data_source_test.go @@ -97,7 +97,7 @@ data "aws_backup_report_plan" "test" { ` func testAccReportPlanDataSourceConfig_basic(rName, rName2 string) string { - return acctest.ConfigCompose(testAccReportPlanBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccReportPlanConfig_base(rName), fmt.Sprintf(` resource "aws_backup_report_plan" "test" { name = %[1]q description = "Test report plan data source" diff --git a/internal/service/backup/report_plan_test.go b/internal/service/backup/report_plan_test.go index c67bb9e4de5..b9f4e90cb3c 100644 --- a/internal/service/backup/report_plan_test.go +++ b/internal/service/backup/report_plan_test.go @@ -362,10 +362,6 @@ func testAccCheckReportPlanExists(ctx context.Context, n string, v *awstypes.Rep return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No Backup Report Plan ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) output, err := tfbackup.FindReportPlanByName(ctx, conn, rs.Primary.ID) @@ -380,7 +376,7 @@ func testAccCheckReportPlanExists(ctx context.Context, n string, v *awstypes.Rep } } -func testAccReportPlanBaseConfig(bucketName string) string { +func testAccReportPlanConfig_base(bucketName string) string { return fmt.Sprintf(` data "aws_region" "current" {} @@ -401,7 +397,7 @@ resource "aws_s3_bucket_public_access_block" "test" { } func testAccReportPlanConfig_basic(rName, rName2, label string) string { - return acctest.ConfigCompose(testAccReportPlanBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccReportPlanConfig_base(rName), fmt.Sprintf(` resource "aws_backup_report_plan" "test" { name = %[1]q description = %[2]q @@ -425,7 +421,7 @@ resource "aws_backup_report_plan" "test" { } func testAccReportPlanConfig_tags1(rName, rName2, label string) string { - return acctest.ConfigCompose(testAccReportPlanBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccReportPlanConfig_base(rName), fmt.Sprintf(` resource "aws_backup_report_plan" "test" { name = %[1]q description = %[2]q @@ -450,7 +446,7 @@ resource "aws_backup_report_plan" "test" { } func testAccReportPlanConfig_tags2(rName, rName2, label string) string { - return acctest.ConfigCompose(testAccReportPlanBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccReportPlanConfig_base(rName), fmt.Sprintf(` resource "aws_backup_report_plan" "test" { name = %[1]q description = %[2]q @@ -476,7 +472,7 @@ resource "aws_backup_report_plan" "test" { } func testAccReportPlanConfig_deliveryChannel(rName, rName2, label string) string { - return acctest.ConfigCompose(testAccReportPlanBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccReportPlanConfig_base(rName), fmt.Sprintf(` resource "aws_backup_report_plan" "test" { name = %[1]q description = %[2]q @@ -501,7 +497,7 @@ resource "aws_backup_report_plan" "test" { } func testAccReportPlanConfig_reportSettings(rName, rName2, label string) string { - return acctest.ConfigCompose(testAccReportPlanBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccReportPlanConfig_base(rName), fmt.Sprintf(` resource "aws_backup_report_plan" "test" { name = %[1]q description = %[2]q diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 927f0ef2dd1..e68161d79bb 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -86,7 +86,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Region Settings", }, { - Factory: ResourceReportPlan, + Factory: resourceReportPlan, TypeName: "aws_backup_report_plan", Name: "Report Plan", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index b5913dce0ea..0e2c28b4ccc 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -115,7 +115,7 @@ func sweepReportPlan(region string) error { } for _, v := range page.ReportPlans { - r := ResourceReportPlan() + r := resourceReportPlan() d := r.Data(nil) d.SetId(aws.ToString(v.ReportPlanName)) From c549d9f9f7b94c93144acd1b3e59b17b056fd736 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 15:21:01 -0400 Subject: [PATCH 17/27] d/aws_backup_report_plan: Reduce visibility. --- internal/service/backup/report_plan_data_source.go | 12 ++++-------- internal/service/backup/service_package_gen.go | 3 ++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/service/backup/report_plan_data_source.go b/internal/service/backup/report_plan_data_source.go index 2df92bb2f6b..91bc2d40f56 100644 --- a/internal/service/backup/report_plan_data_source.go +++ b/internal/service/backup/report_plan_data_source.go @@ -7,7 +7,6 @@ import ( "context" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -16,8 +15,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_backup_report_plan") -func DataSourceReportPlan() *schema.Resource { +// @SDKDataSource("aws_backup_report_plan", name="Report Plan") +func dataSourceReportPlan() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceReportPlanRead, @@ -126,23 +125,20 @@ func dataSourceReportPlanRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "reading Backup Report Plan (%s): %s", name, err) } - d.SetId(aws.ToString(reportPlan.ReportPlanName)) - + d.SetId(name) d.Set(names.AttrARN, reportPlan.ReportPlanArn) d.Set(names.AttrCreationTime, reportPlan.CreationTime.Format(time.RFC3339)) d.Set("deployment_status", reportPlan.DeploymentStatus) d.Set(names.AttrDescription, reportPlan.ReportPlanDescription) d.Set(names.AttrName, reportPlan.ReportPlanName) - if err := d.Set("report_delivery_channel", flattenReportDeliveryChannel(reportPlan.ReportDeliveryChannel)); err != nil { return sdkdiag.AppendErrorf(diags, "setting report_delivery_channel: %s", err) } - if err := d.Set("report_setting", flattenReportSetting(reportPlan.ReportSetting)); err != nil { return sdkdiag.AppendErrorf(diags, "setting report_setting: %s", err) } - tags, err := listTags(ctx, conn, aws.ToString(reportPlan.ReportPlanArn)) + tags, err := listTags(ctx, conn, d.Get(names.AttrARN).(string)) if err != nil { return sdkdiag.AppendErrorf(diags, "listing tags for Backup Report Plan (%s): %s", d.Id(), err) diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index e68161d79bb..db78d17e732 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -43,8 +43,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Plan", }, { - Factory: DataSourceReportPlan, + Factory: dataSourceReportPlan, TypeName: "aws_backup_report_plan", + Name: "Report Plan", }, { Factory: DataSourceSelection, From c33c6035e540d8631a35cd45567e6ac92120ca55 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 15:53:09 -0400 Subject: [PATCH 18/27] r/aws_backup_selection: Reduce visibility. --- internal/service/backup/consts.go | 2 +- internal/service/backup/exports_test.go | 2 + internal/service/backup/selection.go | 357 ++++++++---------- internal/service/backup/selection_test.go | 104 ++--- .../service/backup/service_package_gen.go | 3 +- internal/service/backup/vault_policy.go | 2 +- internal/service/backup/wait.go | 5 - 7 files changed, 193 insertions(+), 282 deletions(-) diff --git a/internal/service/backup/consts.go b/internal/service/backup/consts.go index 3e412bf7ff1..1bb9fa60964 100644 --- a/internal/service/backup/consts.go +++ b/internal/service/backup/consts.go @@ -6,7 +6,7 @@ package backup import "time" const ( - iamPropagationTimeout = 2 * time.Minute + propagationTimeout = 2 * time.Minute ) const ( diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index dfdd80c29a5..08473da61cd 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -11,6 +11,7 @@ var ( ResourcePlan = resourcePlan ResourceRegionSettings = resourceRegionSettings ResourceReportPlan = resourceReportPlan + ResourceSelection = resourceSelection FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName @@ -19,5 +20,6 @@ var ( FindPlanByID = findPlanByID FindRegionSettings = findRegionSettings FindReportPlanByName = findReportPlanByName + FindSelectionByTwoPartKey = findSelectionByTwoPartKey FindVaultAccessPolicyByName = findVaultAccessPolicyByName ) diff --git a/internal/service/backup/selection.go b/internal/service/backup/selection.go index 08cb3a5218d..97055dbe8d0 100644 --- a/internal/service/backup/selection.go +++ b/internal/service/backup/selection.go @@ -4,11 +4,11 @@ package backup import ( - "bytes" "context" "fmt" "log" "strings" + "time" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" @@ -19,7 +19,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" @@ -29,8 +28,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_backup_selection") -func ResourceSelection() *schema.Resource { +// @SDKResource("aws_backup_selection", name="Selection") +func resourceSelection() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceSelectionCreate, ReadWithoutTimeout: resourceSelectionRead, @@ -49,11 +48,6 @@ func ResourceSelection() *schema.Resource { validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z_.-]+$`), "must contain only alphanumeric, hyphen, underscore, and period characters"), ), }, - "plan_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, names.AttrCondition: { Type: schema.TypeSet, Optional: true, @@ -146,23 +140,35 @@ func ResourceSelection() *schema.Resource { ForceNew: true, ValidateFunc: verify.ValidARN, }, + "not_resources": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "plan_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "selection_tag": { Type: schema.TypeSet, Optional: true, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + names.AttrKey: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, names.AttrType: { Type: schema.TypeString, Required: true, ForceNew: true, ValidateDiagFunc: enum.Validate[awstypes.ConditionType](), }, - names.AttrKey: { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, names.AttrValue: { Type: schema.TypeString, Required: true, @@ -171,13 +177,6 @@ func ResourceSelection() *schema.Resource { }, }, }, - "not_resources": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, names.AttrResources: { Type: schema.TypeSet, Optional: true, @@ -192,57 +191,58 @@ func resourceSelectionCreate(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - selection := &awstypes.BackupSelection{ - Conditions: expandConditions(d.Get(names.AttrCondition).(*schema.Set).List()), - IamRoleArn: aws.String(d.Get(names.AttrIAMRoleARN).(string)), - ListOfTags: expandConditionTags(d.Get("selection_tag").(*schema.Set).List()), - NotResources: flex.ExpandStringValueSet(d.Get("not_resources").(*schema.Set)), - Resources: flex.ExpandStringValueSet(d.Get(names.AttrResources).(*schema.Set)), - SelectionName: aws.String(d.Get(names.AttrName).(string)), - } - + name := d.Get(names.AttrName).(string) + planID := d.Get("plan_id").(string) input := &backup.CreateBackupSelectionInput{ - BackupPlanId: aws.String(d.Get("plan_id").(string)), - BackupSelection: selection, + BackupPlanId: aws.String(planID), + BackupSelection: &awstypes.BackupSelection{ + Conditions: expandConditions(d.Get(names.AttrCondition).(*schema.Set).List()), + IamRoleArn: aws.String(d.Get(names.AttrIAMRoleARN).(string)), + ListOfTags: expandConditionTags(d.Get("selection_tag").(*schema.Set).List()), + NotResources: flex.ExpandStringValueSet(d.Get("not_resources").(*schema.Set)), + Resources: flex.ExpandStringValueSet(d.Get(names.AttrResources).(*schema.Set)), + SelectionName: aws.String(name), + }, } - // Retry for IAM eventual consistency - var output *backup.CreateBackupSelectionOutput - err := retry.RetryContext(ctx, propagationTimeout, func() *retry.RetryError { - var err error - output, err = conn.CreateBackupSelection(ctx, input) - - // Retry on the following error: - // InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX cannot be assumed by AWS Backup - if errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "cannot be assumed") { - log.Printf("[DEBUG] Received %s, retrying create backup selection.", err) - return retry.RetryableError(err) - } + // Retry for IAM eventual consistency. + outputRaw, err := tfresource.RetryWhen(ctx, propagationTimeout, + func() (interface{}, error) { + return conn.CreateBackupSelection(ctx, input) + }, + func(err error) (bool, error) { + // InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX cannot be assumed by AWS Backup + if errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "cannot be assumed") { + return true, err + } + + // InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX is not authorized to call tag:GetResources + if errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "is not authorized to call") { + return true, err + } + + return false, err + }, + ) - // Retry on the following error: - // InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX is not authorized to call tag:GetResources - if errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "is not authorized to call") { - log.Printf("[DEBUG] Received %s, retrying create backup selection.", err) - return retry.RetryableError(err) - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating Backup Selection (%s): %s", name, err) + } - if err != nil { - return retry.NonRetryableError(err) - } + d.SetId(aws.ToString(outputRaw.(*backup.CreateBackupSelectionOutput).SelectionId)) - return nil + const ( + // Maximum amount of time to wait for Backup changes to propagate. + timeout = 2 * time.Minute + ) + _, err = tfresource.RetryWhenNotFound(ctx, timeout, func() (interface{}, error) { + return findSelectionByTwoPartKey(ctx, conn, planID, d.Id()) }) - if tfresource.TimedOut(err) { - output, err = conn.CreateBackupSelection(ctx, input) - } - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Backup Selection: %s", err) + return sdkdiag.AppendErrorf(diags, "waiting for Backup Selection (%s) create: %s", d.Id(), err) } - d.SetId(aws.ToString(output.SelectionId)) - return append(diags, resourceSelectionRead(ctx, d, meta)...) } @@ -250,44 +250,10 @@ func resourceSelectionRead(ctx context.Context, d *schema.ResourceData, meta int var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - input := &backup.GetBackupSelectionInput{ - BackupPlanId: aws.String(d.Get("plan_id").(string)), - SelectionId: aws.String(d.Id()), - } - - var resp *backup.GetBackupSelectionOutput - - err := retry.RetryContext(ctx, propagationTimeout, func() *retry.RetryError { - var err error - - resp, err = conn.GetBackupSelection(ctx, input) + planID := d.Get("plan_id").(string) + output, err := findSelectionByTwoPartKey(ctx, conn, planID, d.Id()) - if d.IsNewResource() && errs.IsA[*awstypes.ResourceNotFoundException](err) { - return retry.RetryableError(err) - } - - if d.IsNewResource() && errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "Cannot find Backup plan") { - return retry.RetryableError(err) - } - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil - }) - - if tfresource.TimedOut(err) { - resp, err = conn.GetBackupSelection(ctx, input) - } - - if !d.IsNewResource() && errs.IsA[*awstypes.ResourceNotFoundException](err) { - log.Printf("[WARN] Backup Selection (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - - if !d.IsNewResource() && errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "Cannot find Backup plan") { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Backup Selection (%s) not found, removing from state", d.Id()) d.SetId("") return diags @@ -297,46 +263,38 @@ func resourceSelectionRead(ctx context.Context, d *schema.ResourceData, meta int return sdkdiag.AppendErrorf(diags, "reading Backup Selection (%s): %s", d.Id(), err) } - if resp == nil { - return sdkdiag.AppendErrorf(diags, "reading Backup Selection (%s): empty response", d.Id()) - } - - d.Set("plan_id", resp.BackupPlanId) - d.Set(names.AttrName, resp.BackupSelection.SelectionName) - d.Set(names.AttrIAMRoleARN, resp.BackupSelection.IamRoleArn) - - if conditions := resp.BackupSelection.Conditions; conditions != nil { - if err := d.Set(names.AttrCondition, flattenConditions(conditions)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting conditions: %s", err) + if v := output.Conditions; v != nil { + if err := d.Set(names.AttrCondition, flattenConditions(v)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting condition: %s", err) } } + d.Set(names.AttrIAMRoleARN, output.IamRoleArn) + d.Set(names.AttrName, output.SelectionName) + if err := d.Set("not_resources", output.NotResources); err != nil { + return sdkdiag.AppendErrorf(diags, "setting not resources: %s", err) + } + d.Set("plan_id", planID) + if err := d.Set(names.AttrResources, output.Resources); err != nil { + return sdkdiag.AppendErrorf(diags, "setting resources: %s", err) + } + if v := output.ListOfTags; v != nil { + tfList := make([]interface{}, 0) - if resp.BackupSelection.ListOfTags != nil { - tags := make([]map[string]interface{}, 0) - - for _, r := range resp.BackupSelection.ListOfTags { - m := make(map[string]interface{}) + for _, v := range v { + tfMap := make(map[string]interface{}) - m[names.AttrType] = string(r.ConditionType) - m[names.AttrKey] = aws.ToString(r.ConditionKey) - m[names.AttrValue] = aws.ToString(r.ConditionValue) + tfMap[names.AttrKey] = aws.ToString(v.ConditionKey) + tfMap[names.AttrType] = v.ConditionType + tfMap[names.AttrValue] = aws.ToString(v.ConditionValue) - tags = append(tags, m) + tfList = append(tfList, tfMap) } - if err := d.Set("selection_tag", tags); err != nil { + if err := d.Set("selection_tag", tfList); err != nil { return sdkdiag.AppendErrorf(diags, "setting selection tag: %s", err) } } - if err := d.Set(names.AttrResources, resp.BackupSelection.Resources); err != nil { - return sdkdiag.AppendErrorf(diags, "setting resources: %s", err) - } - - if err := d.Set("not_resources", resp.BackupSelection.NotResources); err != nil { - return sdkdiag.AppendErrorf(diags, "setting not resources: %s", err) - } - return diags } @@ -344,19 +302,18 @@ func resourceSelectionDelete(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - input := &backup.DeleteBackupSelectionInput{ + log.Printf("[DEBUG] Deleting Backup Selection: %s", d.Id()) + _, err := conn.DeleteBackupSelection(ctx, &backup.DeleteBackupSelectionInput{ BackupPlanId: aws.String(d.Get("plan_id").(string)), SelectionId: aws.String(d.Id()), - } - - _, err := conn.DeleteBackupSelection(ctx, input) + }) if errs.IsA[*awstypes.InvalidParameterValueException](err) { return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Backup Selection: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting Backup Selection (%s): %s", d.Id(), err) } return diags @@ -377,112 +334,110 @@ func resourceSelectionImportState(ctx context.Context, d *schema.ResourceData, m return []*schema.ResourceData{d}, nil } -func expandConditionTags(tagList []interface{}) []awstypes.Condition { - conditions := []awstypes.Condition{} - - for _, i := range tagList { - item := i.(map[string]interface{}) - tag := awstypes.Condition{} - - tag.ConditionType = awstypes.ConditionType(item[names.AttrType].(string)) - tag.ConditionKey = aws.String(item[names.AttrKey].(string)) - tag.ConditionValue = aws.String(item[names.AttrValue].(string)) - - conditions = append(conditions, tag) +func findSelectionByTwoPartKey(ctx context.Context, conn *backup.Client, planID, selectionID string) (*awstypes.BackupSelection, error) { + input := &backup.GetBackupSelectionInput{ + BackupPlanId: aws.String(planID), + SelectionId: aws.String(selectionID), } - return conditions + return findSelection(ctx, conn, input) } -func expandConditions(conditionsList []interface{}) *awstypes.Conditions { - conditions := &awstypes.Conditions{} - - for _, condition := range conditionsList { - mCondition := condition.(map[string]interface{}) +func findSelection(ctx context.Context, conn *backup.Client, input *backup.GetBackupSelectionInput) (*awstypes.BackupSelection, error) { + output, err := conn.GetBackupSelection(ctx, input) - if vStringEquals := expandConditionParameters(mCondition["string_equals"].(*schema.Set).List()); len(vStringEquals) > 0 { - conditions.StringEquals = vStringEquals - } - if vStringNotEquals := expandConditionParameters(mCondition["string_not_equals"].(*schema.Set).List()); len(vStringNotEquals) > 0 { - conditions.StringNotEquals = vStringNotEquals - } - if vStringLike := expandConditionParameters(mCondition["string_like"].(*schema.Set).List()); len(vStringLike) > 0 { - conditions.StringLike = vStringLike - } - if vStringNotLike := expandConditionParameters(mCondition["string_not_like"].(*schema.Set).List()); len(vStringNotLike) > 0 { - conditions.StringNotLike = vStringNotLike + if errs.IsA[*awstypes.ResourceNotFoundException](err) || errs.IsAErrorMessageContains[*awstypes.InvalidParameterValueException](err, "Cannot find Backup plan") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } } - return conditions + if output == nil || output.BackupSelection == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.BackupSelection, nil } -func expandConditionParameters(conditionParametersList []interface{}) []awstypes.ConditionParameter { - conditionParameters := []awstypes.ConditionParameter{} +func expandConditionTags(tfList []interface{}) []awstypes.Condition { + apiObjects := []awstypes.Condition{} - for _, i := range conditionParametersList { - item := i.(map[string]interface{}) - conditionParameter := awstypes.ConditionParameter{} + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + apiObject := awstypes.Condition{} - conditionParameter.ConditionKey = aws.String(item[names.AttrKey].(string)) - conditionParameter.ConditionValue = aws.String(item[names.AttrValue].(string)) + apiObject.ConditionKey = aws.String(tfMap[names.AttrKey].(string)) + apiObject.ConditionType = awstypes.ConditionType(tfMap[names.AttrType].(string)) + apiObject.ConditionValue = aws.String(tfMap[names.AttrValue].(string)) - conditionParameters = append(conditionParameters, conditionParameter) + apiObjects = append(apiObjects, apiObject) } - return conditionParameters + return apiObjects } -func flattenConditions(conditions *awstypes.Conditions) *schema.Set { - var vConditions []interface{} +func expandConditions(tfList []interface{}) *awstypes.Conditions { + apiObject := &awstypes.Conditions{} - mCondition := map[string]interface{}{} + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) - mCondition["string_equals"] = flattenConditionParameters(conditions.StringEquals) - mCondition["string_not_equals"] = flattenConditionParameters(conditions.StringNotEquals) - mCondition["string_like"] = flattenConditionParameters(conditions.StringLike) - mCondition["string_not_like"] = flattenConditionParameters(conditions.StringNotLike) - - vConditions = append(vConditions, mCondition) + if v := expandConditionParameters(tfMap["string_equals"].(*schema.Set).List()); len(v) > 0 { + apiObject.StringEquals = v + } + if v := expandConditionParameters(tfMap["string_not_equals"].(*schema.Set).List()); len(v) > 0 { + apiObject.StringNotEquals = v + } + if v := expandConditionParameters(tfMap["string_like"].(*schema.Set).List()); len(v) > 0 { + apiObject.StringLike = v + } + if v := expandConditionParameters(tfMap["string_not_like"].(*schema.Set).List()); len(v) > 0 { + apiObject.StringNotLike = v + } + } - return schema.NewSet(conditionsHash, vConditions) + return apiObject } -func conditionsHash(vCondition interface{}) int { - var buf bytes.Buffer +func expandConditionParameters(tfList []interface{}) []awstypes.ConditionParameter { + apiObjects := []awstypes.ConditionParameter{} - mCondition := vCondition.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + apiObject := awstypes.ConditionParameter{} - if v, ok := mCondition["string_equals"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } + apiObject.ConditionKey = aws.String(tfMap[names.AttrKey].(string)) + apiObject.ConditionValue = aws.String(tfMap[names.AttrValue].(string)) - if v, ok := mCondition["string_not_equals"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) + apiObjects = append(apiObjects, apiObject) } - if v, ok := mCondition["string_like"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } + return apiObjects +} - if v, ok := mCondition["string_not_like"].(string); ok { - buf.WriteString(fmt.Sprintf("%s-", v)) - } +func flattenConditions(apiObject *awstypes.Conditions) []interface{} { + tfMap := map[string]interface{}{} + + tfMap["string_equals"] = flattenConditionParameters(apiObject.StringEquals) + tfMap["string_not_equals"] = flattenConditionParameters(apiObject.StringNotEquals) + tfMap["string_like"] = flattenConditionParameters(apiObject.StringLike) + tfMap["string_not_like"] = flattenConditionParameters(apiObject.StringNotLike) - return create.StringHashcode(buf.String()) + return []interface{}{tfMap} } -func flattenConditionParameters(conditionParameters []awstypes.ConditionParameter) []interface{} { - if len(conditionParameters) == 0 { +func flattenConditionParameters(apiObjects []awstypes.ConditionParameter) []interface{} { + if len(apiObjects) == 0 { return nil } var tfList []interface{} - for _, conditionParameter := range conditionParameters { + for _, apiObject := range apiObjects { tfMap := map[string]interface{}{ - names.AttrKey: aws.ToString(conditionParameter.ConditionKey), - names.AttrValue: aws.ToString(conditionParameter.ConditionValue), + names.AttrKey: aws.ToString(apiObject.ConditionKey), + names.AttrValue: aws.ToString(apiObject.ConditionValue), } tfList = append(tfList, tfMap) diff --git a/internal/service/backup/selection_test.go b/internal/service/backup/selection_test.go index 1602d18256b..28c356f666f 100644 --- a/internal/service/backup/selection_test.go +++ b/internal/service/backup/selection_test.go @@ -8,20 +8,20 @@ import ( "fmt" "testing" - "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" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccBackupSelection_basic(t *testing.T) { ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput + var v awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -34,7 +34,7 @@ func TestAccBackupSelection_basic(t *testing.T) { { Config: testAccSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v), ), }, { @@ -49,7 +49,7 @@ func TestAccBackupSelection_basic(t *testing.T) { func TestAccBackupSelection_disappears(t *testing.T) { ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput + var v awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -62,7 +62,7 @@ func TestAccBackupSelection_disappears(t *testing.T) { { Config: testAccSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceSelection(), resourceName), ), ExpectNonEmptyPlan: true, @@ -71,35 +71,9 @@ func TestAccBackupSelection_disappears(t *testing.T) { }) } -func TestAccBackupSelection_Disappears_backupPlan(t *testing.T) { - ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput - resourceName := "aws_backup_selection.test" - backupPlanResourceName := "aws_backup_plan.test" - rName := 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: testAccCheckSelectionDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccSelectionConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceSelection(), resourceName), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourcePlan(), backupPlanResourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - func TestAccBackupSelection_withTags(t *testing.T) { ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput + var v awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -112,7 +86,7 @@ func TestAccBackupSelection_withTags(t *testing.T) { { Config: testAccSelectionConfig_tags(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v), resource.TestCheckResourceAttr(resourceName, "selection_tag.#", acctest.Ct2), ), }, @@ -128,7 +102,7 @@ func TestAccBackupSelection_withTags(t *testing.T) { func TestAccBackupSelection_conditionsWithTags(t *testing.T) { ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput + var v awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -141,7 +115,7 @@ func TestAccBackupSelection_conditionsWithTags(t *testing.T) { { Config: testAccSelectionConfig_conditionsTags(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v), resource.TestCheckResourceAttr(resourceName, "condition.#", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, "condition.0.string_equals.#", acctest.Ct2), resource.TestCheckResourceAttr(resourceName, "condition.0.string_like.#", acctest.Ct1), @@ -161,7 +135,7 @@ func TestAccBackupSelection_conditionsWithTags(t *testing.T) { func TestAccBackupSelection_withResources(t *testing.T) { ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput + var v awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -174,7 +148,7 @@ func TestAccBackupSelection_withResources(t *testing.T) { { Config: testAccSelectionConfig_resources(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v), resource.TestCheckResourceAttr(resourceName, "resources.#", acctest.Ct2), ), }, @@ -190,7 +164,7 @@ func TestAccBackupSelection_withResources(t *testing.T) { func TestAccBackupSelection_withNotResources(t *testing.T) { ctx := acctest.Context(t) - var selection1 backup.GetBackupSelectionOutput + var v awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -203,7 +177,7 @@ func TestAccBackupSelection_withNotResources(t *testing.T) { { Config: testAccSelectionConfig_notResources(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v), resource.TestCheckResourceAttr(resourceName, "not_resources.#", acctest.Ct1), ), }, @@ -219,7 +193,7 @@ func TestAccBackupSelection_withNotResources(t *testing.T) { func TestAccBackupSelection_updateTag(t *testing.T) { ctx := acctest.Context(t) - var selection1, selection2 backup.GetBackupSelectionOutput + var v1, v2 awstypes.BackupSelection resourceName := "aws_backup_selection.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -232,14 +206,13 @@ func TestAccBackupSelection_updateTag(t *testing.T) { { Config: testAccSelectionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection1), + testAccCheckSelectionExists(ctx, resourceName, &v1), ), }, { Config: testAccSelectionConfig_updateTag(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSelectionExists(ctx, resourceName, &selection2), - testAccCheckSelectionRecreated(t, &selection1, &selection2), + testAccCheckSelectionExists(ctx, resourceName, &v2), ), }, { @@ -255,61 +228,46 @@ func TestAccBackupSelection_updateTag(t *testing.T) { func testAccCheckSelectionDestroy(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_selection" { continue } - input := &backup.GetBackupSelectionInput{ - BackupPlanId: aws.String(rs.Primary.Attributes["plan_id"]), - SelectionId: aws.String(rs.Primary.ID), - } + _, err := tfbackup.FindSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["plan_id"], rs.Primary.ID) - resp, err := conn.GetBackupSelection(ctx, input) + if tfresource.NotFound(err) { + continue + } - if err == nil { - if *resp.SelectionId == rs.Primary.ID { - return fmt.Errorf("Selection '%s' was not deleted properly", rs.Primary.ID) - } + if err != nil { + return err } + + return fmt.Errorf("Backup Selection %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckSelectionExists(ctx context.Context, name string, selection *backup.GetBackupSelectionOutput) resource.TestCheckFunc { +func testAccCheckSelectionExists(ctx context.Context, n string, v *awstypes.BackupSelection) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("not found: %s, %v", name, s.RootModule().Resources) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - input := &backup.GetBackupSelectionInput{ - BackupPlanId: aws.String(rs.Primary.Attributes["plan_id"]), - SelectionId: aws.String(rs.Primary.ID), - } - - output, err := conn.GetBackupSelection(ctx, input) + output, err := tfbackup.FindSelectionByTwoPartKey(ctx, conn, rs.Primary.Attributes["plan_id"], rs.Primary.ID) if err != nil { return err } - *selection = *output - - return nil - } -} + *v = *output -func testAccCheckSelectionRecreated(t *testing.T, - before, after *backup.GetBackupSelectionOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *before.SelectionId == *after.SelectionId { - t.Fatalf("Expected change of Backup Selection IDs, but both were %s", *before.SelectionId) - } return nil } } diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index db78d17e732..1a3d2a3f1b7 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -95,8 +95,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceSelection, + Factory: resourceSelection, TypeName: "aws_backup_selection", + Name: "Selection", }, { Factory: ResourceVault, diff --git a/internal/service/backup/vault_policy.go b/internal/service/backup/vault_policy.go index 59bc4b69471..6cdcfeb80cb 100644 --- a/internal/service/backup/vault_policy.go +++ b/internal/service/backup/vault_policy.go @@ -75,7 +75,7 @@ func resourceVaultPolicyPut(ctx context.Context, d *schema.ResourceData, meta in Policy: aws.String(policy), } - _, err = tfresource.RetryWhenAWSErrMessageContains(ctx, iamPropagationTimeout, + _, err = tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) { return conn.PutBackupVaultAccessPolicy(ctx, input) }, diff --git a/internal/service/backup/wait.go b/internal/service/backup/wait.go index dd810d193dc..ae1f47cd216 100644 --- a/internal/service/backup/wait.go +++ b/internal/service/backup/wait.go @@ -16,11 +16,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -const ( - // Maximum amount of time to wait for Backup changes to propagate - propagationTimeout = 2 * time.Minute -) - func WaitJobCompleted(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeBackupJobOutput, error) { stateConf := &retry.StateChangeConf{ Pending: enum.Slice(awstypes.BackupJobStateCreated, awstypes.BackupJobStatePending, awstypes.BackupJobStateRunning, awstypes.BackupJobStateAborting), From 6ba32b5f448df56fa1e8905e19a3b5860fa5a852 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:05:23 -0400 Subject: [PATCH 19/27] d/aws_backup_selection: Reduce visibility. --- .../service/backup/selection_data_source.go | 42 ++++++++----------- .../service/backup/service_package_gen.go | 3 +- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/internal/service/backup/selection_data_source.go b/internal/service/backup/selection_data_source.go index 401bec6c275..6fa04770133 100644 --- a/internal/service/backup/selection_data_source.go +++ b/internal/service/backup/selection_data_source.go @@ -6,8 +6,6 @@ package backup import ( "context" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -15,20 +13,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_backup_selection") -func DataSourceSelection() *schema.Resource { +// @SDKDataSource("aws_backup_selection", name="Selection") +func dataSourceSelection() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceSelectionRead, Schema: map[string]*schema.Schema{ - "plan_id": { - Type: schema.TypeString, - Required: true, - }, - "selection_id": { - Type: schema.TypeString, - Required: true, - }, names.AttrIAMRoleARN: { Type: schema.TypeString, Computed: true, @@ -37,11 +27,19 @@ func DataSourceSelection() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "plan_id": { + Type: schema.TypeString, + Required: true, + }, names.AttrResources: { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "selection_id": { + Type: schema.TypeString, + Required: true, + }, }, } } @@ -50,23 +48,17 @@ func dataSourceSelectionRead(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - input := &backup.GetBackupSelectionInput{ - BackupPlanId: aws.String(d.Get("plan_id").(string)), - SelectionId: aws.String(d.Get("selection_id").(string)), - } + planID, selectionID := d.Get("plan_id").(string), d.Get("selection_id").(string) + output, err := findSelectionByTwoPartKey(ctx, conn, planID, selectionID) - resp, err := conn.GetBackupSelection(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "getting Backup Selection: %s", err) + return sdkdiag.AppendErrorf(diags, "reading Backup Selection (%s): %s", selectionID, err) } - d.SetId(aws.ToString(resp.SelectionId)) - d.Set(names.AttrIAMRoleARN, resp.BackupSelection.IamRoleArn) - d.Set(names.AttrName, resp.BackupSelection.SelectionName) - - if err := d.Set(names.AttrResources, resp.BackupSelection.Resources); err != nil { - return sdkdiag.AppendErrorf(diags, "setting resources: %s", err) - } + d.SetId(selectionID) + d.Set(names.AttrIAMRoleARN, output.IamRoleArn) + d.Set(names.AttrName, output.SelectionName) + d.Set(names.AttrResources, output.Resources) return diags } diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 1a3d2a3f1b7..4303ec9427d 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -48,8 +48,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Report Plan", }, { - Factory: DataSourceSelection, + Factory: dataSourceSelection, TypeName: "aws_backup_selection", + Name: "Selection", }, { Factory: DataSourceVault, From 97de21c81ad608a7c43a5a5c935fcfa95661dda3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:11:04 -0400 Subject: [PATCH 20/27] r/aws_backup_vault_lock_configuration: Reduce visibility. --- internal/service/backup/exports_test.go | 1 + internal/service/backup/service_package_gen.go | 3 ++- internal/service/backup/sweep.go | 2 +- internal/service/backup/vault_lock_configuration.go | 5 +++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 08473da61cd..330976a15ac 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -12,6 +12,7 @@ var ( ResourceRegionSettings = resourceRegionSettings ResourceReportPlan = resourceReportPlan ResourceSelection = resourceSelection + ResourceVaultLockConfiguration = resourceVaultLockConfiguration FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 4303ec9427d..ca34ad6d211 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -109,8 +109,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceVaultLockConfiguration, + Factory: resourceVaultLockConfiguration, TypeName: "aws_backup_vault_lock_configuration", + Name: "Vault Lock Configuration", }, { Factory: ResourceVaultNotifications, diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index 0e2c28b4ccc..1a9f94d5f79 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -154,7 +154,7 @@ func sweepVaultLockConfiguration(region string) error { } for _, v := range page.BackupVaultList { - r := ResourceVaultLockConfiguration() + r := resourceVaultLockConfiguration() d := r.Data(nil) d.SetId(aws.ToString(v.BackupVaultName)) diff --git a/internal/service/backup/vault_lock_configuration.go b/internal/service/backup/vault_lock_configuration.go index 5dbbd9c2685..1ab83ae380c 100644 --- a/internal/service/backup/vault_lock_configuration.go +++ b/internal/service/backup/vault_lock_configuration.go @@ -20,12 +20,13 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_backup_vault_lock_configuration") -func ResourceVaultLockConfiguration() *schema.Resource { +// @SDKResource("aws_backup_vault_lock_configuration", name="Vault Lock Configuration") +func resourceVaultLockConfiguration() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceVaultLockConfigurationCreate, ReadWithoutTimeout: resourceVaultLockConfigurationRead, DeleteWithoutTimeout: resourceVaultLockConfigurationDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, From 5e60aca2df0bcf11510d3dd5a4996c6cbb454bfa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:25:04 -0400 Subject: [PATCH 21/27] r/aws_backup_vault_notifications: Reduce visibility. --- internal/service/backup/exports_test.go | 2 + .../service/backup/service_package_gen.go | 3 +- internal/service/backup/sweep.go | 2 +- .../service/backup/vault_notifications.go | 104 ++++++++++++------ .../backup/vault_notifications_test.go | 53 +++++---- 5 files changed, 99 insertions(+), 65 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 330976a15ac..55937aefe52 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -13,6 +13,7 @@ var ( ResourceReportPlan = resourceReportPlan ResourceSelection = resourceSelection ResourceVaultLockConfiguration = resourceVaultLockConfiguration + ResourceVaultNotifications = resourceVaultNotifications FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName @@ -23,4 +24,5 @@ var ( FindReportPlanByName = findReportPlanByName FindSelectionByTwoPartKey = findSelectionByTwoPartKey FindVaultAccessPolicyByName = findVaultAccessPolicyByName + FindVaultNotificationsByName = findVaultNotificationsByName ) diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index ca34ad6d211..dcaf2797cbc 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -114,8 +114,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Vault Lock Configuration", }, { - Factory: ResourceVaultNotifications, + Factory: resourceVaultNotifications, TypeName: "aws_backup_vault_notifications", + Name: "Vault Notifications", }, { Factory: ResourceVaultPolicy, diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index 1a9f94d5f79..15f3bd8e906 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -193,7 +193,7 @@ func sweepVaultNotifications(region string) error { } for _, v := range page.BackupVaultList { - r := ResourceVaultNotifications() + r := resourceVaultNotifications() d := r.Data(nil) d.SetId(aws.ToString(v.BackupVaultName)) diff --git a/internal/service/backup/vault_notifications.go b/internal/service/backup/vault_notifications.go index 8e4912ecad1..81ffe460860 100644 --- a/internal/service/backup/vault_notifications.go +++ b/internal/service/backup/vault_notifications.go @@ -11,7 +11,9 @@ import ( "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/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -19,21 +21,36 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_backup_vault_notifications") -func ResourceVaultNotifications() *schema.Resource { +// @SDKResource("aws_backup_vault_notifications", name="Vault Notifications") +func resourceVaultNotifications() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceVaultNotificationsCreate, ReadWithoutTimeout: resourceVaultNotificationsRead, DeleteWithoutTimeout: resourceVaultNotificationsDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ + "backup_vault_arn": { + Type: schema.TypeString, + Computed: true, + }, + "backup_vault_events": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.BackupVaultEvent](), + }, + }, "backup_vault_name": { Type: schema.TypeString, Required: true, @@ -46,19 +63,6 @@ func ResourceVaultNotifications() *schema.Resource { ForceNew: true, ValidateFunc: verify.ValidARN, }, - "backup_vault_events": { - Type: schema.TypeSet, - Required: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: enum.Validate[awstypes.BackupVaultEvent](), - }, - }, - "backup_vault_arn": { - Type: schema.TypeString, - Computed: true, - }, }, } } @@ -67,18 +71,20 @@ func resourceVaultNotificationsCreate(ctx context.Context, d *schema.ResourceDat var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) + name := d.Get("backup_vault_name").(string) input := &backup.PutBackupVaultNotificationsInput{ - BackupVaultName: aws.String(d.Get("backup_vault_name").(string)), - SNSTopicArn: aws.String(d.Get(names.AttrSNSTopicARN).(string)), BackupVaultEvents: flex.ExpandStringyValueSet[awstypes.BackupVaultEvent](d.Get("backup_vault_events").(*schema.Set)), + BackupVaultName: aws.String(name), + SNSTopicArn: aws.String(d.Get(names.AttrSNSTopicARN).(string)), } _, err := conn.PutBackupVaultNotifications(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Backup Vault Notifications (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "creating Backup Vault Notifications (%s): %s", name, err) } - d.SetId(d.Get("backup_vault_name").(string)) + d.SetId(name) return append(diags, resourceVaultNotificationsRead(ctx, d, meta)...) } @@ -87,13 +93,10 @@ func resourceVaultNotificationsRead(ctx context.Context, d *schema.ResourceData, var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - input := &backup.GetBackupVaultNotificationsInput{ - BackupVaultName: aws.String(d.Id()), - } + output, err := findVaultNotificationsByName(ctx, conn, d.Id()) - resp, err := conn.GetBackupVaultNotifications(ctx, input) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - log.Printf("[WARN] Backup Vault Notifcations %s not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Backup Vault Notifications (%s) not found, removing from state", d.Id()) d.SetId("") return diags } @@ -101,12 +104,11 @@ func resourceVaultNotificationsRead(ctx context.Context, d *schema.ResourceData, if err != nil { return sdkdiag.AppendErrorf(diags, "reading Backup Vault Notifications (%s): %s", d.Id(), err) } - d.Set("backup_vault_name", resp.BackupVaultName) - d.Set(names.AttrSNSTopicARN, resp.SNSTopicArn) - d.Set("backup_vault_arn", resp.BackupVaultArn) - if err := d.Set("backup_vault_events", flex.FlattenStringyValueSet(resp.BackupVaultEvents)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting backup_vault_events: %s", err) - } + + d.Set("backup_vault_arn", output.BackupVaultArn) + d.Set("backup_vault_events", output.BackupVaultEvents) + d.Set("backup_vault_name", output.BackupVaultName) + d.Set(names.AttrSNSTopicARN, output.SNSTopicArn) return diags } @@ -115,17 +117,47 @@ func resourceVaultNotificationsDelete(ctx context.Context, d *schema.ResourceDat var diags diag.Diagnostics conn := meta.(*conns.AWSClient).BackupClient(ctx) - input := &backup.DeleteBackupVaultNotificationsInput{ + log.Printf("[DEBUG] Deleting Backup Vault Notifications: %s", d.Id()) + _, err := conn.DeleteBackupVaultNotifications(ctx, &backup.DeleteBackupVaultNotificationsInput{ BackupVaultName: aws.String(d.Id()), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return diags } - _, err := conn.DeleteBackupVaultNotifications(ctx, input) if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return diags - } return sdkdiag.AppendErrorf(diags, "deleting Backup Vault Notifications (%s): %s", d.Id(), err) } return diags } + +func findVaultNotificationsByName(ctx context.Context, conn *backup.Client, name string) (*backup.GetBackupVaultNotificationsOutput, error) { + input := &backup.GetBackupVaultNotificationsInput{ + BackupVaultName: aws.String(name), + } + + return findVaultNotifications(ctx, conn, input) +} + +func findVaultNotifications(ctx context.Context, conn *backup.Client, input *backup.GetBackupVaultNotificationsInput) (*backup.GetBackupVaultNotificationsOutput, error) { + output, err := conn.GetBackupVaultNotifications(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/backup/vault_notifications_test.go b/internal/service/backup/vault_notifications_test.go index 867ac62a829..b51e51f7d4b 100644 --- a/internal/service/backup/vault_notifications_test.go +++ b/internal/service/backup/vault_notifications_test.go @@ -8,7 +8,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/backup" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -16,10 +15,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfbackup "github.com/hashicorp/terraform-provider-aws/internal/service/backup" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccBackupVaultNotification_basic(t *testing.T) { +func TestAccBackupVaultNotifications_basic(t *testing.T) { ctx := acctest.Context(t) var vault backup.GetBackupVaultNotificationsOutput @@ -29,12 +29,12 @@ func TestAccBackupVaultNotification_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckVaultNotificationDestroy(ctx), + CheckDestroy: testAccCheckVaultNotificationsDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccVaultNotificationsConfig_notification(rName), + Config: testAccVaultNotificationsConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVaultNotificationExists(ctx, resourceName, &vault), + testAccCheckVaultNotificationsExists(ctx, resourceName, &vault), resource.TestCheckResourceAttr(resourceName, "backup_vault_events.#", acctest.Ct2), ), }, @@ -47,7 +47,7 @@ func TestAccBackupVaultNotification_basic(t *testing.T) { }) } -func TestAccBackupVaultNotification_disappears(t *testing.T) { +func TestAccBackupVaultNotifications_disappears(t *testing.T) { ctx := acctest.Context(t) var vault backup.GetBackupVaultNotificationsOutput @@ -57,12 +57,12 @@ func TestAccBackupVaultNotification_disappears(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.BackupServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckVaultNotificationDestroy(ctx), + CheckDestroy: testAccCheckVaultNotificationsDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccVaultNotificationsConfig_notification(rName), + Config: testAccVaultNotificationsConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVaultNotificationExists(ctx, resourceName, &vault), + testAccCheckVaultNotificationsExists(ctx, resourceName, &vault), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfbackup.ResourceVaultNotifications(), resourceName), ), ExpectNonEmptyPlan: true, @@ -71,7 +71,7 @@ func TestAccBackupVaultNotification_disappears(t *testing.T) { }) } -func testAccCheckVaultNotificationDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckVaultNotificationsDestroy(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 { @@ -79,46 +79,45 @@ func testAccCheckVaultNotificationDestroy(ctx context.Context) resource.TestChec continue } - input := &backup.GetBackupVaultNotificationsInput{ - BackupVaultName: aws.String(rs.Primary.ID), - } + _, err := tfbackup.FindVaultNotificationsByName(ctx, conn, rs.Primary.ID) - resp, err := conn.GetBackupVaultNotifications(ctx, input) + if tfresource.NotFound(err) { + continue + } - if err == nil { - if aws.ToString(resp.BackupVaultName) == rs.Primary.ID { - return fmt.Errorf("Backup Plan notifications '%s' was not deleted properly", rs.Primary.ID) - } + if err != nil { + return err } + + return fmt.Errorf("Backup Vault Notifications %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckVaultNotificationExists(ctx context.Context, name string, vault *backup.GetBackupVaultNotificationsOutput) resource.TestCheckFunc { +func testAccCheckVaultNotificationsExists(ctx context.Context, n string, v *backup.GetBackupVaultNotificationsOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) - params := &backup.GetBackupVaultNotificationsInput{ - BackupVaultName: aws.String(rs.Primary.ID), - } - resp, err := conn.GetBackupVaultNotifications(ctx, params) + + output, err := tfbackup.FindVaultNotificationsByName(ctx, conn, rs.Primary.ID) + if err != nil { return err } - *vault = *resp + *v = *output return nil } } -func testAccVaultNotificationsConfig_notification(rName string) string { +func testAccVaultNotificationsConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_backup_vault" "test" { name = %[1]q From 8ccd5b8dfd4fff8e2fab0178954f060900c88e90 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:33:13 -0400 Subject: [PATCH 22/27] r/aws_backup_vault_policy: Reduce visibility. --- internal/service/backup/exports_test.go | 1 + internal/service/backup/find.go | 26 ------------ .../service/backup/service_package_gen.go | 3 +- internal/service/backup/sweep.go | 2 +- internal/service/backup/vault_policy.go | 42 +++++++++++++++---- internal/service/backup/vault_policy_test.go | 12 ++---- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 55937aefe52..3e7b3d9f417 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -14,6 +14,7 @@ var ( ResourceSelection = resourceSelection ResourceVaultLockConfiguration = resourceVaultLockConfiguration ResourceVaultNotifications = resourceVaultNotifications + ResourceVaultPolicy = resourceVaultPolicy FindBackupVaultByName = findBackupVaultByName FindFrameworkByName = findFrameworkByName diff --git a/internal/service/backup/find.go b/internal/service/backup/find.go index e7e383230f0..f56e7bc6eca 100644 --- a/internal/service/backup/find.go +++ b/internal/service/backup/find.go @@ -9,7 +9,6 @@ import ( "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/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -65,28 +64,3 @@ func findRecoveryPointByTwoPartKey(ctx context.Context, conn *backup.Client, bac return output, nil } - -func findVaultAccessPolicyByName(ctx context.Context, conn *backup.Client, name string) (*backup.GetBackupVaultAccessPolicyOutput, error) { - input := &backup.GetBackupVaultAccessPolicyInput{ - BackupVaultName: aws.String(name), - } - - output, err := conn.GetBackupVaultAccessPolicy(ctx, input) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output, nil -} diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index dcaf2797cbc..24334ab93ec 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -119,8 +119,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Vault Notifications", }, { - Factory: ResourceVaultPolicy, + Factory: resourceVaultPolicy, TypeName: "aws_backup_vault_policy", + Name: "Vault Policy", }, } } diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index 15f3bd8e906..ffcb4f1919d 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -232,7 +232,7 @@ func sweepVaultPolicies(region string) error { } for _, v := range page.BackupVaultList { - r := ResourceVaultPolicy() + r := resourceVaultPolicy() d := r.Data(nil) d.SetId(aws.ToString(v.BackupVaultName)) diff --git a/internal/service/backup/vault_policy.go b/internal/service/backup/vault_policy.go index 6cdcfeb80cb..5232f2cfa95 100644 --- a/internal/service/backup/vault_policy.go +++ b/internal/service/backup/vault_policy.go @@ -12,6 +12,7 @@ import ( awstypes "github.com/aws/aws-sdk-go-v2/service/backup/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -23,13 +24,14 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_backup_vault_policy") -func ResourceVaultPolicy() *schema.Resource { +// @SDKResource("aws_backup_vault_policy", name="Vault Policy") +func resourceVaultPolicy() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceVaultPolicyPut, - UpdateWithoutTimeout: resourceVaultPolicyPut, ReadWithoutTimeout: resourceVaultPolicyRead, + UpdateWithoutTimeout: resourceVaultPolicyPut, DeleteWithoutTimeout: resourceVaultPolicyDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -64,9 +66,8 @@ func resourceVaultPolicyPut(ctx context.Context, d *schema.ResourceData, meta in conn := meta.(*conns.AWSClient).BackupClient(ctx) policy, err := structure.NormalizeJsonString(d.Get(names.AttrPolicy).(string)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policy, err) + return sdkdiag.AppendFromErr(diags, err) } name := d.Get("backup_vault_name").(string) @@ -111,15 +112,13 @@ func resourceVaultPolicyRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("backup_vault_name", output.BackupVaultName) policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get(names.AttrPolicy).(string), aws.ToString(output.Policy)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "while setting policy (%s), encountered: %s", policyToSet, err) + return sdkdiag.AppendFromErr(diags, err) } policyToSet, err = structure.NormalizeJsonString(policyToSet) - if err != nil { - return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policyToSet, err) + return sdkdiag.AppendFromErr(diags, err) } d.Set(names.AttrPolicy, policyToSet) @@ -146,3 +145,28 @@ func resourceVaultPolicyDelete(ctx context.Context, d *schema.ResourceData, meta return diags } + +func findVaultAccessPolicyByName(ctx context.Context, conn *backup.Client, name string) (*backup.GetBackupVaultAccessPolicyOutput, error) { + input := &backup.GetBackupVaultAccessPolicyInput{ + BackupVaultName: aws.String(name), + } + + output, err := conn.GetBackupVaultAccessPolicy(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/backup/vault_policy_test.go b/internal/service/backup/vault_policy_test.go index 586b8b32a8e..2d99b37f06c 100644 --- a/internal/service/backup/vault_policy_test.go +++ b/internal/service/backup/vault_policy_test.go @@ -178,15 +178,11 @@ func testAccCheckVaultPolicyDestroy(ctx context.Context) resource.TestCheckFunc } } -func testAccCheckVaultPolicyExists(ctx context.Context, name string, vault *backup.GetBackupVaultAccessPolicyOutput) resource.TestCheckFunc { +func testAccCheckVaultPolicyExists(ctx context.Context, n string, v *backup.GetBackupVaultAccessPolicyOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No Backup Vault Policy ID is set") + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).BackupClient(ctx) @@ -197,7 +193,7 @@ func testAccCheckVaultPolicyExists(ctx context.Context, name string, vault *back return err } - *vault = *output + *v = *output return nil } From 9c2ce1fc913a0fbe4c411a29a7a5c1f99797eb16 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:43:02 -0400 Subject: [PATCH 23/27] r/aws_backup_vault: Reduce visibility. --- internal/service/backup/exports_test.go | 1 + internal/service/backup/find.go | 66 ----------------- .../service/backup/service_package_gen.go | 2 +- internal/service/backup/status.go | 44 ----------- internal/service/backup/sweep.go | 2 +- internal/service/backup/vault.go | 73 ++++++++++++++++++- internal/service/backup/vault_test.go | 67 ++++++++++++++++- internal/service/backup/wait.go | 55 -------------- 8 files changed, 138 insertions(+), 172 deletions(-) delete mode 100644 internal/service/backup/find.go delete mode 100644 internal/service/backup/status.go delete mode 100644 internal/service/backup/wait.go diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index 3e7b3d9f417..e35e416d577 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -12,6 +12,7 @@ var ( ResourceRegionSettings = resourceRegionSettings ResourceReportPlan = resourceReportPlan ResourceSelection = resourceSelection + ResourceVault = resourceVault ResourceVaultLockConfiguration = resourceVaultLockConfiguration ResourceVaultNotifications = resourceVaultNotifications ResourceVaultPolicy = resourceVaultPolicy diff --git a/internal/service/backup/find.go b/internal/service/backup/find.go deleted file mode 100644 index f56e7bc6eca..00000000000 --- a/internal/service/backup/find.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package backup - -import ( - "context" - - "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-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/errs" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func findJobByID(ctx context.Context, conn *backup.Client, id string) (*backup.DescribeBackupJobOutput, error) { - input := &backup.DescribeBackupJobInput{ - BackupJobId: aws.String(id), - } - - output, err := conn.DescribeBackupJob(ctx, input) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output, nil -} - -func findRecoveryPointByTwoPartKey(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string) (*backup.DescribeRecoveryPointOutput, error) { - input := &backup.DescribeRecoveryPointInput{ - BackupVaultName: aws.String(backupVaultName), - RecoveryPointArn: aws.String(recoveryPointARN), - } - - output, err := conn.DescribeRecoveryPoint(ctx, input) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output, nil -} diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 24334ab93ec..1349d22d934 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -101,7 +101,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Selection", }, { - Factory: ResourceVault, + Factory: resourceVault, TypeName: "aws_backup_vault", Name: "Vault", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/service/backup/status.go b/internal/service/backup/status.go deleted file mode 100644 index e322d0f2708..00000000000 --- a/internal/service/backup/status.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package backup - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/service/backup" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func statusJobState(ctx context.Context, conn *backup.Client, id string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := findJobByID(ctx, conn, id) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return output, string(output.State), nil - } -} - -func statusRecoveryPoint(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := findRecoveryPointByTwoPartKey(ctx, conn, backupVaultName, recoveryPointARN) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return output, string(output.Status), nil - } -} diff --git a/internal/service/backup/sweep.go b/internal/service/backup/sweep.go index ffcb4f1919d..12ea9a415b6 100644 --- a/internal/service/backup/sweep.go +++ b/internal/service/backup/sweep.go @@ -279,7 +279,7 @@ func sweepVaults(region string) error { continue } - r := ResourceVault() + r := resourceVault() d := r.Data(nil) d.SetId(name) d.Set(names.AttrForceDestroy, true) diff --git a/internal/service/backup/vault.go b/internal/service/backup/vault.go index 73f6ee6f145..cfedf62301a 100644 --- a/internal/service/backup/vault.go +++ b/internal/service/backup/vault.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" @@ -30,7 +31,7 @@ import ( // @SDKResource("aws_backup_vault", name="Vault") // @Tags(identifierAttribute="arn") -func ResourceVault() *schema.Resource { +func resourceVault() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceVaultCreate, ReadWithoutTimeout: resourceVaultRead, @@ -151,7 +152,6 @@ func resourceVaultDelete(ctx context.Context, d *schema.ResourceData, meta inter var errs []error pages := backup.NewListRecoveryPointsByBackupVaultPaginator(conn, input) - for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -173,12 +173,12 @@ func resourceVaultDelete(ctx context.Context, d *schema.ResourceData, meta inter }) if err != nil { - errs = append(errs, fmt.Errorf("deleting recovery point (%s): %w", recoveryPointARN, err)) + errs = append(errs, fmt.Errorf("deleting Backup Vault recovery point (%s): %w", recoveryPointARN, err)) continue } if _, err := waitRecoveryPointDeleted(ctx, conn, d.Id(), recoveryPointARN, d.Timeout(schema.TimeoutDelete)); err != nil { - errs = append(errs, fmt.Errorf("waiting for recovery point (%s) delete: %w", recoveryPointARN, err)) + errs = append(errs, fmt.Errorf("waiting for Backup Vault recovery point (%s) delete: %w", recoveryPointARN, err)) continue } } @@ -243,3 +243,68 @@ func findVault(ctx context.Context, conn *backup.Client, input *backup.DescribeB return output, nil } + +func findRecoveryPointByTwoPartKey(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string) (*backup.DescribeRecoveryPointOutput, error) { + input := &backup.DescribeRecoveryPointInput{ + BackupVaultName: aws.String(backupVaultName), + RecoveryPointArn: aws.String(recoveryPointARN), + } + + return findRecoveryPoint(ctx, conn, input) +} + +func findRecoveryPoint(ctx context.Context, conn *backup.Client, input *backup.DescribeRecoveryPointInput) (*backup.DescribeRecoveryPointOutput, error) { + output, err := conn.DescribeRecoveryPoint(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusRecoveryPoint(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findRecoveryPointByTwoPartKey(ctx, conn, backupVaultName, recoveryPointARN) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status), nil + } +} + +func waitRecoveryPointDeleted(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string, timeout time.Duration) (*backup.DescribeRecoveryPointOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.RecoveryPointStatusDeleting), + Target: []string{}, + Refresh: statusRecoveryPoint(ctx, conn, backupVaultName, recoveryPointARN), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*backup.DescribeRecoveryPointOutput); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusMessage))) + + return output, err + } + + return nil, err +} diff --git a/internal/service/backup/vault_test.go b/internal/service/backup/vault_test.go index 33d39c2d1f6..7c76d05ae9a 100644 --- a/internal/service/backup/vault_test.go +++ b/internal/service/backup/vault_test.go @@ -5,6 +5,7 @@ package backup_test import ( "context" + "errors" "fmt" "testing" "time" @@ -12,11 +13,15 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/arn" "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-sdk/v2/helper/retry" 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/enum" + "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" @@ -297,7 +302,7 @@ func testAccCheckRunDynamoDBTableBackupJob(ctx context.Context, rName string) re jobID := aws.ToString(output.BackupJobId) - _, err = tfbackup.WaitJobCompleted(ctx, conn, jobID, 10*time.Minute) + _, err = waitJobCompleted(ctx, conn, jobID, 10*time.Minute) if err != nil { return fmt.Errorf("error waiting for Backup Job (%s) complete: %w", jobID, err) @@ -323,6 +328,66 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { } } +func findJobByID(ctx context.Context, conn *backup.Client, id string) (*backup.DescribeBackupJobOutput, error) { + input := &backup.DescribeBackupJobInput{ + BackupJobId: aws.String(id), + } + + output, err := conn.DescribeBackupJob(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusJobState(ctx context.Context, conn *backup.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findJobByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.State), nil + } +} + +func waitJobCompleted(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeBackupJobOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.BackupJobStateCreated, awstypes.BackupJobStatePending, awstypes.BackupJobStateRunning, awstypes.BackupJobStateAborting), + Target: enum.Slice(awstypes.BackupJobStateCompleted), + Refresh: statusJobState(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*backup.DescribeBackupJobOutput); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusMessage))) + + return output, err + } + + return nil, err +} + func testAccVaultConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_backup_vault" "test" { diff --git a/internal/service/backup/wait.go b/internal/service/backup/wait.go deleted file mode 100644 index ae1f47cd216..00000000000 --- a/internal/service/backup/wait.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package backup - -import ( - "context" - "errors" - "time" - - "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-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/enum" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func WaitJobCompleted(ctx context.Context, conn *backup.Client, id string, timeout time.Duration) (*backup.DescribeBackupJobOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(awstypes.BackupJobStateCreated, awstypes.BackupJobStatePending, awstypes.BackupJobStateRunning, awstypes.BackupJobStateAborting), - Target: enum.Slice(awstypes.BackupJobStateCompleted), - Refresh: statusJobState(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*backup.DescribeBackupJobOutput); ok { - tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusMessage))) - - return output, err - } - - return nil, err -} - -func waitRecoveryPointDeleted(ctx context.Context, conn *backup.Client, backupVaultName, recoveryPointARN string, timeout time.Duration) (*backup.DescribeRecoveryPointOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(awstypes.RecoveryPointStatusDeleting), - Target: []string{}, - Refresh: statusRecoveryPoint(ctx, conn, backupVaultName, recoveryPointARN), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*backup.DescribeRecoveryPointOutput); ok { - tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusMessage))) - - return output, err - } - - return nil, err -} From f472cce8584c6d562555fd170de11dd1a22a798b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:46:30 -0400 Subject: [PATCH 24/27] d/aws_backup_vault: Reduce visibility. --- .../service/backup/service_package_gen.go | 3 +- internal/service/backup/vault_data_source.go | 37 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/internal/service/backup/service_package_gen.go b/internal/service/backup/service_package_gen.go index 1349d22d934..148ded0ae7e 100644 --- a/internal/service/backup/service_package_gen.go +++ b/internal/service/backup/service_package_gen.go @@ -53,8 +53,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Selection", }, { - Factory: DataSourceVault, + Factory: dataSourceVault, TypeName: "aws_backup_vault", + Name: "Vault", }, } } diff --git a/internal/service/backup/vault_data_source.go b/internal/service/backup/vault_data_source.go index fca00796f5c..5bfe0e5cc58 100644 --- a/internal/service/backup/vault_data_source.go +++ b/internal/service/backup/vault_data_source.go @@ -6,8 +6,6 @@ package backup import ( "context" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/backup" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -16,16 +14,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_backup_vault") -func DataSourceVault() *schema.Resource { +// @SDKDataSource("aws_backup_vault", name="Vault") +func dataSourceVault() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceVaultRead, Schema: map[string]*schema.Schema{ - names.AttrName: { - Type: schema.TypeString, - Required: true, - }, names.AttrARN: { Type: schema.TypeString, Computed: true, @@ -34,6 +28,10 @@ func DataSourceVault() *schema.Resource { Type: schema.TypeString, Computed: true, }, + names.AttrName: { + Type: schema.TypeString, + Required: true, + }, "recovery_points": { Type: schema.TypeInt, Computed: true, @@ -49,25 +47,24 @@ func dataSourceVaultRead(ctx context.Context, d *schema.ResourceData, meta inter ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig name := d.Get(names.AttrName).(string) - input := &backup.DescribeBackupVaultInput{ - BackupVaultName: aws.String(name), - } + output, err := findBackupVaultByName(ctx, conn, name) - resp, err := conn.DescribeBackupVault(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "getting Backup Vault: %s", err) + return sdkdiag.AppendErrorf(diags, "reading Backup Vault (%s): %s", name, err) } - d.SetId(aws.ToString(resp.BackupVaultName)) - d.Set(names.AttrARN, resp.BackupVaultArn) - d.Set(names.AttrKMSKeyARN, resp.EncryptionKeyArn) - d.Set(names.AttrName, resp.BackupVaultName) - d.Set("recovery_points", resp.NumberOfRecoveryPoints) + d.SetId(name) + d.Set(names.AttrARN, output.BackupVaultArn) + d.Set(names.AttrKMSKeyARN, output.EncryptionKeyArn) + d.Set(names.AttrName, output.BackupVaultName) + d.Set("recovery_points", output.NumberOfRecoveryPoints) + + tags, err := listTags(ctx, conn, d.Get(names.AttrARN).(string)) - tags, err := listTags(ctx, conn, aws.ToString(resp.BackupVaultArn)) if err != nil { - return sdkdiag.AppendErrorf(diags, "listing tags for Backup Vault (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "listing tags for Backup Vault (%s): %s", d.Id(), err) } + if err := d.Set(names.AttrTags, tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) } From 8e2a75aeabb84663cfb7d09988204b75f7126429 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:50:07 -0400 Subject: [PATCH 25/27] Fix semgrep 'ci.semgrep.framework.flex-type-from-framework'. --- internal/service/backup/logically_air_gapped_vault.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go index d852e3278e4..8afc26c639d 100644 --- a/internal/service/backup/logically_air_gapped_vault.go +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -182,7 +182,7 @@ func (r *logicallyAirGappedVaultResource) Delete(ctx context.Context, request re conn := r.Meta().BackupClient(ctx) _, err := conn.DeleteBackupVault(ctx, &backup.DeleteBackupVaultInput{ - BackupVaultName: aws.String(data.ID.ValueString()), + BackupVaultName: fwflex.StringFromFramework(ctx, data.ID), }) if errs.IsA[*awstypes.ResourceNotFoundException](err) || tfawserr.ErrCodeEquals(err, errCodeAccessDeniedException) { From a4b4e28b318f166359ea94531c34baf8e784243c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:53:37 -0400 Subject: [PATCH 26/27] Fix semgrep 'ci.backup-in-func-name' and 'ci.backup-in-var-name'. --- internal/service/backup/exports_test.go | 4 ++-- internal/service/backup/logically_air_gapped_vault.go | 2 +- internal/service/backup/vault.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/backup/exports_test.go b/internal/service/backup/exports_test.go index e35e416d577..48ad42d68b3 100644 --- a/internal/service/backup/exports_test.go +++ b/internal/service/backup/exports_test.go @@ -17,10 +17,10 @@ var ( ResourceVaultNotifications = resourceVaultNotifications ResourceVaultPolicy = resourceVaultPolicy - FindBackupVaultByName = findBackupVaultByName + FindBackupVaultByName = findBackupVaultByName // nosemgrep:ci.backup-in-var-name FindFrameworkByName = findFrameworkByName FindGlobalSettings = findGlobalSettings - FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName + FindLogicallyAirGappedBackupVaultByName = findLogicallyAirGappedBackupVaultByName // nosemgrep:ci.backup-in-var-name FindPlanByID = findPlanByID FindRegionSettings = findRegionSettings FindReportPlanByName = findReportPlanByName diff --git a/internal/service/backup/logically_air_gapped_vault.go b/internal/service/backup/logically_air_gapped_vault.go index 8afc26c639d..56ceaef5c99 100644 --- a/internal/service/backup/logically_air_gapped_vault.go +++ b/internal/service/backup/logically_air_gapped_vault.go @@ -211,7 +211,7 @@ type logicallyAirGappedVaultResourceModel struct { TagsAll tftags.Map `tfsdk:"tags_all"` } -func findLogicallyAirGappedBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { +func findLogicallyAirGappedBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { // nosemgrep:ci.backup-in-func-name return findVaultByNameAndType(ctx, conn, name, awstypes.VaultTypeLogicallyAirGappedBackupVault) } diff --git a/internal/service/backup/vault.go b/internal/service/backup/vault.go index cfedf62301a..4f7e6529482 100644 --- a/internal/service/backup/vault.go +++ b/internal/service/backup/vault.go @@ -201,7 +201,7 @@ func resourceVaultDelete(ctx context.Context, d *schema.ResourceData, meta inter return diags } -func findBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { +func findBackupVaultByName(ctx context.Context, conn *backup.Client, name string) (*backup.DescribeBackupVaultOutput, error) { // nosemgrep:ci.backup-in-func-name return findVaultByNameAndType(ctx, conn, name, awstypes.VaultTypeBackupVault) } From 18410cee90227b0e20392667375b426e6681ce8d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 2 Oct 2024 16:57:21 -0400 Subject: [PATCH 27/27] Cosmetics. --- internal/service/backup/consts.go | 7 ------- internal/service/backup/report_plan.go | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/service/backup/consts.go b/internal/service/backup/consts.go index 1bb9fa60964..570b57fa18c 100644 --- a/internal/service/backup/consts.go +++ b/internal/service/backup/consts.go @@ -9,13 +9,6 @@ const ( propagationTimeout = 2 * time.Minute ) -const ( - reportPlanDeploymentStatusCompleted = "COMPLETED" - reportPlanDeploymentStatusCreateInProgress = "CREATE_IN_PROGRESS" - reportPlanDeploymentStatusDeleteInProgress = "DELETE_IN_PROGRESS" - reportPlanDeploymentStatusUpdateInProgress = "UPDATE_IN_PROGRESS" -) - const ( reportDeliveryChannelFormatCSV = "CSV" reportDeliveryChannelFormatJSON = "JSON" diff --git a/internal/service/backup/report_plan.go b/internal/service/backup/report_plan.go index 4f54108ac9c..de27fad2f6d 100644 --- a/internal/service/backup/report_plan.go +++ b/internal/service/backup/report_plan.go @@ -415,6 +415,13 @@ func statusReportPlan(ctx context.Context, conn *backup.Client, name string) ret } } +const ( + reportPlanDeploymentStatusCompleted = "COMPLETED" + reportPlanDeploymentStatusCreateInProgress = "CREATE_IN_PROGRESS" + reportPlanDeploymentStatusDeleteInProgress = "DELETE_IN_PROGRESS" + reportPlanDeploymentStatusUpdateInProgress = "UPDATE_IN_PROGRESS" +) + func waitReportPlanCreated(ctx context.Context, conn *backup.Client, name string, timeout time.Duration) (*awstypes.ReportPlan, error) { stateConf := &retry.StateChangeConf{ Pending: []string{reportPlanDeploymentStatusCreateInProgress},