From c1f9e0cef8c7967a02efb084bacc178b13f85031 Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 04:21:26 -0500 Subject: [PATCH 01/15] feat: rds cluster snapshot copy resource --- internal/service/rds/cluster_snapshot_copy.go | 288 +++++++++++++++ .../service/rds/cluster_snapshot_copy_test.go | 331 ++++++++++++++++++ internal/service/rds/exports_test.go | 1 + 3 files changed, 620 insertions(+) create mode 100644 internal/service/rds/cluster_snapshot_copy.go create mode 100644 internal/service/rds/cluster_snapshot_copy_test.go diff --git a/internal/service/rds/cluster_snapshot_copy.go b/internal/service/rds/cluster_snapshot_copy.go new file mode 100644 index 00000000000..f85812ad2c4 --- /dev/null +++ b/internal/service/rds/cluster_snapshot_copy.go @@ -0,0 +1,288 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rds + +import ( + "context" + "log" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_db_cluster_snapshot_copy", name="DB Cluster Snapshot Copy") +// @Tags(identifierAttribute="db_cluster_snapshot_arn") +// @Testing(tagsTest=false) +func resourceClusterSnapshotCopy() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceClusterSnapshotCopyCreate, + ReadWithoutTimeout: resourceClusterSnapshotCopyRead, + UpdateWithoutTimeout: resourceClusterSnapshotCopyUpdate, + DeleteWithoutTimeout: resourceClusterSnapshotCopyDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + names.AttrAllocatedStorage: { + Type: schema.TypeInt, + Computed: true, + }, + names.AttrAvailabilityZones: { + Type: schema.TypeList, + Computed: true, + }, + "copy_tags": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "db_cluster_snapshot_arn": { + Type: schema.TypeString, + Computed: true, + }, + "destination_region": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + names.AttrStorageEncrypted: { + Type: schema.TypeBool, + Computed: true, + }, + names.AttrEngine: { + Type: schema.TypeString, + Computed: true, + }, + names.AttrEngineVersion: { + Type: schema.TypeString, + Computed: true, + }, + names.AttrKMSKeyID: { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "license_model": { + Type: schema.TypeString, + Computed: true, + }, + "presigned_url": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "shared_accounts": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "source_db_cluster_snapshot_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "source_region": { + Type: schema.TypeString, + Computed: true, + }, + "snapshot_type": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrStorageType: { + Type: schema.TypeString, + Computed: true, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + "target_db_cluster_snapshot_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 255), + validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z][\w-]+`), "must contain only alphanumeric, and hyphen (-) characters"), + ), + }, + names.AttrVPCID: { + Type: schema.TypeString, + Computed: true, + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSClient(ctx) + + targetDBClusterSnapshotID := d.Get("target_db_cluster_snapshot_identifier").(string) + input := &rds.CopyDBClusterSnapshotInput{ + SourceDBClusterSnapshotIdentifier: aws.String(d.Get("source_db_cluster_snapshot_identifier").(string)), + Tags: getTagsIn(ctx), + TargetDBClusterSnapshotIdentifier: aws.String(targetDBClusterSnapshotID), + } + + if v, ok := d.GetOk("copy_tags"); ok { + input.CopyTags = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk(names.AttrKMSKeyID); ok { + input.KmsKeyId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("presigned_url"); ok { + input.PreSignedUrl = aws.String(v.(string)) + } else if v, ok := d.GetOk("destination_region"); ok { + output, err := rds.NewPresignClient(conn, func(o *rds.PresignOptions) { + o.ClientOptions = append(o.ClientOptions, func(o *rds.Options) { + o.Region = v.(string) + }) + }).PresignCopyDBClusterSnapshot(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "presigning RDS DB Snapshot Copy (%s) request: %s", targetDBClusterSnapshotID, err) + } + + input.PreSignedUrl = aws.String(output.URL) + } + + output, err := conn.CopyDBClusterSnapshot(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating RDS DB Snapshot Copy (%s): %s", targetDBClusterSnapshotID, err) + } + + d.SetId(aws.ToString(output.DBClusterSnapshot.DBClusterSnapshotIdentifier)) + + if _, err := waitDBClusterSnapshotCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Snapshot Copy (%s) create: %s", d.Id(), err) + } + + if v, ok := d.GetOk("shared_accounts"); ok && v.(*schema.Set).Len() > 0 { + input := &rds.ModifyDBClusterSnapshotAttributeInput{ + AttributeName: aws.String("restore"), + DBClusterSnapshotIdentifier: aws.String(d.Id()), + ValuesToAdd: flex.ExpandStringValueSet(v.(*schema.Set)), + } + + _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "modifying RDS DB Snapshot (%s) attribute: %s", d.Id(), err) + } + } + + return append(diags, resourceSnapshotCopyRead(ctx, d, meta)...) +} + +func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSClient(ctx) + + snapshot, err := findDBClusterSnapshotByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] RDS DB Cluster Snapshot (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading RDS DB Snapshot Copy (%s): %s", d.Id(), err) + } + + arn := aws.ToString(snapshot.DBClusterSnapshotArn) + d.Set(names.AttrAllocatedStorage, snapshot.AllocatedStorage) + d.Set(names.AttrAvailabilityZones, snapshot.AvailabilityZones) + d.Set("db_cluster_snapshot_arn", arn) + d.Set(names.AttrStorageEncrypted, snapshot.StorageEncrypted) + d.Set(names.AttrEngine, snapshot.Engine) + d.Set(names.AttrEngineVersion, snapshot.EngineVersion) + d.Set(names.AttrKMSKeyID, snapshot.KmsKeyId) + d.Set("license_model", snapshot.LicenseModel) + d.Set("snapshot_type", snapshot.SnapshotType) + d.Set("source_db_cluster_snapshot_identifier", d.Get("source_db_cluster_snapshot_identifier")) + d.Set(names.AttrStorageType, snapshot.StorageType) + d.Set("target_db_cluster_snapshot_identifier", snapshot.DBClusterSnapshotIdentifier) + d.Set(names.AttrVPCID, snapshot.VpcId) + + attribute, err := findDBSnapshotAttributeByTwoPartKey(ctx, conn, d.Id(), dbSnapshotAttributeNameRestore) + switch { + case err == nil: + d.Set("shared_accounts", attribute.AttributeValues) + case tfresource.NotFound(err): + default: + return sdkdiag.AppendErrorf(diags, "reading RDS DB Snapshot (%s) attribute: %s", d.Id(), err) + } + + return diags +} + +func resourceClusterSnapshotCopyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSClient(ctx) + + if d.HasChange("shared_accounts") { + o, n := d.GetChange("shared_accounts") + os, ns := o.(*schema.Set), n.(*schema.Set) + add, del := ns.Difference(os), os.Difference(ns) + input := &rds.ModifyDBClusterSnapshotAttributeInput{ + AttributeName: aws.String("restore"), + DBClusterSnapshotIdentifier: aws.String(d.Id()), + ValuesToAdd: flex.ExpandStringValueSet(add), + ValuesToRemove: flex.ExpandStringValueSet(del), + } + + _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "modifying RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) + } + } + + return append(diags, resourceSnapshotCopyRead(ctx, d, meta)...) +} + +func resourceClusterSnapshotCopyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).RDSClient(ctx) + + log.Printf("[DEBUG] Deleting RDS DB Cluster Snapshot Copy: %s", d.Id()) + _, err := conn.DeleteDBClusterSnapshot(ctx, &rds.DeleteDBClusterSnapshotInput{ + DBClusterSnapshotIdentifier: aws.String(d.Id()), + }) + + if errs.IsA[*types.DBClusterSnapshotNotFoundFault](err) { + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) + } + + return diags +} diff --git a/internal/service/rds/cluster_snapshot_copy_test.go b/internal/service/rds/cluster_snapshot_copy_test.go new file mode 100644 index 00000000000..e1f8a350180 --- /dev/null +++ b/internal/service/rds/cluster_snapshot_copy_test.go @@ -0,0 +1,331 @@ +package rds_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/rds/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" + tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccRDSClusterSnapshotCopy_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v types.DBClusterSnapshot + resourceName := "aws_db_cluster_snapshot_copy.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterSnapshotCopyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotCopyConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "shared_accounts.#", "0"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRDSClusterSnapshotCopy_share(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v types.DBClusterSnapshot + resourceName := "aws_db_cluster_snapshot_copy.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterSnapshotCopyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotCopyConfig_share(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "shared_accounts.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "shared_accounts.*", "all"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccClusterSnapshotCopyConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "shared_accounts.#", "0"), + ), + }, + }, + }) +} + +func TestAccRDSClusterSnapshotCopy_tags(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v types.DBClusterSnapshot + resourceName := "aws_db_cluster_snapshot_copy.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterSnapshotCopyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotCopyConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccClusterSnapshotCopyConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "2"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccClusterSnapshotCopyConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +func TestAccRDSClusterSnapshotCopy_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v types.DBClusterSnapshot + resourceName := "aws_db_cluster_snapshot_copy.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterSnapshotCopyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotCopyConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceClusterSnapshotCopy(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccRDSClusterSnapshotCopy_destinationRegion(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v types.DBClusterSnapshot + resourceName := "aws_db_cluster_snapshot_copy.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckClusterSnapshotCopyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotCopyConfig_destinationRegion(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, names.AttrAllocatedStorage), + resource.TestCheckResourceAttrSet(resourceName, names.AttrAvailabilityZones), + resource.TestCheckResourceAttr(resourceName, "destination_region", acctest.AlternateRegion()), + resource.TestCheckResourceAttr(resourceName, names.AttrStorageEncrypted, acctest.CtFalse), + resource.TestCheckResourceAttrSet(resourceName, names.AttrEngine), + resource.TestCheckResourceAttrSet(resourceName, names.AttrEngineVersion), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + resource.TestCheckResourceAttrSet(resourceName, "license_model"), + resource.TestCheckResourceAttrSet(resourceName, "snapshot_type"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrStorageType), + resource.TestCheckResourceAttrSet(resourceName, names.AttrVPCID), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"destination_region"}, + }, + }, + }) +} + +func testAccCheckClusterSnapshotCopyDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_db_cluster_snapshot_copy" { + continue + } + + _, err := tfrds.FindDBClusterSnapshotByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("RDS DB Cluster Snapshot Copy %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckClusterSnapshotCopyExists(ctx context.Context, n string, v *types.DBClusterSnapshot) 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).RDSClient(ctx) + + output, err := tfrds.FindDBClusterSnapshotByID(ctx, conn, rs.Primary.ID) + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccClusterSnapshotCopyConfig_base(rName string) string { + return fmt.Sprintf(` +data "aws_rds_engine_version" "default" { + engine = "mysql" +} + +resource "aws_rds_cluster" "test" { + cluster_identifier = %[1]q + database_name = "test" + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + master_username = "tfacctest" + master_password = "avoid-plaintext-passwords" + skip_final_snapshot = true +} + +resource "aws_db_cluser_snapshot" "test" { + db_cluster_identifier = aws_db_cluster.test.identifier + db_cluster_snapshot_identifier = "%[1]s-source" +}`, rName) +} + +func testAccClusterSnapshotCopyConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` +resource "aws_db_cluster_snapshot_copy" "test" { + source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_snapshot_identifier = "%[1]s-target" +}`, rName)) +} + +func testAccClusterSnapshotCopyConfig_tags1(rName, tagKey, tagValue string) string { + return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` +resource "aws_db_cluster_snapshot_copy" "test" { + source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_snapshot_identifier = "%[1]s-target" + + tags = { + %[2]q = %[3]q + } +}`, rName, tagKey, tagValue)) +} + +func testAccClusterSnapshotCopyConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` +resource "aws_db_cluster_snapshot_copy" "test" { + source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_snapshot_identifier = "%[1]s-target" + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +}`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccClusterSnapshotCopyConfig_share(rName string) string { + return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` +resource "aws_db_snapshot_copy" "test" { + source_db_snapshot_identifier = aws_db_snapshot.test.db_snapshot_arn + target_db_snapshot_identifier = "%[1]s-target" + shared_accounts = ["all"] +} +`, rName)) +} + +func testAccClusterSnapshotCopyConfig_destinationRegion(rName string) string { + return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` +resource "aws_db_cluster_snapshot_copy" "test" { + source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_snapshot_identifier = "%[1]s-target" + destination_region = %[2]q +}`, rName, acctest.AlternateRegion())) +} diff --git a/internal/service/rds/exports_test.go b/internal/service/rds/exports_test.go index 044d60e69f4..f5d11952ec7 100644 --- a/internal/service/rds/exports_test.go +++ b/internal/service/rds/exports_test.go @@ -13,6 +13,7 @@ var ( ResourceClusterParameterGroup = resourceClusterParameterGroup ResourceClusterRoleAssociation = resourceClusterRoleAssociation ResourceClusterSnapshot = resourceClusterSnapshot + ResourceClusterSnapshotCopy = resourceClusterSnapshotCopy ResourceCustomDBEngineVersion = resourceCustomDBEngineVersion ResourceEventSubscription = resourceEventSubscription ResourceGlobalCluster = resourceGlobalCluster From f3ea3aa333e04026794a67b20a50dfff052cd1c7 Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 04:21:53 -0500 Subject: [PATCH 02/15] chore: generate new service package for rds cluster snapshot copy --- internal/service/rds/service_package_gen.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/service/rds/service_package_gen.go b/internal/service/rds/service_package_gen.go index 1f7d02aef0f..2164f6e0f32 100644 --- a/internal/service/rds/service_package_gen.go +++ b/internal/service/rds/service_package_gen.go @@ -131,6 +131,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "db_cluster_snapshot_arn", }, }, + { + Factory: resourceClusterSnapshotCopy, + TypeName: "aws_db_cluster_snapshot_copy", + Name: "DB Cluster Snapshot Copy", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "db_cluster_snapshot_arn", + }, + }, { Factory: resourceEventSubscription, TypeName: "aws_db_event_subscription", From 889825b37cddbc7a4420f2a204e92add76609631 Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 04:22:24 -0500 Subject: [PATCH 03/15] docs: create documentation for aws_rds_cluster_snapshot_copy --- .../r/db_cluster_snapshot_copy.html.markdown | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 website/docs/r/db_cluster_snapshot_copy.html.markdown diff --git a/website/docs/r/db_cluster_snapshot_copy.html.markdown b/website/docs/r/db_cluster_snapshot_copy.html.markdown new file mode 100644 index 00000000000..47d3a2f07e9 --- /dev/null +++ b/website/docs/r/db_cluster_snapshot_copy.html.markdown @@ -0,0 +1,96 @@ +--- +subcategory: "RDS (Relational Database)" +layout: "aws" +page_title: "AWS: aws_db_cluster_snapshot_copy" +description: |- + Manages an RDS database cluster snapshot copy. +--- + +# Resource: aws_db_cluster_snapshot_copy + +Manages an RDS database cluster snapshot copy. For managing RDS database instance snapshot copies, see the [`aws_db_cluster_snapshot_copy` resource](/docs/providers/aws/r/db_snapshot_copy.html). + +## Example Usage + +```terraform +data "aws_rds_engine_version" "default" { + engine = "mysql" +} + +resource "aws_rds_cluster" "test" { + cluster_identifier = "aurora-cluster-demo" + database_name = "test" + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + master_username = "tfacctest" + master_password = "avoid-plaintext-passwords" + skip_final_snapshot = true +} + +resource "aws_db_cluser_snapshot" "backup" { + db_cluster_identifier = aws_db_cluster.default.identifier + db_cluster_snapshot_identifier = "cluster-snapshot-source" +} + + +resource "aws_db_cluster_snapshot_copy" "copy" { + source_db_snapshot_identifier = aws_db_cluster_snapshot.backup.db_cluster_snapshot_arn + target_db_snapshot_identifier = "copy-of-cluster-snapshot-source" +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `copy_tags` - (Optional) Whether to copy existing tags. Defaults to `false`. +* `destination_region` - (Optional) The Destination region to place snapshot copy. +* `kms_key_id` - (Optional) KMS key ID. +* `presigned_url` - (Optional) he URL that contains a Signature Version 4 signed request. +* `shared_accounts` - (Optional) List of AWS Account IDs to share the snapshot with. Use `all` to make the snapshot public. +* `source_db_cluster_snapshot_identifier` - (Required) Snapshot identifier of the source snapshot. +* `target_db_cluster_snapshot_identifier` - (Required) The Identifier for the snapshot. +* `tags` - (Optional) Key-value map of resource tags. 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` - Snapshot Identifier. +* `allocated_storage` - Specifies the allocated storage size in gigabytes (GB). +* `availability_zones` - Specifies the the Availability Zones the DB cluster was located in at the time of the DB snapshot. +* `db_cluster_snapshot_arn` - The Amazon Resource Name (ARN) for the DB snapshot. +* `storage_encrypted` - Specifies whether the DB snapshot is encrypted. +* `engine` - Specifies the name of the database engine. +* `engine_version` - Specifies the version of the database engine. +* `kms_key_id` - The ARN for the KMS encryption key. +* `license_model` - License model information for the restored DB instance. +* `shared_accounts` - (Optional) List of AWS Account IDs to share the snapshot with. Use `all` to make the snapshot public. +* `source_db_cluster_snapshot_identifier` - The DB snapshot Arn that the DB snapshot was copied from. It only has value in case of cross customer or cross region copy. +* `source_region` - The region that the DB snapshot was created in or copied from. +* `storage_type` - Specifies the storage type associated with DB snapshot. +* `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). +* `vpc_id` - Provides the VPC ID associated with the DB snapshot. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +- `create` - (Default `20m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import `aws_db_cluster_snapshot_copy` using the snapshot identifier. For example: + +```terraform +import { + to = aws_db_cluster_snapshot_copy.example + id = "my-snapshot" +} +``` + +Using `terraform import`, import `aws_db_cluster_snapshot_copy` using the snapshot identifier. For example: + +```console +% terraform import aws_db_cluster_snapshot_copy.example my-snapshot +``` From a63c335cfd42dbfefd3e0e5cbeb1e49680c87036 Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 14:16:13 -0500 Subject: [PATCH 04/15] chore: changelog entry for 40398 --- .changelog/40398.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/40398.txt diff --git a/.changelog/40398.txt b/.changelog/40398.txt new file mode 100644 index 00000000000..7e79cab285b --- /dev/null +++ b/.changelog/40398.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_rds_cluster_snapshot-copy +``` From b9fe058813ab7b09c1521f476c2a717f0bb42e2a Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 14:16:51 -0500 Subject: [PATCH 05/15] fix: example for cluster snapshot copy --- website/docs/r/db_cluster_snapshot_copy.html.markdown | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/website/docs/r/db_cluster_snapshot_copy.html.markdown b/website/docs/r/db_cluster_snapshot_copy.html.markdown index 47d3a2f07e9..16efd53fd85 100644 --- a/website/docs/r/db_cluster_snapshot_copy.html.markdown +++ b/website/docs/r/db_cluster_snapshot_copy.html.markdown @@ -13,15 +13,10 @@ Manages an RDS database cluster snapshot copy. For managing RDS database instanc ## Example Usage ```terraform -data "aws_rds_engine_version" "default" { - engine = "mysql" -} - resource "aws_rds_cluster" "test" { cluster_identifier = "aurora-cluster-demo" database_name = "test" - engine = data.aws_rds_engine_version.default.engine - engine_version = data.aws_rds_engine_version.default.version + engine = "aurora-mysql" master_username = "tfacctest" master_password = "avoid-plaintext-passwords" skip_final_snapshot = true From 24a5a9d6aa7e8b6db5d2d45db482abe10da5d99b Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 14:19:02 -0500 Subject: [PATCH 06/15] fix: add copying status to the pending status for cluster snapshots --- internal/service/rds/cluster_snapshot.go | 2 +- internal/service/rds/consts.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/rds/cluster_snapshot.go b/internal/service/rds/cluster_snapshot.go index f6c25230ad1..728a5b2ee6c 100644 --- a/internal/service/rds/cluster_snapshot.go +++ b/internal/service/rds/cluster_snapshot.go @@ -341,7 +341,7 @@ func statusDBClusterSnapshot(ctx context.Context, conn *rds.Client, id string) r func waitDBClusterSnapshotCreated(ctx context.Context, conn *rds.Client, id string, timeout time.Duration) (*types.DBClusterSnapshot, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{clusterSnapshotStatusCreating}, + Pending: []string{clusterSnapshotStatusCreating, clusterSnapshotStatusCopying}, Target: []string{clusterSnapshotStatusAvailable}, Refresh: statusDBClusterSnapshot(ctx, conn, id), Timeout: timeout, diff --git a/internal/service/rds/consts.go b/internal/service/rds/consts.go index d34aabeca54..9fdd7c5e0ea 100644 --- a/internal/service/rds/consts.go +++ b/internal/service/rds/consts.go @@ -39,6 +39,7 @@ const ( const ( clusterSnapshotStatusAvailable = "available" clusterSnapshotStatusCreating = "creating" + clusterSnapshotStatusCopying = "copying" ) const ( From b1d86bbcaed3aa85e2474b19194abe1010e513a4 Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 14:38:48 -0500 Subject: [PATCH 07/15] fix: extra schema and copy_tags usage --- internal/service/rds/cluster_snapshot_copy.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/service/rds/cluster_snapshot_copy.go b/internal/service/rds/cluster_snapshot_copy.go index f85812ad2c4..890a436a564 100644 --- a/internal/service/rds/cluster_snapshot_copy.go +++ b/internal/service/rds/cluster_snapshot_copy.go @@ -48,10 +48,6 @@ func resourceClusterSnapshotCopy() *schema.Resource { Type: schema.TypeInt, Computed: true, }, - names.AttrAvailabilityZones: { - Type: schema.TypeList, - Computed: true, - }, "copy_tags": { Type: schema.TypeBool, Optional: true, @@ -144,6 +140,7 @@ func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceDa SourceDBClusterSnapshotIdentifier: aws.String(d.Get("source_db_cluster_snapshot_identifier").(string)), Tags: getTagsIn(ctx), TargetDBClusterSnapshotIdentifier: aws.String(targetDBClusterSnapshotID), + CopyTags: aws.Bool(d.Get("copy_tags").(bool)), } if v, ok := d.GetOk("copy_tags"); ok { @@ -217,7 +214,6 @@ func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData arn := aws.ToString(snapshot.DBClusterSnapshotArn) d.Set(names.AttrAllocatedStorage, snapshot.AllocatedStorage) - d.Set(names.AttrAvailabilityZones, snapshot.AvailabilityZones) d.Set("db_cluster_snapshot_arn", arn) d.Set(names.AttrStorageEncrypted, snapshot.StorageEncrypted) d.Set(names.AttrEngine, snapshot.Engine) @@ -225,7 +221,7 @@ func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData d.Set(names.AttrKMSKeyID, snapshot.KmsKeyId) d.Set("license_model", snapshot.LicenseModel) d.Set("snapshot_type", snapshot.SnapshotType) - d.Set("source_db_cluster_snapshot_identifier", d.Get("source_db_cluster_snapshot_identifier")) + d.Set("source_db_cluster_snapshot_identifier", snapshot.SourceDBClusterSnapshotArn) d.Set(names.AttrStorageType, snapshot.StorageType) d.Set("target_db_cluster_snapshot_identifier", snapshot.DBClusterSnapshotIdentifier) d.Set(names.AttrVPCID, snapshot.VpcId) From 660d858c0649f8d4c7d9d070ca4fb046b4fe245c Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 14:39:20 -0500 Subject: [PATCH 08/15] fix: error messages and cluster snapshot lookups --- internal/service/rds/cluster_snapshot_copy.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/service/rds/cluster_snapshot_copy.go b/internal/service/rds/cluster_snapshot_copy.go index 890a436a564..1b51ca8f408 100644 --- a/internal/service/rds/cluster_snapshot_copy.go +++ b/internal/service/rds/cluster_snapshot_copy.go @@ -161,7 +161,7 @@ func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceDa }).PresignCopyDBClusterSnapshot(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "presigning RDS DB Snapshot Copy (%s) request: %s", targetDBClusterSnapshotID, err) + return sdkdiag.AppendErrorf(diags, "presigning RDS DB Cluster Snapshot Copy (%s) request: %s", targetDBClusterSnapshotID, err) } input.PreSignedUrl = aws.String(output.URL) @@ -170,7 +170,7 @@ func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceDa output, err := conn.CopyDBClusterSnapshot(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating RDS DB Snapshot Copy (%s): %s", targetDBClusterSnapshotID, err) + return sdkdiag.AppendErrorf(diags, "creating RDS DB Cluster Snapshot Copy (%s): %s", targetDBClusterSnapshotID, err) } d.SetId(aws.ToString(output.DBClusterSnapshot.DBClusterSnapshotIdentifier)) @@ -193,7 +193,7 @@ func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceDa } } - return append(diags, resourceSnapshotCopyRead(ctx, d, meta)...) + return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) } func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -209,7 +209,7 @@ func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData } if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS DB Snapshot Copy (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) } arn := aws.ToString(snapshot.DBClusterSnapshotArn) @@ -226,13 +226,13 @@ func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData d.Set("target_db_cluster_snapshot_identifier", snapshot.DBClusterSnapshotIdentifier) d.Set(names.AttrVPCID, snapshot.VpcId) - attribute, err := findDBSnapshotAttributeByTwoPartKey(ctx, conn, d.Id(), dbSnapshotAttributeNameRestore) + attribute, err := findDBClusterSnapshotAttributeByTwoPartKey(ctx, conn, d.Id(), dbSnapshotAttributeNameRestore) switch { case err == nil: d.Set("shared_accounts", attribute.AttributeValues) case tfresource.NotFound(err): default: - return sdkdiag.AppendErrorf(diags, "reading RDS DB Snapshot (%s) attribute: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) } return diags @@ -260,7 +260,7 @@ func resourceClusterSnapshotCopyUpdate(ctx context.Context, d *schema.ResourceDa } } - return append(diags, resourceSnapshotCopyRead(ctx, d, meta)...) + return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) } func resourceClusterSnapshotCopyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { From 6e3d164e9fcff0823e71d6bdcd2dfa76d1bb326d Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 14:39:48 -0500 Subject: [PATCH 09/15] chore: cluster snapshot kms key test --- .../service/rds/cluster_snapshot_copy_test.go | 96 +++++++++++++++---- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/internal/service/rds/cluster_snapshot_copy_test.go b/internal/service/rds/cluster_snapshot_copy_test.go index e1f8a350180..5afb5c6449c 100644 --- a/internal/service/rds/cluster_snapshot_copy_test.go +++ b/internal/service/rds/cluster_snapshot_copy_test.go @@ -188,15 +188,14 @@ func TestAccRDSClusterSnapshotCopy_destinationRegion(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, names.AttrAllocatedStorage), - resource.TestCheckResourceAttrSet(resourceName, names.AttrAvailabilityZones), resource.TestCheckResourceAttr(resourceName, "destination_region", acctest.AlternateRegion()), resource.TestCheckResourceAttr(resourceName, names.AttrStorageEncrypted, acctest.CtFalse), resource.TestCheckResourceAttrSet(resourceName, names.AttrEngine), resource.TestCheckResourceAttrSet(resourceName, names.AttrEngineVersion), resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + resource.TestCheckResourceAttr(resourceName, "storage_encrypted", acctest.CtFalse), resource.TestCheckResourceAttrSet(resourceName, "license_model"), resource.TestCheckResourceAttrSet(resourceName, "snapshot_type"), - resource.TestCheckResourceAttrSet(resourceName, names.AttrStorageType), resource.TestCheckResourceAttrSet(resourceName, names.AttrVPCID), ), }, @@ -210,6 +209,35 @@ func TestAccRDSClusterSnapshotCopy_destinationRegion(t *testing.T) { }) } +func TestAccRDSClusterSnapshotCopy_kmsKeyID(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v types.DBClusterSnapshot + resourceName := "aws_db_cluster_snapshot_copy.test" + keyResourceName := "aws_kms_key.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterSnapshotCopyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotCopyConfig_kms(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, names.AttrKMSKeyID, keyResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "storage_encrypted", acctest.CtTrue), + ), + }, + }, + }) +} + func testAccCheckClusterSnapshotCopyDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) @@ -258,39 +286,52 @@ func testAccCheckClusterSnapshotCopyExists(ctx context.Context, n string, v *typ func testAccClusterSnapshotCopyConfig_base(rName string) string { return fmt.Sprintf(` -data "aws_rds_engine_version" "default" { - engine = "mysql" -} - resource "aws_rds_cluster" "test" { cluster_identifier = %[1]q database_name = "test" - engine = data.aws_rds_engine_version.default.engine - engine_version = data.aws_rds_engine_version.default.version + engine = "aurora-mysql" master_username = "tfacctest" master_password = "avoid-plaintext-passwords" skip_final_snapshot = true } -resource "aws_db_cluser_snapshot" "test" { - db_cluster_identifier = aws_db_cluster.test.identifier +resource "aws_db_cluster_snapshot" "test" { + db_cluster_identifier = aws_rds_cluster.test.cluster_identifier db_cluster_snapshot_identifier = "%[1]s-source" }`, rName) } +func testAccClusterSnapshotCopyConfig_encryptedBase(rName string) string { + return fmt.Sprintf(` +resource "aws_rds_cluster" "encrypted" { + cluster_identifier = %[1]q + database_name = "test" + engine = "aurora-mysql" + master_username = "tfacctest" + master_password = "avoid-plaintext-passwords" + skip_final_snapshot = true + storage_encrypted = true +} + +resource "aws_db_cluster_snapshot" "encrypted" { + db_cluster_identifier = aws_rds_cluster.encrypted.cluster_identifier + db_cluster_snapshot_identifier = "%[1]s-source" +}`, rName) +} + func testAccClusterSnapshotCopyConfig_basic(rName string) string { return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` resource "aws_db_cluster_snapshot_copy" "test" { - source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn - target_db_snapshot_identifier = "%[1]s-target" + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" }`, rName)) } func testAccClusterSnapshotCopyConfig_tags1(rName, tagKey, tagValue string) string { return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` resource "aws_db_cluster_snapshot_copy" "test" { - source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn - target_db_snapshot_identifier = "%[1]s-target" + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" tags = { %[2]q = %[3]q @@ -301,8 +342,8 @@ resource "aws_db_cluster_snapshot_copy" "test" { func testAccClusterSnapshotCopyConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` resource "aws_db_cluster_snapshot_copy" "test" { - source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn - target_db_snapshot_identifier = "%[1]s-target" + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" tags = { %[2]q = %[3]q @@ -313,9 +354,9 @@ resource "aws_db_cluster_snapshot_copy" "test" { func testAccClusterSnapshotCopyConfig_share(rName string) string { return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` -resource "aws_db_snapshot_copy" "test" { - source_db_snapshot_identifier = aws_db_snapshot.test.db_snapshot_arn - target_db_snapshot_identifier = "%[1]s-target" +resource "aws_db_cluster_snapshot_copy" "test" { + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" shared_accounts = ["all"] } `, rName)) @@ -324,8 +365,21 @@ resource "aws_db_snapshot_copy" "test" { func testAccClusterSnapshotCopyConfig_destinationRegion(rName string) string { return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` resource "aws_db_cluster_snapshot_copy" "test" { - source_db_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn - target_db_snapshot_identifier = "%[1]s-target" + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" destination_region = %[2]q }`, rName, acctest.AlternateRegion())) } + +func testAccClusterSnapshotCopyConfig_kms(rName string) string { + return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_encryptedBase(rName), fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = "test" +} + +resource "aws_db_cluster_snapshot_copy" "test" { + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.encrypted.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" + kms_key_id = aws_kms_key.test.arn +}`, rName)) +} From c5076e81d9b63c686d8bb55f8ecaca5d4a7cf18b Mon Sep 17 00:00:00 2001 From: rfhold Date: Tue, 3 Dec 2024 15:20:24 -0500 Subject: [PATCH 10/15] fix: status check errors --- .../service/rds/cluster_snapshot_copy_test.go | 35 ++++++++++--------- .../r/db_cluster_snapshot_copy.html.markdown | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/internal/service/rds/cluster_snapshot_copy_test.go b/internal/service/rds/cluster_snapshot_copy_test.go index 5afb5c6449c..641d483771f 100644 --- a/internal/service/rds/cluster_snapshot_copy_test.go +++ b/internal/service/rds/cluster_snapshot_copy_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package rds_test import ( @@ -296,7 +299,7 @@ resource "aws_rds_cluster" "test" { } resource "aws_db_cluster_snapshot" "test" { - db_cluster_identifier = aws_rds_cluster.test.cluster_identifier + db_cluster_identifier = aws_rds_cluster.test.cluster_identifier db_cluster_snapshot_identifier = "%[1]s-source" }`, rName) } @@ -304,18 +307,18 @@ resource "aws_db_cluster_snapshot" "test" { func testAccClusterSnapshotCopyConfig_encryptedBase(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster" "encrypted" { - cluster_identifier = %[1]q - database_name = "test" - engine = "aurora-mysql" - master_username = "tfacctest" - master_password = "avoid-plaintext-passwords" - skip_final_snapshot = true - storage_encrypted = true + cluster_identifier = %[1]q + database_name = "test" + engine = "aurora-mysql" + master_username = "tfacctest" + master_password = "avoid-plaintext-passwords" + skip_final_snapshot = true + storage_encrypted = true } resource "aws_db_cluster_snapshot" "encrypted" { - db_cluster_identifier = aws_rds_cluster.encrypted.cluster_identifier - db_cluster_snapshot_identifier = "%[1]s-source" + db_cluster_identifier = aws_rds_cluster.encrypted.cluster_identifier + db_cluster_snapshot_identifier = "%[1]s-source" }`, rName) } @@ -357,7 +360,7 @@ func testAccClusterSnapshotCopyConfig_share(rName string) string { resource "aws_db_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" - shared_accounts = ["all"] + shared_accounts = ["all"] } `, rName)) } @@ -367,19 +370,19 @@ func testAccClusterSnapshotCopyConfig_destinationRegion(rName string) string { resource "aws_db_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" - destination_region = %[2]q + destination_region = %[2]q }`, rName, acctest.AlternateRegion())) } func testAccClusterSnapshotCopyConfig_kms(rName string) string { return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_encryptedBase(rName), fmt.Sprintf(` resource "aws_kms_key" "test" { - description = "test" + description = "test" } resource "aws_db_cluster_snapshot_copy" "test" { - source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.encrypted.db_cluster_snapshot_arn - target_db_cluster_snapshot_identifier = "%[1]s-target" - kms_key_id = aws_kms_key.test.arn + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.encrypted.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "%[1]s-target" + kms_key_id = aws_kms_key.test.arn }`, rName)) } diff --git a/website/docs/r/db_cluster_snapshot_copy.html.markdown b/website/docs/r/db_cluster_snapshot_copy.html.markdown index 16efd53fd85..34f84a7a3a3 100644 --- a/website/docs/r/db_cluster_snapshot_copy.html.markdown +++ b/website/docs/r/db_cluster_snapshot_copy.html.markdown @@ -23,7 +23,7 @@ resource "aws_rds_cluster" "test" { } resource "aws_db_cluser_snapshot" "backup" { - db_cluster_identifier = aws_db_cluster.default.identifier + db_cluster_identifier = aws_db_cluster.default.identifier db_cluster_snapshot_identifier = "cluster-snapshot-source" } From f54f3689fa2d3f69359088761e97d703a989b79d Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 12 Dec 2024 15:38:59 -0500 Subject: [PATCH 11/15] r/aws_rds_cluster_snapshot_copy: migrate to Terraform Plugin Framework --- internal/service/rds/cluster_snapshot_copy.go | 485 +++++++++++------- .../service/rds/cluster_snapshot_copy_sdk.go | 284 ++++++++++ .../service/rds/cluster_snapshot_copy_test.go | 60 ++- internal/service/rds/exports_test.go | 2 +- internal/service/rds/service_package_gen.go | 15 +- ...> rds_cluster_snapshot_copy.html.markdown} | 22 +- 6 files changed, 648 insertions(+), 220 deletions(-) create mode 100644 internal/service/rds/cluster_snapshot_copy_sdk.go rename website/docs/r/{db_cluster_snapshot_copy.html.markdown => rds_cluster_snapshot_copy.html.markdown} (84%) diff --git a/internal/service/rds/cluster_snapshot_copy.go b/internal/service/rds/cluster_snapshot_copy.go index 1b51ca8f408..f9044db1ed7 100644 --- a/internal/service/rds/cluster_snapshot_copy.go +++ b/internal/service/rds/cluster_snapshot_copy.go @@ -5,280 +5,415 @@ package rds import ( "context" - "log" + "errors" + "fmt" "time" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/rds" - "github.com/aws/aws-sdk-go-v2/service/rds/types" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/hashicorp/terraform-provider-aws/internal/conns" + awstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "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/boolplanmodifier" + "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" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" - "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/flex" + intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" + "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/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_db_cluster_snapshot_copy", name="DB Cluster Snapshot Copy") +// @FrameworkResource("aws_rds_cluster_snapshot_copy", name="Cluster Snapshot Copy") // @Tags(identifierAttribute="db_cluster_snapshot_arn") // @Testing(tagsTest=false) -func resourceClusterSnapshotCopy() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceClusterSnapshotCopyCreate, - ReadWithoutTimeout: resourceClusterSnapshotCopyRead, - UpdateWithoutTimeout: resourceClusterSnapshotCopyUpdate, - DeleteWithoutTimeout: resourceClusterSnapshotCopyDelete, - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, +func newResourceClusterSnapshotCopy(context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceClusterSnapshotCopy{} - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(20 * time.Minute), - }, + r.SetDefaultCreateTimeout(20 * time.Minute) + + return r, nil +} + +const ( + ResNameClusterSnapshotCopy = "Cluster Snapshot Copy" +) + +type resourceClusterSnapshotCopy struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} - Schema: map[string]*schema.Schema{ - names.AttrAllocatedStorage: { - Type: schema.TypeInt, +func (r *resourceClusterSnapshotCopy) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_rds_cluster_snapshot_copy" +} + +func (r *resourceClusterSnapshotCopy) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrAllocatedStorage: schema.Int64Attribute{ Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, }, - "copy_tags": { - Type: schema.TypeBool, + "copy_tags": schema.BoolAttribute{ Optional: true, - ForceNew: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, }, - "db_cluster_snapshot_arn": { - Type: schema.TypeString, + "db_cluster_snapshot_arn": schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "destination_region": { - Type: schema.TypeString, + "destination_region": schema.StringAttribute{ Optional: true, - ForceNew: true, - }, - names.AttrStorageEncrypted: { - Type: schema.TypeBool, - Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, - names.AttrEngine: { - Type: schema.TypeString, + names.AttrEngine: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - names.AttrEngineVersion: { - Type: schema.TypeString, + names.AttrEngineVersion: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - names.AttrKMSKeyID: { - Type: schema.TypeString, + names.AttrID: framework.IDAttribute(), + names.AttrKMSKeyID: schema.StringAttribute{ Optional: true, - ForceNew: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, - "license_model": { - Type: schema.TypeString, + "license_model": schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "presigned_url": { - Type: schema.TypeString, + "presigned_url": schema.StringAttribute{ Optional: true, - ForceNew: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, - "shared_accounts": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "source_db_cluster_snapshot_identifier": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "shared_accounts": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, }, - "source_region": { - Type: schema.TypeString, + "snapshot_type": schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "snapshot_type": { - Type: schema.TypeString, + "source_db_cluster_snapshot_identifier": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrStorageEncrypted: schema.BoolAttribute{ Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, }, - names.AttrStorageType: { - Type: schema.TypeString, + names.AttrStorageType: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - names.AttrTags: tftags.TagsSchema(), - names.AttrTagsAll: tftags.TagsSchemaComputed(), - "target_db_cluster_snapshot_identifier": { - Type: schema.TypeString, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "target_db_cluster_snapshot_identifier": schema.StringAttribute{ Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 255), - validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z][\w-]+`), "must contain only alphanumeric, and hyphen (-) characters"), - ), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 255), + stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9A-Za-z][\w-]+`), "must contain only alphanumeric, and hyphen (-) characters"), + }, }, - names.AttrVPCID: { - Type: schema.TypeString, + names.AttrVPCID: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, }, - - CustomizeDiff: verify.SetTagsDiff, + Blocks: map[string]schema.Block{ + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + }), + }, } } -func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSClient(ctx) +func (r *resourceClusterSnapshotCopy) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resourceClusterSnapshotCopyData + conn := r.Meta().RDSClient(ctx) - targetDBClusterSnapshotID := d.Get("target_db_cluster_snapshot_identifier").(string) - input := &rds.CopyDBClusterSnapshotInput{ - SourceDBClusterSnapshotIdentifier: aws.String(d.Get("source_db_cluster_snapshot_identifier").(string)), - Tags: getTagsIn(ctx), - TargetDBClusterSnapshotIdentifier: aws.String(targetDBClusterSnapshotID), - CopyTags: aws.Bool(d.Get("copy_tags").(bool)), + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return } - if v, ok := d.GetOk("copy_tags"); ok { - input.CopyTags = aws.Bool(v.(bool)) + in := &rds.CopyDBClusterSnapshotInput{} + resp.Diagnostics.Append(flex.Expand(ctx, data, in)...) + if resp.Diagnostics.HasError() { + return } + in.Tags = getTagsIn(ctx) - if v, ok := d.GetOk(names.AttrKMSKeyID); ok { - input.KmsKeyId = aws.String(v.(string)) - } - - if v, ok := d.GetOk("presigned_url"); ok { - input.PreSignedUrl = aws.String(v.(string)) - } else if v, ok := d.GetOk("destination_region"); ok { + if !data.DestinationRegion.IsNull() && data.PresignedURL.IsNull() { output, err := rds.NewPresignClient(conn, func(o *rds.PresignOptions) { o.ClientOptions = append(o.ClientOptions, func(o *rds.Options) { - o.Region = v.(string) + o.Region = data.DestinationRegion.ValueString() }) - }).PresignCopyDBClusterSnapshot(ctx, input) + }).PresignCopyDBClusterSnapshot(ctx, in) if err != nil { - return sdkdiag.AppendErrorf(diags, "presigning RDS DB Cluster Snapshot Copy (%s) request: %s", targetDBClusterSnapshotID, err) + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionCreating, ResNameClusterSnapshotCopy, data.TargetDBClusterSnapshotIdentifier.String(), err), + err.Error(), + ) + return } - input.PreSignedUrl = aws.String(output.URL) + in.PreSignedUrl = aws.String(output.URL) } - output, err := conn.CopyDBClusterSnapshot(ctx, input) - + out, err := conn.CopyDBClusterSnapshot(ctx, in) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating RDS DB Cluster Snapshot Copy (%s): %s", targetDBClusterSnapshotID, err) + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionCreating, ResNameClusterSnapshotCopy, data.TargetDBClusterSnapshotIdentifier.String(), err), + err.Error(), + ) + return + } + if out == nil || out.DBClusterSnapshot == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionCreating, ResNameClusterSnapshotCopy, data.TargetDBClusterSnapshotIdentifier.String(), err), + errors.New("empty output").Error(), + ) + return } - d.SetId(aws.ToString(output.DBClusterSnapshot.DBClusterSnapshotIdentifier)) - - if _, err := waitDBClusterSnapshotCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Snapshot Copy (%s) create: %s", d.Id(), err) + resp.Diagnostics.Append(flex.Flatten(ctx, out.DBClusterSnapshot, &data)...) + if resp.Diagnostics.HasError() { + return + } + data.ID = types.StringValue(aws.ToString(out.DBClusterSnapshot.DBClusterSnapshotIdentifier)) + + createTimeout := r.CreateTimeout(ctx, data.Timeouts) + if _, err := waitDBClusterSnapshotCreated(ctx, conn, data.ID.ValueString(), createTimeout); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionWaitingForCreation, ResNameClusterSnapshotCopy, data.TargetDBClusterSnapshotIdentifier.String(), err), + err.Error(), + ) + return } - if v, ok := d.GetOk("shared_accounts"); ok && v.(*schema.Set).Len() > 0 { - input := &rds.ModifyDBClusterSnapshotAttributeInput{ - AttributeName: aws.String("restore"), - DBClusterSnapshotIdentifier: aws.String(d.Id()), - ValuesToAdd: flex.ExpandStringValueSet(v.(*schema.Set)), + if !data.SharedAccounts.IsNull() { + toAdd := []string{} + resp.Diagnostics.Append(data.SharedAccounts.ElementsAs(ctx, &toAdd, false)...) + if resp.Diagnostics.HasError() { + return } - _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) + input := &rds.ModifyDBClusterSnapshotAttributeInput{ + AttributeName: aws.String(dbSnapshotAttributeNameRestore), + DBClusterSnapshotIdentifier: data.TargetDBClusterSnapshotIdentifier.ValueStringPointer(), + ValuesToAdd: toAdd, + } - if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying RDS DB Snapshot (%s) attribute: %s", d.Id(), err) + if _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionCreating, ResNameClusterSnapshotCopy, data.TargetDBClusterSnapshotIdentifier.String(), err), + err.Error(), + ) + return } } - return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSClient(ctx) +func (r *resourceClusterSnapshotCopy) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resourceClusterSnapshotCopyData + conn := r.Meta().RDSClient(ctx) - snapshot, err := findDBClusterSnapshotByID(ctx, conn, d.Id()) - - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] RDS DB Cluster Snapshot (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return } + out, err := findDBClusterSnapshotByID(ctx, conn, data.ID.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionReading, ResNameClusterSnapshotCopy, data.ID.String(), err), + err.Error(), + ) + return + } + // Account for variance in naming between the AWS Create and Describe APIs + data.SourceDBClusterSnapshotIdentifier = flex.StringToFramework(ctx, out.SourceDBClusterSnapshotArn) + data.TargetDBClusterSnapshotIdentifier = flex.StringToFramework(ctx, out.DBClusterSnapshotIdentifier) + + resp.Diagnostics.Append(flex.Flatten(ctx, out, &data)...) + if resp.Diagnostics.HasError() { + return } - arn := aws.ToString(snapshot.DBClusterSnapshotArn) - d.Set(names.AttrAllocatedStorage, snapshot.AllocatedStorage) - d.Set("db_cluster_snapshot_arn", arn) - d.Set(names.AttrStorageEncrypted, snapshot.StorageEncrypted) - d.Set(names.AttrEngine, snapshot.Engine) - d.Set(names.AttrEngineVersion, snapshot.EngineVersion) - d.Set(names.AttrKMSKeyID, snapshot.KmsKeyId) - d.Set("license_model", snapshot.LicenseModel) - d.Set("snapshot_type", snapshot.SnapshotType) - d.Set("source_db_cluster_snapshot_identifier", snapshot.SourceDBClusterSnapshotArn) - d.Set(names.AttrStorageType, snapshot.StorageType) - d.Set("target_db_cluster_snapshot_identifier", snapshot.DBClusterSnapshotIdentifier) - d.Set(names.AttrVPCID, snapshot.VpcId) - - attribute, err := findDBClusterSnapshotAttributeByTwoPartKey(ctx, conn, d.Id(), dbSnapshotAttributeNameRestore) - switch { - case err == nil: - d.Set("shared_accounts", attribute.AttributeValues) - case tfresource.NotFound(err): - default: - return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) + outAttr, err := findDBClusterSnapshotAttributeByTwoPartKey(ctx, conn, data.ID.ValueString(), dbSnapshotAttributeNameRestore) + if err != nil && !tfresource.NotFound(err) { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionReading, ResNameClusterSnapshotCopy, data.ID.String(), err), + err.Error(), + ) + return + } + + if len(outAttr.AttributeValues) > 0 { + resp.Diagnostics.Append(flex.Flatten(ctx, outAttr.AttributeValues, &data.SharedAccounts)...) + if resp.Diagnostics.HasError() { + return + } } - return diags + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func resourceClusterSnapshotCopyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSClient(ctx) +func (r *resourceClusterSnapshotCopy) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var old, new resourceClusterSnapshotCopyData + conn := r.Meta().RDSClient(ctx) - if d.HasChange("shared_accounts") { - o, n := d.GetChange("shared_accounts") - os, ns := o.(*schema.Set), n.(*schema.Set) - add, del := ns.Difference(os), os.Difference(ns) - input := &rds.ModifyDBClusterSnapshotAttributeInput{ - AttributeName: aws.String("restore"), - DBClusterSnapshotIdentifier: aws.String(d.Id()), - ValuesToAdd: flex.ExpandStringValueSet(add), - ValuesToRemove: flex.ExpandStringValueSet(del), + resp.Diagnostics.Append(req.State.Get(ctx, &old)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &new)...) + if resp.Diagnostics.HasError() { + return + } + + if !old.SharedAccounts.Equal(new.SharedAccounts) { + var have, want []string + resp.Diagnostics.Append(old.SharedAccounts.ElementsAs(ctx, &have, false)...) + resp.Diagnostics.Append(new.SharedAccounts.ElementsAs(ctx, &want, false)...) + if resp.Diagnostics.HasError() { + return } - _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) + toAdd, toRemove, _ := intflex.DiffSlices(have, want, func(s1, s2 string) bool { return s1 == s2 }) - if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) + input := &rds.ModifyDBClusterSnapshotAttributeInput{ + AttributeName: aws.String(dbSnapshotAttributeNameRestore), + DBClusterSnapshotIdentifier: new.TargetDBClusterSnapshotIdentifier.ValueStringPointer(), + ValuesToAdd: toAdd, + ValuesToRemove: toRemove, + } + + if _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionUpdating, ResNameClusterSnapshotCopy, new.ID.String(), err), + err.Error(), + ) + return } } - return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) + // StorageType can be null, and UseStateForUnknown takes no action + // on null state values. Explicitly pass through the null value in + // this case to prevent "invalid result object after apply" errors + if old.StorageType.IsNull() { + new.StorageType = types.StringNull() + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &new)...) } -func resourceClusterSnapshotCopyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSClient(ctx) +func (r *resourceClusterSnapshotCopy) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resourceClusterSnapshotCopyData + conn := r.Meta().RDSClient(ctx) + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("deleting %s", ResNameClusterSnapshotCopy), map[string]interface{}{ + names.AttrID: data.ID.ValueString(), + }) - log.Printf("[DEBUG] Deleting RDS DB Cluster Snapshot Copy: %s", d.Id()) _, err := conn.DeleteDBClusterSnapshot(ctx, &rds.DeleteDBClusterSnapshotInput{ - DBClusterSnapshotIdentifier: aws.String(d.Id()), + DBClusterSnapshotIdentifier: data.ID.ValueStringPointer(), }) + if err != nil { + if errs.IsA[*awstypes.DBClusterSnapshotNotFoundFault](err) { + return + } - if errs.IsA[*types.DBClusterSnapshotNotFoundFault](err) { - return diags + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.RDS, create.ErrActionDeleting, ResNameClusterSnapshotCopy, data.ID.String(), err), + err.Error(), + ) + return } +} - if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) - } +func (r *resourceClusterSnapshotCopy) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) +} + +func (r *resourceClusterSnapshotCopy) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, req, resp) +} - return diags +type resourceClusterSnapshotCopyData struct { + AllocatedStorage types.Int64 `tfsdk:"allocated_storage"` + CopyTags types.Bool `tfsdk:"copy_tags"` + DBClusterSnapshotARN types.String `tfsdk:"db_cluster_snapshot_arn"` + DestinationRegion types.String `tfsdk:"destination_region"` + Engine types.String `tfsdk:"engine"` + EngineVersion types.String `tfsdk:"engine_version"` + ID types.String `tfsdk:"id"` + KMSKeyID types.String `tfsdk:"kms_key_id"` + LicenseModel types.String `tfsdk:"license_model"` + PresignedURL types.String `tfsdk:"presigned_url"` + SharedAccounts types.Set `tfsdk:"shared_accounts"` + SnapshotType types.String `tfsdk:"snapshot_type"` + SourceDBClusterSnapshotIdentifier types.String `tfsdk:"source_db_cluster_snapshot_identifier"` + StorageEncrypted types.Bool `tfsdk:"storage_encrypted"` + StorageType types.String `tfsdk:"storage_type"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + TargetDBClusterSnapshotIdentifier types.String `tfsdk:"target_db_cluster_snapshot_identifier"` + VPCID types.String `tfsdk:"vpc_id"` + + Timeouts timeouts.Value `tfsdk:"timeouts"` } diff --git a/internal/service/rds/cluster_snapshot_copy_sdk.go b/internal/service/rds/cluster_snapshot_copy_sdk.go new file mode 100644 index 00000000000..9b42bce993e --- /dev/null +++ b/internal/service/rds/cluster_snapshot_copy_sdk.go @@ -0,0 +1,284 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rds + +// import ( +// "context" +// "log" +// "time" +// +// "github.com/YakDriver/regexache" +// "github.com/aws/aws-sdk-go-v2/aws" +// "github.com/aws/aws-sdk-go-v2/service/rds" +// "github.com/aws/aws-sdk-go-v2/service/rds/types" +// "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +// "github.com/hashicorp/terraform-provider-aws/internal/conns" +// "github.com/hashicorp/terraform-provider-aws/internal/errs" +// "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" +// "github.com/hashicorp/terraform-provider-aws/internal/flex" +// tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +// "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +// "github.com/hashicorp/terraform-provider-aws/internal/verify" +// "github.com/hashicorp/terraform-provider-aws/names" +// ) +// +// // @SDKResource("aws_db_cluster_snapshot_copy", name="DB Cluster Snapshot Copy") +// // @Tags(identifierAttribute="db_cluster_snapshot_arn") +// // @Testing(tagsTest=false) +// // func resourceClusterSnapshotCopy() *schema.Resource { +// // return &schema.Resource{ +// // CreateWithoutTimeout: resourceClusterSnapshotCopyCreate, +// // ReadWithoutTimeout: resourceClusterSnapshotCopyRead, +// // UpdateWithoutTimeout: resourceClusterSnapshotCopyUpdate, +// // DeleteWithoutTimeout: resourceClusterSnapshotCopyDelete, +// // +// // Importer: &schema.ResourceImporter{ +// // StateContext: schema.ImportStatePassthroughContext, +// // }, +// // +// // Timeouts: &schema.ResourceTimeout{ +// // Create: schema.DefaultTimeout(20 * time.Minute), +// // }, +// // +// // Schema: map[string]*schema.Schema{ +// // names.AttrAllocatedStorage: { +// // Type: schema.TypeInt, +// // Computed: true, +// // }, +// // "copy_tags": { +// // Type: schema.TypeBool, +// // Optional: true, +// // ForceNew: true, +// // }, +// // "db_cluster_snapshot_arn": { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // "destination_region": { +// // Type: schema.TypeString, +// // Optional: true, +// // ForceNew: true, +// // }, +// // names.AttrStorageEncrypted: { +// // Type: schema.TypeBool, +// // Computed: true, +// // }, +// // names.AttrEngine: { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // names.AttrEngineVersion: { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // names.AttrKMSKeyID: { +// // Type: schema.TypeString, +// // Optional: true, +// // ForceNew: true, +// // }, +// // "license_model": { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // "presigned_url": { +// // Type: schema.TypeString, +// // Optional: true, +// // ForceNew: true, +// // }, +// // "shared_accounts": { +// // Type: schema.TypeSet, +// // Optional: true, +// // Elem: &schema.Schema{Type: schema.TypeString}, +// // }, +// // "source_db_cluster_snapshot_identifier": { +// // Type: schema.TypeString, +// // Required: true, +// // ForceNew: true, +// // }, +// // "source_region": { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // "snapshot_type": { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // names.AttrStorageType: { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // names.AttrTags: tftags.TagsSchema(), +// // names.AttrTagsAll: tftags.TagsSchemaComputed(), +// // "target_db_cluster_snapshot_identifier": { +// // Type: schema.TypeString, +// // Required: true, +// // ForceNew: true, +// // ValidateFunc: validation.All( +// // validation.StringLenBetween(1, 255), +// // validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z][\w-]+`), "must contain only alphanumeric, and hyphen (-) characters"), +// // ), +// // }, +// // names.AttrVPCID: { +// // Type: schema.TypeString, +// // Computed: true, +// // }, +// // }, +// // +// // CustomizeDiff: verify.SetTagsDiff, +// // } +// // } +// +// func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// var diags diag.Diagnostics +// conn := meta.(*conns.AWSClient).RDSClient(ctx) +// +// targetDBClusterSnapshotID := d.Get("target_db_cluster_snapshot_identifier").(string) +// input := &rds.CopyDBClusterSnapshotInput{ +// SourceDBClusterSnapshotIdentifier: aws.String(d.Get("source_db_cluster_snapshot_identifier").(string)), +// Tags: getTagsIn(ctx), +// TargetDBClusterSnapshotIdentifier: aws.String(targetDBClusterSnapshotID), +// CopyTags: aws.Bool(d.Get("copy_tags").(bool)), +// } +// +// if v, ok := d.GetOk("copy_tags"); ok { +// input.CopyTags = aws.Bool(v.(bool)) +// } +// +// if v, ok := d.GetOk(names.AttrKMSKeyID); ok { +// input.KmsKeyId = aws.String(v.(string)) +// } +// +// if v, ok := d.GetOk("presigned_url"); ok { +// input.PreSignedUrl = aws.String(v.(string)) +// } else if v, ok := d.GetOk("destination_region"); ok { +// output, err := rds.NewPresignClient(conn, func(o *rds.PresignOptions) { +// o.ClientOptions = append(o.ClientOptions, func(o *rds.Options) { +// o.Region = v.(string) +// }) +// }).PresignCopyDBClusterSnapshot(ctx, input) +// +// if err != nil { +// return sdkdiag.AppendErrorf(diags, "presigning RDS DB Cluster Snapshot Copy (%s) request: %s", targetDBClusterSnapshotID, err) +// } +// +// input.PreSignedUrl = aws.String(output.URL) +// } +// +// output, err := conn.CopyDBClusterSnapshot(ctx, input) +// +// if err != nil { +// return sdkdiag.AppendErrorf(diags, "creating RDS DB Cluster Snapshot Copy (%s): %s", targetDBClusterSnapshotID, err) +// } +// +// d.SetId(aws.ToString(output.DBClusterSnapshot.DBClusterSnapshotIdentifier)) +// +// if _, err := waitDBClusterSnapshotCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { +// return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Snapshot Copy (%s) create: %s", d.Id(), err) +// } +// +// if v, ok := d.GetOk("shared_accounts"); ok && v.(*schema.Set).Len() > 0 { +// input := &rds.ModifyDBClusterSnapshotAttributeInput{ +// AttributeName: aws.String("restore"), +// DBClusterSnapshotIdentifier: aws.String(d.Id()), +// ValuesToAdd: flex.ExpandStringValueSet(v.(*schema.Set)), +// } +// +// _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) +// +// if err != nil { +// return sdkdiag.AppendErrorf(diags, "modifying RDS DB Snapshot (%s) attribute: %s", d.Id(), err) +// } +// } +// +// return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) +// } +// +// func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// var diags diag.Diagnostics +// conn := meta.(*conns.AWSClient).RDSClient(ctx) +// +// snapshot, err := findDBClusterSnapshotByID(ctx, conn, d.Id()) +// +// if !d.IsNewResource() && tfresource.NotFound(err) { +// log.Printf("[WARN] RDS DB Cluster Snapshot (%s) not found, removing from state", d.Id()) +// d.SetId("") +// return diags +// } +// +// if err != nil { +// return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) +// } +// +// arn := aws.ToString(snapshot.DBClusterSnapshotArn) +// d.Set(names.AttrAllocatedStorage, snapshot.AllocatedStorage) +// d.Set("db_cluster_snapshot_arn", arn) +// d.Set(names.AttrStorageEncrypted, snapshot.StorageEncrypted) +// d.Set(names.AttrEngine, snapshot.Engine) +// d.Set(names.AttrEngineVersion, snapshot.EngineVersion) +// d.Set(names.AttrKMSKeyID, snapshot.KmsKeyId) +// d.Set("license_model", snapshot.LicenseModel) +// d.Set("snapshot_type", snapshot.SnapshotType) +// d.Set("source_db_cluster_snapshot_identifier", snapshot.SourceDBClusterSnapshotArn) +// d.Set(names.AttrStorageType, snapshot.StorageType) +// d.Set("target_db_cluster_snapshot_identifier", snapshot.DBClusterSnapshotIdentifier) +// d.Set(names.AttrVPCID, snapshot.VpcId) +// +// attribute, err := findDBClusterSnapshotAttributeByTwoPartKey(ctx, conn, d.Id(), dbSnapshotAttributeNameRestore) +// switch { +// case err == nil: +// d.Set("shared_accounts", attribute.AttributeValues) +// case tfresource.NotFound(err): +// default: +// return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) +// } +// +// return diags +// } +// +// func resourceClusterSnapshotCopyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// var diags diag.Diagnostics +// conn := meta.(*conns.AWSClient).RDSClient(ctx) +// +// if d.HasChange("shared_accounts") { +// o, n := d.GetChange("shared_accounts") +// os, ns := o.(*schema.Set), n.(*schema.Set) +// add, del := ns.Difference(os), os.Difference(ns) +// input := &rds.ModifyDBClusterSnapshotAttributeInput{ +// AttributeName: aws.String("restore"), +// DBClusterSnapshotIdentifier: aws.String(d.Id()), +// ValuesToAdd: flex.ExpandStringValueSet(add), +// ValuesToRemove: flex.ExpandStringValueSet(del), +// } +// +// _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) +// +// if err != nil { +// return sdkdiag.AppendErrorf(diags, "modifying RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) +// } +// } +// +// return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) +// } +// +// func resourceClusterSnapshotCopyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// var diags diag.Diagnostics +// conn := meta.(*conns.AWSClient).RDSClient(ctx) +// +// log.Printf("[DEBUG] Deleting RDS DB Cluster Snapshot Copy: %s", d.Id()) +// _, err := conn.DeleteDBClusterSnapshot(ctx, &rds.DeleteDBClusterSnapshotInput{ +// DBClusterSnapshotIdentifier: aws.String(d.Id()), +// }) +// +// if errs.IsA[*types.DBClusterSnapshotNotFoundFault](err) { +// return diags +// } +// +// if err != nil { +// return sdkdiag.AppendErrorf(diags, "deleting RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) +// } +// +// return diags +// } diff --git a/internal/service/rds/cluster_snapshot_copy_test.go b/internal/service/rds/cluster_snapshot_copy_test.go index 641d483771f..7e3a3518d99 100644 --- a/internal/service/rds/cluster_snapshot_copy_test.go +++ b/internal/service/rds/cluster_snapshot_copy_test.go @@ -26,7 +26,7 @@ func TestAccRDSClusterSnapshotCopy_basic(t *testing.T) { } var v types.DBClusterSnapshot - resourceName := "aws_db_cluster_snapshot_copy.test" + resourceName := "aws_rds_cluster_snapshot_copy.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -59,7 +59,7 @@ func TestAccRDSClusterSnapshotCopy_share(t *testing.T) { } var v types.DBClusterSnapshot - resourceName := "aws_db_cluster_snapshot_copy.test" + resourceName := "aws_rds_cluster_snapshot_copy.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -99,7 +99,7 @@ func TestAccRDSClusterSnapshotCopy_tags(t *testing.T) { } var v types.DBClusterSnapshot - resourceName := "aws_db_cluster_snapshot_copy.test" + resourceName := "aws_rds_cluster_snapshot_copy.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -149,7 +149,7 @@ func TestAccRDSClusterSnapshotCopy_disappears(t *testing.T) { } var v types.DBClusterSnapshot - resourceName := "aws_db_cluster_snapshot_copy.test" + resourceName := "aws_rds_cluster_snapshot_copy.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -162,7 +162,7 @@ func TestAccRDSClusterSnapshotCopy_disappears(t *testing.T) { Config: testAccClusterSnapshotCopyConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceClusterSnapshotCopy(), resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfrds.ResourceClusterSnapshotCopy, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -177,7 +177,7 @@ func TestAccRDSClusterSnapshotCopy_destinationRegion(t *testing.T) { } var v types.DBClusterSnapshot - resourceName := "aws_db_cluster_snapshot_copy.test" + resourceName := "aws_rds_cluster_snapshot_copy.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -195,8 +195,8 @@ func TestAccRDSClusterSnapshotCopy_destinationRegion(t *testing.T) { resource.TestCheckResourceAttr(resourceName, names.AttrStorageEncrypted, acctest.CtFalse), resource.TestCheckResourceAttrSet(resourceName, names.AttrEngine), resource.TestCheckResourceAttrSet(resourceName, names.AttrEngineVersion), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - resource.TestCheckResourceAttr(resourceName, "storage_encrypted", acctest.CtFalse), + resource.TestCheckNoResourceAttr(resourceName, names.AttrKMSKeyID), + resource.TestCheckResourceAttr(resourceName, names.AttrStorageEncrypted, acctest.CtFalse), resource.TestCheckResourceAttrSet(resourceName, "license_model"), resource.TestCheckResourceAttrSet(resourceName, "snapshot_type"), resource.TestCheckResourceAttrSet(resourceName, names.AttrVPCID), @@ -219,7 +219,7 @@ func TestAccRDSClusterSnapshotCopy_kmsKeyID(t *testing.T) { } var v types.DBClusterSnapshot - resourceName := "aws_db_cluster_snapshot_copy.test" + resourceName := "aws_rds_cluster_snapshot_copy.test" keyResourceName := "aws_kms_key.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -233,8 +233,8 @@ func TestAccRDSClusterSnapshotCopy_kmsKeyID(t *testing.T) { Config: testAccClusterSnapshotCopyConfig_kms(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterSnapshotCopyExists(ctx, resourceName, &v), - resource.TestCheckResourceAttrPair(resourceName, names.AttrKMSKeyID, keyResourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "storage_encrypted", acctest.CtTrue), + resource.TestCheckResourceAttrPair(resourceName, names.AttrKMSKeyID, keyResourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrStorageEncrypted, acctest.CtTrue), ), }, }, @@ -246,7 +246,7 @@ func testAccCheckClusterSnapshotCopyDestroy(ctx context.Context) resource.TestCh conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_db_cluster_snapshot_copy" { + if rs.Type != "aws_rds_cluster_snapshot_copy" { continue } @@ -323,16 +323,20 @@ resource "aws_db_cluster_snapshot" "encrypted" { } func testAccClusterSnapshotCopyConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` -resource "aws_db_cluster_snapshot_copy" "test" { + return acctest.ConfigCompose( + testAccClusterSnapshotCopyConfig_base(rName), + fmt.Sprintf(` +resource "aws_rds_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" }`, rName)) } func testAccClusterSnapshotCopyConfig_tags1(rName, tagKey, tagValue string) string { - return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` -resource "aws_db_cluster_snapshot_copy" "test" { + return acctest.ConfigCompose( + testAccClusterSnapshotCopyConfig_base(rName), + fmt.Sprintf(` +resource "aws_rds_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" @@ -343,8 +347,10 @@ resource "aws_db_cluster_snapshot_copy" "test" { } func testAccClusterSnapshotCopyConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` -resource "aws_db_cluster_snapshot_copy" "test" { + return acctest.ConfigCompose( + testAccClusterSnapshotCopyConfig_base(rName), + fmt.Sprintf(` +resource "aws_rds_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" @@ -356,8 +362,10 @@ resource "aws_db_cluster_snapshot_copy" "test" { } func testAccClusterSnapshotCopyConfig_share(rName string) string { - return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` -resource "aws_db_cluster_snapshot_copy" "test" { + return acctest.ConfigCompose( + testAccClusterSnapshotCopyConfig_base(rName), + fmt.Sprintf(` +resource "aws_rds_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" shared_accounts = ["all"] @@ -366,8 +374,10 @@ resource "aws_db_cluster_snapshot_copy" "test" { } func testAccClusterSnapshotCopyConfig_destinationRegion(rName string) string { - return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_base(rName), fmt.Sprintf(` -resource "aws_db_cluster_snapshot_copy" "test" { + return acctest.ConfigCompose( + testAccClusterSnapshotCopyConfig_base(rName), + fmt.Sprintf(` +resource "aws_rds_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.test.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" destination_region = %[2]q @@ -375,12 +385,14 @@ resource "aws_db_cluster_snapshot_copy" "test" { } func testAccClusterSnapshotCopyConfig_kms(rName string) string { - return acctest.ConfigCompose(testAccClusterSnapshotCopyConfig_encryptedBase(rName), fmt.Sprintf(` + return acctest.ConfigCompose( + testAccClusterSnapshotCopyConfig_encryptedBase(rName), + fmt.Sprintf(` resource "aws_kms_key" "test" { description = "test" } -resource "aws_db_cluster_snapshot_copy" "test" { +resource "aws_rds_cluster_snapshot_copy" "test" { source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.encrypted.db_cluster_snapshot_arn target_db_cluster_snapshot_identifier = "%[1]s-target" kms_key_id = aws_kms_key.test.arn diff --git a/internal/service/rds/exports_test.go b/internal/service/rds/exports_test.go index f5d11952ec7..b5d2678a457 100644 --- a/internal/service/rds/exports_test.go +++ b/internal/service/rds/exports_test.go @@ -13,7 +13,7 @@ var ( ResourceClusterParameterGroup = resourceClusterParameterGroup ResourceClusterRoleAssociation = resourceClusterRoleAssociation ResourceClusterSnapshot = resourceClusterSnapshot - ResourceClusterSnapshotCopy = resourceClusterSnapshotCopy + ResourceClusterSnapshotCopy = newResourceClusterSnapshotCopy ResourceCustomDBEngineVersion = resourceCustomDBEngineVersion ResourceEventSubscription = resourceEventSubscription ResourceGlobalCluster = resourceGlobalCluster diff --git a/internal/service/rds/service_package_gen.go b/internal/service/rds/service_package_gen.go index 2164f6e0f32..1f680fc7038 100644 --- a/internal/service/rds/service_package_gen.go +++ b/internal/service/rds/service_package_gen.go @@ -32,6 +32,13 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic IdentifierAttribute: names.AttrARN, }, }, + { + Factory: newResourceClusterSnapshotCopy, + Name: "Cluster Snapshot Copy", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "db_cluster_snapshot_arn", + }, + }, { Factory: newResourceExportTask, }, @@ -131,14 +138,6 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "db_cluster_snapshot_arn", }, }, - { - Factory: resourceClusterSnapshotCopy, - TypeName: "aws_db_cluster_snapshot_copy", - Name: "DB Cluster Snapshot Copy", - Tags: &types.ServicePackageResourceTags{ - IdentifierAttribute: "db_cluster_snapshot_arn", - }, - }, { Factory: resourceEventSubscription, TypeName: "aws_db_event_subscription", diff --git a/website/docs/r/db_cluster_snapshot_copy.html.markdown b/website/docs/r/rds_cluster_snapshot_copy.html.markdown similarity index 84% rename from website/docs/r/db_cluster_snapshot_copy.html.markdown rename to website/docs/r/rds_cluster_snapshot_copy.html.markdown index 34f84a7a3a3..d99504e049a 100644 --- a/website/docs/r/db_cluster_snapshot_copy.html.markdown +++ b/website/docs/r/rds_cluster_snapshot_copy.html.markdown @@ -1,14 +1,14 @@ --- subcategory: "RDS (Relational Database)" layout: "aws" -page_title: "AWS: aws_db_cluster_snapshot_copy" +page_title: "AWS: aws_rds_cluster_snapshot_copy" description: |- Manages an RDS database cluster snapshot copy. --- -# Resource: aws_db_cluster_snapshot_copy +# Resource: aws_rds_cluster_snapshot_copy -Manages an RDS database cluster snapshot copy. For managing RDS database instance snapshot copies, see the [`aws_db_cluster_snapshot_copy` resource](/docs/providers/aws/r/db_snapshot_copy.html). +Manages an RDS database cluster snapshot copy. For managing RDS database instance snapshot copies, see the [`aws_db_snapshot_copy` resource](/docs/providers/aws/r/db_snapshot_copy.html). ## Example Usage @@ -27,8 +27,7 @@ resource "aws_db_cluser_snapshot" "backup" { db_cluster_snapshot_identifier = "cluster-snapshot-source" } - -resource "aws_db_cluster_snapshot_copy" "copy" { +resource "aws_rds_cluster_snapshot_copy" "copy" { source_db_snapshot_identifier = aws_db_cluster_snapshot.backup.db_cluster_snapshot_arn target_db_snapshot_identifier = "copy-of-cluster-snapshot-source" } @@ -51,18 +50,17 @@ This resource supports the following arguments: This resource exports the following attributes in addition to the arguments above: -* `id` - Snapshot Identifier. * `allocated_storage` - Specifies the allocated storage size in gigabytes (GB). * `availability_zones` - Specifies the the Availability Zones the DB cluster was located in at the time of the DB snapshot. * `db_cluster_snapshot_arn` - The Amazon Resource Name (ARN) for the DB snapshot. -* `storage_encrypted` - Specifies whether the DB snapshot is encrypted. * `engine` - Specifies the name of the database engine. * `engine_version` - Specifies the version of the database engine. +* `id` - Snapshot Identifier. * `kms_key_id` - The ARN for the KMS encryption key. * `license_model` - License model information for the restored DB instance. * `shared_accounts` - (Optional) List of AWS Account IDs to share the snapshot with. Use `all` to make the snapshot public. * `source_db_cluster_snapshot_identifier` - The DB snapshot Arn that the DB snapshot was copied from. It only has value in case of cross customer or cross region copy. -* `source_region` - The region that the DB snapshot was created in or copied from. +* `storage_encrypted` - Specifies whether the DB snapshot is encrypted. * `storage_type` - Specifies the storage type associated with DB snapshot. * `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). * `vpc_id` - Provides the VPC ID associated with the DB snapshot. @@ -75,17 +73,17 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import `aws_db_cluster_snapshot_copy` using the snapshot identifier. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import `aws_rds_cluster_snapshot_copy` using the snapshot identifier. For example: ```terraform import { - to = aws_db_cluster_snapshot_copy.example + to = aws_rds_cluster_snapshot_copy.example id = "my-snapshot" } ``` -Using `terraform import`, import `aws_db_cluster_snapshot_copy` using the snapshot identifier. For example: +Using `terraform import`, import `aws_rds_cluster_snapshot_copy` using the snapshot identifier. For example: ```console -% terraform import aws_db_cluster_snapshot_copy.example my-snapshot +% terraform import aws_rds_cluster_snapshot_copy.example my-snapshot ``` From 3e355ea2080ec15ecd22d723835513ce3cde66e5 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 12 Dec 2024 15:42:06 -0500 Subject: [PATCH 12/15] chore: fix changelog entry --- .changelog/40398.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/40398.txt b/.changelog/40398.txt index 7e79cab285b..4a2e24cafef 100644 --- a/.changelog/40398.txt +++ b/.changelog/40398.txt @@ -1,3 +1,3 @@ ```release-note:new-resource -aws_rds_cluster_snapshot-copy +aws_rds_cluster_snapshot_copy ``` From 05358fb1b4e5e5c3c006edbf409142eeba94780e Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 12 Dec 2024 15:43:01 -0500 Subject: [PATCH 13/15] chore: rm leftover sdk version --- .../service/rds/cluster_snapshot_copy_sdk.go | 284 ------------------ 1 file changed, 284 deletions(-) delete mode 100644 internal/service/rds/cluster_snapshot_copy_sdk.go diff --git a/internal/service/rds/cluster_snapshot_copy_sdk.go b/internal/service/rds/cluster_snapshot_copy_sdk.go deleted file mode 100644 index 9b42bce993e..00000000000 --- a/internal/service/rds/cluster_snapshot_copy_sdk.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package rds - -// import ( -// "context" -// "log" -// "time" -// -// "github.com/YakDriver/regexache" -// "github.com/aws/aws-sdk-go-v2/aws" -// "github.com/aws/aws-sdk-go-v2/service/rds" -// "github.com/aws/aws-sdk-go-v2/service/rds/types" -// "github.com/hashicorp/terraform-plugin-sdk/v2/diag" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -// "github.com/hashicorp/terraform-provider-aws/internal/conns" -// "github.com/hashicorp/terraform-provider-aws/internal/errs" -// "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" -// "github.com/hashicorp/terraform-provider-aws/internal/flex" -// tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" -// "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -// "github.com/hashicorp/terraform-provider-aws/internal/verify" -// "github.com/hashicorp/terraform-provider-aws/names" -// ) -// -// // @SDKResource("aws_db_cluster_snapshot_copy", name="DB Cluster Snapshot Copy") -// // @Tags(identifierAttribute="db_cluster_snapshot_arn") -// // @Testing(tagsTest=false) -// // func resourceClusterSnapshotCopy() *schema.Resource { -// // return &schema.Resource{ -// // CreateWithoutTimeout: resourceClusterSnapshotCopyCreate, -// // ReadWithoutTimeout: resourceClusterSnapshotCopyRead, -// // UpdateWithoutTimeout: resourceClusterSnapshotCopyUpdate, -// // DeleteWithoutTimeout: resourceClusterSnapshotCopyDelete, -// // -// // Importer: &schema.ResourceImporter{ -// // StateContext: schema.ImportStatePassthroughContext, -// // }, -// // -// // Timeouts: &schema.ResourceTimeout{ -// // Create: schema.DefaultTimeout(20 * time.Minute), -// // }, -// // -// // Schema: map[string]*schema.Schema{ -// // names.AttrAllocatedStorage: { -// // Type: schema.TypeInt, -// // Computed: true, -// // }, -// // "copy_tags": { -// // Type: schema.TypeBool, -// // Optional: true, -// // ForceNew: true, -// // }, -// // "db_cluster_snapshot_arn": { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // "destination_region": { -// // Type: schema.TypeString, -// // Optional: true, -// // ForceNew: true, -// // }, -// // names.AttrStorageEncrypted: { -// // Type: schema.TypeBool, -// // Computed: true, -// // }, -// // names.AttrEngine: { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // names.AttrEngineVersion: { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // names.AttrKMSKeyID: { -// // Type: schema.TypeString, -// // Optional: true, -// // ForceNew: true, -// // }, -// // "license_model": { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // "presigned_url": { -// // Type: schema.TypeString, -// // Optional: true, -// // ForceNew: true, -// // }, -// // "shared_accounts": { -// // Type: schema.TypeSet, -// // Optional: true, -// // Elem: &schema.Schema{Type: schema.TypeString}, -// // }, -// // "source_db_cluster_snapshot_identifier": { -// // Type: schema.TypeString, -// // Required: true, -// // ForceNew: true, -// // }, -// // "source_region": { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // "snapshot_type": { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // names.AttrStorageType: { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // names.AttrTags: tftags.TagsSchema(), -// // names.AttrTagsAll: tftags.TagsSchemaComputed(), -// // "target_db_cluster_snapshot_identifier": { -// // Type: schema.TypeString, -// // Required: true, -// // ForceNew: true, -// // ValidateFunc: validation.All( -// // validation.StringLenBetween(1, 255), -// // validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z][\w-]+`), "must contain only alphanumeric, and hyphen (-) characters"), -// // ), -// // }, -// // names.AttrVPCID: { -// // Type: schema.TypeString, -// // Computed: true, -// // }, -// // }, -// // -// // CustomizeDiff: verify.SetTagsDiff, -// // } -// // } -// -// func resourceClusterSnapshotCopyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// var diags diag.Diagnostics -// conn := meta.(*conns.AWSClient).RDSClient(ctx) -// -// targetDBClusterSnapshotID := d.Get("target_db_cluster_snapshot_identifier").(string) -// input := &rds.CopyDBClusterSnapshotInput{ -// SourceDBClusterSnapshotIdentifier: aws.String(d.Get("source_db_cluster_snapshot_identifier").(string)), -// Tags: getTagsIn(ctx), -// TargetDBClusterSnapshotIdentifier: aws.String(targetDBClusterSnapshotID), -// CopyTags: aws.Bool(d.Get("copy_tags").(bool)), -// } -// -// if v, ok := d.GetOk("copy_tags"); ok { -// input.CopyTags = aws.Bool(v.(bool)) -// } -// -// if v, ok := d.GetOk(names.AttrKMSKeyID); ok { -// input.KmsKeyId = aws.String(v.(string)) -// } -// -// if v, ok := d.GetOk("presigned_url"); ok { -// input.PreSignedUrl = aws.String(v.(string)) -// } else if v, ok := d.GetOk("destination_region"); ok { -// output, err := rds.NewPresignClient(conn, func(o *rds.PresignOptions) { -// o.ClientOptions = append(o.ClientOptions, func(o *rds.Options) { -// o.Region = v.(string) -// }) -// }).PresignCopyDBClusterSnapshot(ctx, input) -// -// if err != nil { -// return sdkdiag.AppendErrorf(diags, "presigning RDS DB Cluster Snapshot Copy (%s) request: %s", targetDBClusterSnapshotID, err) -// } -// -// input.PreSignedUrl = aws.String(output.URL) -// } -// -// output, err := conn.CopyDBClusterSnapshot(ctx, input) -// -// if err != nil { -// return sdkdiag.AppendErrorf(diags, "creating RDS DB Cluster Snapshot Copy (%s): %s", targetDBClusterSnapshotID, err) -// } -// -// d.SetId(aws.ToString(output.DBClusterSnapshot.DBClusterSnapshotIdentifier)) -// -// if _, err := waitDBClusterSnapshotCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { -// return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Snapshot Copy (%s) create: %s", d.Id(), err) -// } -// -// if v, ok := d.GetOk("shared_accounts"); ok && v.(*schema.Set).Len() > 0 { -// input := &rds.ModifyDBClusterSnapshotAttributeInput{ -// AttributeName: aws.String("restore"), -// DBClusterSnapshotIdentifier: aws.String(d.Id()), -// ValuesToAdd: flex.ExpandStringValueSet(v.(*schema.Set)), -// } -// -// _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) -// -// if err != nil { -// return sdkdiag.AppendErrorf(diags, "modifying RDS DB Snapshot (%s) attribute: %s", d.Id(), err) -// } -// } -// -// return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) -// } -// -// func resourceClusterSnapshotCopyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// var diags diag.Diagnostics -// conn := meta.(*conns.AWSClient).RDSClient(ctx) -// -// snapshot, err := findDBClusterSnapshotByID(ctx, conn, d.Id()) -// -// if !d.IsNewResource() && tfresource.NotFound(err) { -// log.Printf("[WARN] RDS DB Cluster Snapshot (%s) not found, removing from state", d.Id()) -// d.SetId("") -// return diags -// } -// -// if err != nil { -// return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) -// } -// -// arn := aws.ToString(snapshot.DBClusterSnapshotArn) -// d.Set(names.AttrAllocatedStorage, snapshot.AllocatedStorage) -// d.Set("db_cluster_snapshot_arn", arn) -// d.Set(names.AttrStorageEncrypted, snapshot.StorageEncrypted) -// d.Set(names.AttrEngine, snapshot.Engine) -// d.Set(names.AttrEngineVersion, snapshot.EngineVersion) -// d.Set(names.AttrKMSKeyID, snapshot.KmsKeyId) -// d.Set("license_model", snapshot.LicenseModel) -// d.Set("snapshot_type", snapshot.SnapshotType) -// d.Set("source_db_cluster_snapshot_identifier", snapshot.SourceDBClusterSnapshotArn) -// d.Set(names.AttrStorageType, snapshot.StorageType) -// d.Set("target_db_cluster_snapshot_identifier", snapshot.DBClusterSnapshotIdentifier) -// d.Set(names.AttrVPCID, snapshot.VpcId) -// -// attribute, err := findDBClusterSnapshotAttributeByTwoPartKey(ctx, conn, d.Id(), dbSnapshotAttributeNameRestore) -// switch { -// case err == nil: -// d.Set("shared_accounts", attribute.AttributeValues) -// case tfresource.NotFound(err): -// default: -// return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) -// } -// -// return diags -// } -// -// func resourceClusterSnapshotCopyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// var diags diag.Diagnostics -// conn := meta.(*conns.AWSClient).RDSClient(ctx) -// -// if d.HasChange("shared_accounts") { -// o, n := d.GetChange("shared_accounts") -// os, ns := o.(*schema.Set), n.(*schema.Set) -// add, del := ns.Difference(os), os.Difference(ns) -// input := &rds.ModifyDBClusterSnapshotAttributeInput{ -// AttributeName: aws.String("restore"), -// DBClusterSnapshotIdentifier: aws.String(d.Id()), -// ValuesToAdd: flex.ExpandStringValueSet(add), -// ValuesToRemove: flex.ExpandStringValueSet(del), -// } -// -// _, err := conn.ModifyDBClusterSnapshotAttribute(ctx, input) -// -// if err != nil { -// return sdkdiag.AppendErrorf(diags, "modifying RDS DB Cluster Snapshot (%s) attribute: %s", d.Id(), err) -// } -// } -// -// return append(diags, resourceClusterSnapshotCopyRead(ctx, d, meta)...) -// } -// -// func resourceClusterSnapshotCopyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// var diags diag.Diagnostics -// conn := meta.(*conns.AWSClient).RDSClient(ctx) -// -// log.Printf("[DEBUG] Deleting RDS DB Cluster Snapshot Copy: %s", d.Id()) -// _, err := conn.DeleteDBClusterSnapshot(ctx, &rds.DeleteDBClusterSnapshotInput{ -// DBClusterSnapshotIdentifier: aws.String(d.Id()), -// }) -// -// if errs.IsA[*types.DBClusterSnapshotNotFoundFault](err) { -// return diags -// } -// -// if err != nil { -// return sdkdiag.AppendErrorf(diags, "deleting RDS DB Cluster Snapshot Copy (%s): %s", d.Id(), err) -// } -// -// return diags -// } From 5728634b9482e10447113bc81c953b7de1c0f5aa Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 12 Dec 2024 15:54:38 -0500 Subject: [PATCH 14/15] r/aws_rds_cluster_snapshot_copy(doc): fix example, tidy --- .../r/rds_cluster_snapshot_copy.html.markdown | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/website/docs/r/rds_cluster_snapshot_copy.html.markdown b/website/docs/r/rds_cluster_snapshot_copy.html.markdown index d99504e049a..0f70350deb4 100644 --- a/website/docs/r/rds_cluster_snapshot_copy.html.markdown +++ b/website/docs/r/rds_cluster_snapshot_copy.html.markdown @@ -13,7 +13,7 @@ Manages an RDS database cluster snapshot copy. For managing RDS database instanc ## Example Usage ```terraform -resource "aws_rds_cluster" "test" { +resource "aws_rds_cluster" "example" { cluster_identifier = "aurora-cluster-demo" database_name = "test" engine = "aurora-mysql" @@ -22,28 +22,31 @@ resource "aws_rds_cluster" "test" { skip_final_snapshot = true } -resource "aws_db_cluser_snapshot" "backup" { - db_cluster_identifier = aws_db_cluster.default.identifier - db_cluster_snapshot_identifier = "cluster-snapshot-source" +resource "aws_db_cluster_snapshot" "example" { + db_cluster_identifier = aws_rds_cluster.example.cluster_identifier + db_cluster_snapshot_identifier = "example" } -resource "aws_rds_cluster_snapshot_copy" "copy" { - source_db_snapshot_identifier = aws_db_cluster_snapshot.backup.db_cluster_snapshot_arn - target_db_snapshot_identifier = "copy-of-cluster-snapshot-source" +resource "aws_rds_cluster_snapshot_copy" "example" { + source_db_cluster_snapshot_identifier = aws_db_cluster_snapshot.example.db_cluster_snapshot_arn + target_db_cluster_snapshot_identifier = "example-copy" } ``` ## Argument Reference -This resource supports the following arguments: +The following arguments are required: + +* `source_db_cluster_snapshot_identifier` - (Required) Identifier of the source snapshot. +* `target_db_cluster_snapshot_identifier` - (Required) Identifier for the snapshot. + +The following arguments are optional: * `copy_tags` - (Optional) Whether to copy existing tags. Defaults to `false`. * `destination_region` - (Optional) The Destination region to place snapshot copy. * `kms_key_id` - (Optional) KMS key ID. -* `presigned_url` - (Optional) he URL that contains a Signature Version 4 signed request. +* `presigned_url` - (Optional) URL that contains a Signature Version 4 signed request. * `shared_accounts` - (Optional) List of AWS Account IDs to share the snapshot with. Use `all` to make the snapshot public. -* `source_db_cluster_snapshot_identifier` - (Required) Snapshot identifier of the source snapshot. -* `target_db_cluster_snapshot_identifier` - (Required) The Identifier for the snapshot. * `tags` - (Optional) Key-value map of resource tags. 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 @@ -52,18 +55,18 @@ This resource exports the following attributes in addition to the arguments abov * `allocated_storage` - Specifies the allocated storage size in gigabytes (GB). * `availability_zones` - Specifies the the Availability Zones the DB cluster was located in at the time of the DB snapshot. -* `db_cluster_snapshot_arn` - The Amazon Resource Name (ARN) for the DB snapshot. +* `db_cluster_snapshot_arn` - The Amazon Resource Name (ARN) for the DB cluster snapshot. * `engine` - Specifies the name of the database engine. * `engine_version` - Specifies the version of the database engine. -* `id` - Snapshot Identifier. -* `kms_key_id` - The ARN for the KMS encryption key. +* `id` - Cluster snapshot identifier. +* `kms_key_id` - ARN for the KMS encryption key. * `license_model` - License model information for the restored DB instance. * `shared_accounts` - (Optional) List of AWS Account IDs to share the snapshot with. Use `all` to make the snapshot public. -* `source_db_cluster_snapshot_identifier` - The DB snapshot Arn that the DB snapshot was copied from. It only has value in case of cross customer or cross region copy. -* `storage_encrypted` - Specifies whether the DB snapshot is encrypted. -* `storage_type` - Specifies the storage type associated with DB snapshot. +* `source_db_cluster_snapshot_identifier` - DB snapshot ARN that the DB cluster snapshot was copied from. It only has value in case of cross customer or cross region copy. +* `storage_encrypted` - Specifies whether the DB cluster snapshot is encrypted. +* `storage_type` - Specifies the storage type associated with DB cluster snapshot. * `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). -* `vpc_id` - Provides the VPC ID associated with the DB snapshot. +* `vpc_id` - Provides the VPC ID associated with the DB cluster snapshot. ## Timeouts @@ -82,7 +85,7 @@ import { } ``` -Using `terraform import`, import `aws_rds_cluster_snapshot_copy` using the snapshot identifier. For example: +Using `terraform import`, import `aws_rds_cluster_snapshot_copy` using the `id`. For example: ```console % terraform import aws_rds_cluster_snapshot_copy.example my-snapshot From e5f5fe6c542405ae059e28ee52b7ffb2fd532ae9 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 12 Dec 2024 21:54:40 -0500 Subject: [PATCH 15/15] chore: fix golangci-lint finding --- internal/service/rds/cluster_snapshot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/rds/cluster_snapshot.go b/internal/service/rds/cluster_snapshot.go index 728a5b2ee6c..ff228791809 100644 --- a/internal/service/rds/cluster_snapshot.go +++ b/internal/service/rds/cluster_snapshot.go @@ -339,7 +339,7 @@ func statusDBClusterSnapshot(ctx context.Context, conn *rds.Client, id string) r } } -func waitDBClusterSnapshotCreated(ctx context.Context, conn *rds.Client, id string, timeout time.Duration) (*types.DBClusterSnapshot, error) { +func waitDBClusterSnapshotCreated(ctx context.Context, conn *rds.Client, id string, timeout time.Duration) (*types.DBClusterSnapshot, error) { //nolint:unparam stateConf := &retry.StateChangeConf{ Pending: []string{clusterSnapshotStatusCreating, clusterSnapshotStatusCopying}, Target: []string{clusterSnapshotStatusAvailable},