From a38643da05cd9f6bef400535ba1dd061d4f12bc0 Mon Sep 17 00:00:00 2001 From: "snellman.lars" Date: Tue, 21 Sep 2021 14:19:24 -0400 Subject: [PATCH 01/31] Initial support for new resource - aws_docdb_global_cluster --- aws/provider.go | 1 + aws/resource_aws_docdb_cluster.go | 88 ++- aws/resource_aws_docdb_cluster_test.go | 327 ++++++++++ aws/resource_aws_docdb_global_cluster.go | 574 ++++++++++++++++++ aws/resource_aws_docdb_global_cluster_test.go | 567 +++++++++++++++++ website/docs/r/docdb_cluster.html.markdown | 1 + .../docs/r/docdb_global_cluster.html.markdown | 148 +++++ 7 files changed, 1700 insertions(+), 6 deletions(-) create mode 100644 aws/resource_aws_docdb_global_cluster.go create mode 100644 aws/resource_aws_docdb_global_cluster_test.go create mode 100644 website/docs/r/docdb_global_cluster.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 549af60dd13..54429b05994 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -682,6 +682,7 @@ func Provider() *schema.Provider { "aws_docdb_cluster_instance": resourceAwsDocDBClusterInstance(), "aws_docdb_cluster_parameter_group": resourceAwsDocDBClusterParameterGroup(), "aws_docdb_cluster_snapshot": resourceAwsDocDBClusterSnapshot(), + "aws_docdb_global_cluster": resourceAwsDocDBGlobalCluster(), "aws_docdb_subnet_group": resourceAwsDocDBSubnetGroup(), "aws_dx_bgp_peer": resourceAwsDxBgpPeer(), "aws_dx_connection": resourceAwsDxConnection(), diff --git a/aws/resource_aws_docdb_cluster.go b/aws/resource_aws_docdb_cluster.go index 420585a7c46..8a2ab1c6012 100644 --- a/aws/resource_aws_docdb_cluster.go +++ b/aws/resource_aws_docdb_cluster.go @@ -1,7 +1,10 @@ package aws import ( + "context" + "errors" "fmt" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "log" "regexp" "strings" @@ -90,6 +93,11 @@ func resourceAwsDocDBCluster() *schema.Resource { Computed: true, }, + "global_cluster_identifier": { + Type: schema.TypeString, + Optional: true, + }, + "reader_endpoint": { Type: schema.TypeString, Computed: true, @@ -358,12 +366,18 @@ func resourceAwsDocDBClusterCreate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error creating DocDB Cluster: %s", err) } } else { - if _, ok := d.GetOk("master_password"); !ok { - return fmt.Errorf(`provider.aws: aws_docdb_cluster: %s: "master_password": required field is not set`, identifier) + // Secondary DocDB clusters part of a global cluster will not supply the master_password + if _, ok := d.GetOk("global_cluster_identifier"); !ok { + if _, ok := d.GetOk("master_password"); !ok { + return fmt.Errorf(`provider.aws: aws_docdb_cluster: %s: "master_password": required field is not set`, identifier) + } } - if _, ok := d.GetOk("master_username"); !ok { - return fmt.Errorf(`provider.aws: aws_docdb_cluster: %s: "master_username": required field is not set`, identifier) + // Secondary DocDB clusters part of a global cluster will not supply the master_username + if _, ok := d.GetOk("global_cluster_identifier"); !ok { + if _, ok := d.GetOk("master_username"); !ok { + return fmt.Errorf(`provider.aws: aws_docdb_cluster: %s: "master_username": required field is not set`, identifier) + } } createOpts := &docdb.CreateDBClusterInput{ @@ -375,6 +389,10 @@ func resourceAwsDocDBClusterCreate(d *schema.ResourceData, meta interface{}) err Tags: tags.IgnoreAws().DocdbTags(), } + if attr, ok := d.GetOk("global_cluster_identifier"); ok { + createOpts.GlobalClusterIdentifier = aws.String(attr.(string)) + } + if attr, ok := d.GetOk("port"); ok { createOpts.Port = aws.Int64(int64(attr.(int))) } @@ -527,6 +545,20 @@ func resourceAwsDocDBClusterRead(d *schema.ResourceData, meta interface{}) error return nil } + globalCluster, err := docDBDescribeGlobalClusterFromDbClusterARN(context.TODO(), conn, aws.StringValue(dbc.DBClusterArn)) + + // Ignore the following API error for regions/partitions that do not support DocDB Global Clusters: + // InvalidParameterValue: Access Denied to API Version: APIGlobalDatabases + if err != nil && !isAWSErr(err, "InvalidParameterValue", "Access Denied to API Version: APIGlobalDatabases") { + return fmt.Errorf("error reading DocDB Global Cluster information for DB Cluster (%s): %w", d.Id(), err) + } + + if globalCluster != nil { + d.Set("global_cluster_identifier", globalCluster.GlobalClusterIdentifier) + } else { + d.Set("global_cluster_identifier", "") + } + if err := d.Set("availability_zones", aws.StringValueSlice(dbc.AvailabilityZones)); err != nil { return fmt.Errorf("error setting availability_zones: %s", err) } @@ -544,7 +576,6 @@ func resourceAwsDocDBClusterRead(d *schema.ResourceData, meta interface{}) error } d.Set("cluster_resource_id", dbc.DbClusterResourceId) - d.Set("db_cluster_parameter_group_name", dbc.DBClusterParameterGroup) d.Set("db_subnet_group_name", dbc.DBSubnetGroup) @@ -652,6 +683,32 @@ func resourceAwsDocDBClusterUpdate(d *schema.ResourceData, meta interface{}) err requestUpdate = true } + if d.HasChange("global_cluster_identifier") { + oRaw, nRaw := d.GetChange("global_cluster_identifier") + o := oRaw.(string) + n := nRaw.(string) + + if o == "" { + return errors.New("existing DocDB Clusters cannot be added to an existing DocDB Global Cluster") + } + + if n != "" { + return errors.New("existing DocDB Clusters cannot be migrated between existing DocDB Global Clusters") + } + + input := &docdb.RemoveFromGlobalClusterInput{ + DbClusterIdentifier: aws.String(d.Get("arn").(string)), + GlobalClusterIdentifier: aws.String(o), + } + + log.Printf("[DEBUG] Removing DocDB Cluster from DocDB Global Cluster: %s", input) + _, err := conn.RemoveFromGlobalCluster(input) + + if err != nil && !tfawserr.ErrCodeEquals(err, docdb.ErrCodeGlobalClusterNotFoundFault) && !tfawserr.ErrMessageContains(err, "InvalidParameterValue", "is not found in global cluster") { + return fmt.Errorf("error removing DocDB Cluster (%s) from DocDB Global Cluster: %w", d.Id(), err) + } + } + if requestUpdate { err := resource.Retry(5*time.Minute, func() *resource.RetryError { _, err := conn.ModifyDBCluster(req) @@ -702,6 +759,22 @@ func resourceAwsDocDBClusterDelete(d *schema.ResourceData, meta interface{}) err conn := meta.(*AWSClient).docdbconn log.Printf("[DEBUG] Destroying DocDB Cluster (%s)", d.Id()) + // Automatically remove from global cluster to bypass this error on deletion: + // InvalidDBClusterStateFault: This cluster is a part of a global cluster, please remove it from globalcluster first + if d.Get("global_cluster_identifier").(string) != "" { + input := &docdb.RemoveFromGlobalClusterInput{ + DbClusterIdentifier: aws.String(d.Get("arn").(string)), + GlobalClusterIdentifier: aws.String(d.Get("global_cluster_identifier").(string)), + } + + log.Printf("[DEBUG] Removing DocDB Cluster from DocDB Global Cluster: %s", input) + _, err := conn.RemoveFromGlobalCluster(input) + + if err != nil && !tfawserr.ErrCodeEquals(err, docdb.ErrCodeGlobalClusterNotFoundFault) && !tfawserr.ErrMessageContains(err, "InvalidParameterValue", "is not found in global cluster") { + return fmt.Errorf("error removing DocDB Cluster (%s) from DocDB Global Cluster: %w", d.Id(), err) + } + } + deleteOpts := docdb.DeleteDBClusterInput{ DBClusterIdentifier: aws.String(d.Id()), } @@ -719,12 +792,15 @@ func resourceAwsDocDBClusterDelete(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] DocDB Cluster delete options: %s", deleteOpts) - err := resource.Retry(1*time.Minute, func() *resource.RetryError { + err := resource.Retry(5*time.Minute, func() *resource.RetryError { _, err := conn.DeleteDBCluster(&deleteOpts) if err != nil { if isAWSErr(err, docdb.ErrCodeInvalidDBClusterStateFault, "is not currently in the available state") { return resource.RetryableError(err) } + if isAWSErr(err, docdb.ErrCodeInvalidDBClusterStateFault, "cluster is a part of a global cluster") { + return resource.RetryableError(err) + } if isAWSErr(err, docdb.ErrCodeDBClusterNotFoundFault, "") { return nil } diff --git a/aws/resource_aws_docdb_cluster_test.go b/aws/resource_aws_docdb_cluster_test.go index 650b8f18b6f..5b86e6975ea 100644 --- a/aws/resource_aws_docdb_cluster_test.go +++ b/aws/resource_aws_docdb_cluster_test.go @@ -128,6 +128,197 @@ func TestAccAWSDocDBCluster_generatedName(t *testing.T) { }) } +func TestAccAWSDocDBCluster_GlobalClusterIdentifier(t *testing.T) { + var dbCluster1 docdb.DBCluster + + rName := acctest.RandomWithPrefix("tf-acc-test") + globalClusterResourceName := "aws_docdb_cluster.test" + resourceName := "aws_docdb_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckDocDBClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBClusterConfigGlobalClusterIdentifier(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDocDBClusterExists(resourceName, &dbCluster1), + resource.TestCheckResourceAttrPair(resourceName, "global_cluster_identifier", globalClusterResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "apply_immediately", + "cluster_identifier_prefix", + "master_password", + "skip_final_snapshot", + "snapshot_identifier", + }, + }, + }, + }) +} + +func TestAccAWSDocDBCluster_GlobalClusterIdentifier_Add(t *testing.T) { + var dbCluster1 docdb.DBCluster + + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckDocDBClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBClusterConfigGlobalCompatible(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDocDBClusterExists(resourceName, &dbCluster1), + resource.TestCheckResourceAttr(resourceName, "global_cluster_identifier", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "apply_immediately", + "cluster_identifier_prefix", + "master_password", + "skip_final_snapshot", + "snapshot_identifier", + }, + }, + { + Config: testAccAWSDocDBClusterConfigGlobalClusterIdentifier(rName), + ExpectError: regexp.MustCompile(`existing DocDB Clusters cannot be added to an existing DocDB Global Cluster`), + }, + }, + }) +} + +func TestAccAWSDocDBCluster_GlobalClusterIdentifier_Remove(t *testing.T) { + var dbCluster1 docdb.DBCluster + + rName := acctest.RandomWithPrefix("tf-acc-test") + globalClusterResourceName := "aws_docdb_global_cluster.test" + resourceName := "aws_docdb_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckDocDBClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBClusterConfigGlobalClusterIdentifier(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDocDBClusterExists(resourceName, &dbCluster1), + resource.TestCheckResourceAttrPair(resourceName, "global_cluster_identifier", globalClusterResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "apply_immediately", + "cluster_identifier_prefix", + "master_password", + "skip_final_snapshot", + "snapshot_identifier", + }, + }, + { + Config: testAccAWSDocDBClusterConfigGlobalCompatible(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDocDBClusterExists(resourceName, &dbCluster1), + resource.TestCheckResourceAttr(resourceName, "global_cluster_identifier", ""), + ), + }, + }, + }) +} + +func TestAccAWSDocDBCluster_GlobalClusterIdentifier_Update(t *testing.T) { + var dbCluster1 docdb.DBCluster + + rName := acctest.RandomWithPrefix("tf-acc-test") + globalClusterResourceName1 := "aws_docdb_global_cluster.test.0" + globalClusterResourceName2 := "aws_docdb_global_cluster.test.1" + resourceName := "aws_docdb_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckDocDBClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBClusterConfigGlobalClusterIdentifier_Update(rName, globalClusterResourceName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckDocDBClusterExists(resourceName, &dbCluster1), + resource.TestCheckResourceAttrPair(resourceName, "global_cluster_identifier", globalClusterResourceName1, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "apply_immediately", + "cluster_identifier_prefix", + "master_password", + "skip_final_snapshot", + "snapshot_identifier", + }, + }, + { + Config: testAccAWSDocDBClusterConfigGlobalClusterIdentifier_Update(rName, globalClusterResourceName2), + ExpectError: regexp.MustCompile(`existing DocDB Clusters cannot be migrated between existing DocDB Global Clusters`), + }, + }, + }) +} + +func TestAccAWSDocDBCluster_GlobalClusterIdentifier_PrimarySecondaryClusters(t *testing.T) { + var providers []*schema.Provider + var primaryDbCluster, secondaryDbCluster docdb.DBCluster + + rNameGlobal := acctest.RandomWithPrefix("tf-acc-test-global") + rNamePrimary := acctest.RandomWithPrefix("tf-acc-test-primary") + rNameSecondary := acctest.RandomWithPrefix("tf-acc-test-secondary") + + resourceNamePrimary := "aws_docdb_cluster.primary" + resourceNameSecondary := "aws_docdb_cluster.secondary" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccMultipleRegionPreCheck(t, 2) + testAccPreCheckAWSDocDBGlobalCluster(t) + }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckDocDBClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBClusterConfigGlobalClusterIdentifierPrimarySecondaryClusters(rNameGlobal, rNamePrimary, rNameSecondary), + Check: resource.ComposeTestCheckFunc( + testAccCheckDocDBClusterExistsWithProvider(resourceNamePrimary, &primaryDbCluster, testAccAwsRegionProviderFunc(testAccGetRegion(), &providers)), + testAccCheckDocDBClusterExistsWithProvider(resourceNameSecondary, &secondaryDbCluster, testAccAwsRegionProviderFunc(testAccGetAlternateRegion(), &providers)), + ), + }, + }, + }) +} + func TestAccAWSDocDBCluster_takeFinalSnapshot(t *testing.T) { var v docdb.DBCluster rInt := acctest.RandInt() @@ -472,6 +663,142 @@ func TestAccAWSDocDBCluster_deleteProtection(t *testing.T) { }) } +func testAccAWSDocDBClusterConfigGlobalClusterIdentifierPrimarySecondaryClusters(rNameGlobal, rNamePrimary, rNameSecondary string) string { + return composeConfig( + testAccMultipleRegionProviderConfig(2), + fmt.Sprintf(` +data "aws_availability_zones" "alternate" { + provider = "awsalternate" + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_docdb_global_cluster" "test" { + global_cluster_identifier = "%[1]s" + engine = "docdb" + engine_version = "4.0.0" +} + +resource "aws_docdb_cluster" "primary" { + cluster_identifier = "%[2]s" + master_username = "foo" + master_password = "barbarbar" + skip_final_snapshot = true + global_cluster_identifier = aws_docdb_global_cluster.test.id + engine = aws_docdb_global_cluster.test.engine + engine_version = aws_docdb_global_cluster.test.engine_version +} + +resource "aws_docdb_cluster_instance" "primary" { + identifier = "%[2]s" + cluster_identifier = aws_docdb_cluster.primary.id + instance_class = "db.r5.large" +} + +resource "aws_vpc" "alternate" { + provider = "awsalternate" + cidr_block = "10.0.0.0/16" + + tags = { + Name = "%[3]s" + } +} + +resource "aws_subnet" "alternate" { + provider = "awsalternate" + count = 3 + vpc_id = aws_vpc.alternate.id + availability_zone = data.aws_availability_zones.alternate.names[count.index] + cidr_block = "10.0.${count.index}.0/24" + + tags = { + Name = "%[3]s" + } +} + +resource "aws_docdb_subnet_group" "alternate" { + provider = "awsalternate" + name = "%[3]s" + subnet_ids = aws_subnet.alternate[*].id +} + +resource "aws_docdb_cluster" "secondary" { + provider = "awsalternate" + cluster_identifier = "%[3]s" + skip_final_snapshot = true + db_subnet_group_name = aws_docdb_subnet_group.alternate.name + global_cluster_identifier = aws_docdb_global_cluster.test.id + engine = aws_docdb_global_cluster.test.engine + engine_version = aws_docdb_global_cluster.test.engine_version + depends_on = [aws_docdb_cluster_instance.primary] +} + +resource "aws_docdb_cluster_instance" "secondary" { + provider = "awsalternate" + identifier = "%[3]s" + cluster_identifier = aws_docdb_cluster.secondary.id + instance_class = "db.r5.large" +} +`, rNameGlobal, rNamePrimary, rNameSecondary)) +} + +func testAccAWSDocDBClusterConfigGlobalClusterIdentifier_Update(rName, globalClusterIdentifierResourceName string) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + count = 2 + + engine_version = "4.0.0" # version compatible with global + global_cluster_identifier = "%[1]s-${count.index}" +} + +resource "aws_docdb_cluster" "test" { + cluster_identifier = %[1]q + global_cluster_identifier = %[2]s.id + engine_version = %[2]s.engine_version + master_password = "barbarbarbar" + master_username = "foo" + skip_final_snapshot = true +} +`, rName, globalClusterIdentifierResourceName) +} + +func testAccAWSDocDBClusterConfigGlobalCompatible(rName string) string { + return fmt.Sprintf(` +resource "aws_docdb_cluster" "test" { + cluster_identifier = %[1]q + engine_version = "4.0.0" # version compatible with global + master_password = "barbarbarbar" + master_username = "foo" + skip_final_snapshot = true +} +`, rName) +} + +// engine_version = "4.0.0" # version compatible +// force_destroy = true # Partial configuration removal ordering fix for after Terraform 0.12 +func testAccAWSDocDBClusterConfigGlobalClusterIdentifier(rName string) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + engine_version = "4.0.0" # version compatible + global_cluster_identifier = %[1]q + force_destroy = true # Partial configuration removal ordering fix for after Terraform 0.12 +} + +resource "aws_docdb_cluster" "test" { + cluster_identifier = %[1]q + global_cluster_identifier = aws_docdb_global_cluster.test.id + engine_version = aws_docdb_global_cluster.test.engine_version + master_password = "barbarbarbar" + master_username = "foo" + skip_final_snapshot = true +} +`, rName) +} + func testAccCheckDocDBClusterDestroy(s *terraform.State) error { return testAccCheckDocDBClusterDestroyWithProvider(s, testAccProvider) } diff --git a/aws/resource_aws_docdb_global_cluster.go b/aws/resource_aws_docdb_global_cluster.go new file mode 100644 index 00000000000..0aa68a85ef3 --- /dev/null +++ b/aws/resource_aws_docdb_global_cluster.go @@ -0,0 +1,574 @@ +package aws + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/docdb" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +const ( + docDBGlobalClusterRemovalTimeout = 2 * time.Minute +) + +func resourceAwsDocDBGlobalCluster() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAwsDocDBGlobalClusterCreate, + ReadWithoutTimeout: resourceAwsDocDBGlobalClusterRead, + UpdateWithoutTimeout: resourceAwsDocDBGlobalClusterUpdate, + DeleteWithoutTimeout: resourceAwsDocDBGlobalClusterDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "database_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "deletion_protection": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "engine": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"source_db_cluster_identifier"}, + ValidateFunc: validation.StringInSlice([]string{ + "docdb", + }, false), + }, + "engine_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "force_destroy": { + Type: schema.TypeBool, + Optional: true, + }, + "global_cluster_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "global_cluster_members": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "db_cluster_arn": { + Type: schema.TypeString, + Computed: true, + }, + "is_writer": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "global_cluster_resource_id": { + Type: schema.TypeString, + Computed: true, + }, + "source_db_cluster_identifier": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"engine"}, + RequiredWith: []string{"force_destroy"}, + }, + "storage_encrypted": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsDocDBGlobalClusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).docdbconn + + input := &docdb.CreateGlobalClusterInput{ + GlobalClusterIdentifier: aws.String(d.Get("global_cluster_identifier").(string)), + } + + if v, ok := d.GetOk("database_name"); ok { + input.DatabaseName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("deletion_protection"); ok { + input.DeletionProtection = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("engine"); ok { + input.Engine = aws.String(v.(string)) + } + + if v, ok := d.GetOk("engine_version"); ok { + input.EngineVersion = aws.String(v.(string)) + } + + if v, ok := d.GetOk("source_db_cluster_identifier"); ok { + input.SourceDBClusterIdentifier = aws.String(v.(string)) + } + + if v, ok := d.GetOk("storage_encrypted"); ok { + input.StorageEncrypted = aws.Bool(v.(bool)) + } + + // Prevent the following error and keep the previous default, + // since we cannot have Engine default after adding SourceDBClusterIdentifier: + // InvalidParameterValue: When creating standalone global cluster, value for engineName should be specified + if input.Engine == nil && input.SourceDBClusterIdentifier == nil { + input.Engine = aws.String("docdb") + } + + output, err := conn.CreateGlobalClusterWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating DocDB Global Cluster: %w", err)) + } + + d.SetId(aws.StringValue(output.GlobalCluster.GlobalClusterIdentifier)) + + if err := waitForDocDBGlobalClusterCreation(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) availability: %w", d.Id(), err)) + } + + return resourceAwsDocDBGlobalClusterRead(ctx, d, meta) +} + +func resourceAwsDocDBGlobalClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).docdbconn + + globalCluster, err := docDBDescribeGlobalCluster(ctx, conn, d.Id()) + + if isAWSErr(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { + log.Printf("[WARN] DocDB Global Cluster (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading DocDB Global Cluster: %w", err)) + } + + if globalCluster == nil { + log.Printf("[WARN] DocDB Global Cluster (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if aws.StringValue(globalCluster.Status) == "deleting" || aws.StringValue(globalCluster.Status) == "deleted" { + log.Printf("[WARN] DocDB Global Cluster (%s) in deleted state (%s), removing from state", d.Id(), aws.StringValue(globalCluster.Status)) + d.SetId("") + return nil + } + + _ = d.Set("arn", globalCluster.GlobalClusterArn) + _ = d.Set("database_name", globalCluster.DatabaseName) + _ = d.Set("deletion_protection", globalCluster.DeletionProtection) + _ = d.Set("engine", globalCluster.Engine) + _ = d.Set("engine_version", globalCluster.EngineVersion) + _ = d.Set("global_cluster_identifier", globalCluster.GlobalClusterIdentifier) + + if err := d.Set("global_cluster_members", flattenDocDBGlobalClusterMembers(globalCluster.GlobalClusterMembers)); err != nil { + return diag.FromErr(fmt.Errorf("error setting global_cluster_members: %w", err)) + } + + _ = d.Set("global_cluster_resource_id", globalCluster.GlobalClusterResourceId) + _ = d.Set("storage_encrypted", globalCluster.StorageEncrypted) + + return nil +} + +func resourceAwsDocDBGlobalClusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).docdbconn + + input := &docdb.ModifyGlobalClusterInput{ + DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), + GlobalClusterIdentifier: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Updating DocDB Global Cluster (%s): %s", d.Id(), input) + + if d.HasChange("engine_version") { + if err := resourceAwsDocDBGlobalClusterUpgradeEngineVersion(ctx, d, conn); err != nil { + return diag.FromErr(err) + } + } + + _, err := conn.ModifyGlobalClusterWithContext(ctx, input) + + if isAWSErr(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error updating DocDB Global Cluster: %w", err)) + } + + if err := waitForDocDBGlobalClusterUpdate(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) update: %w", d.Id(), err)) + } + + return resourceAwsDocDBGlobalClusterRead(ctx, d, meta) +} + +func resourceAwsDocDBGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).docdbconn + + if d.Get("force_destroy").(bool) { + for _, globalClusterMemberRaw := range d.Get("global_cluster_members").(*schema.Set).List() { + globalClusterMember, ok := globalClusterMemberRaw.(map[string]interface{}) + + if !ok { + continue + } + + dbClusterArn, ok := globalClusterMember["db_cluster_arn"].(string) + + if !ok { + continue + } + + input := &docdb.RemoveFromGlobalClusterInput{ + DbClusterIdentifier: aws.String(dbClusterArn), + GlobalClusterIdentifier: aws.String(d.Id()), + } + + _, err := conn.RemoveFromGlobalClusterWithContext(ctx, input) + + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "is not found in global cluster") { + continue + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error removing DocDB Cluster (%s) from Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) + } + + if err := waitForDocDBGlobalClusterRemoval(ctx, conn, dbClusterArn); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for DocDB Cluster (%s) removal from DocDB Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) + } + } + } + + input := &docdb.DeleteGlobalClusterInput{ + GlobalClusterIdentifier: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DocDB Global Cluster (%s): %s", d.Id(), input) + + // Allow for eventual consistency + err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { + _, err := conn.DeleteGlobalClusterWithContext(ctx, input) + + if isAWSErr(err, docdb.ErrCodeInvalidGlobalClusterStateFault, "is not empty") { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.DeleteGlobalClusterWithContext(ctx, input) + } + + if isAWSErr(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting DocDB Global Cluster: %w", err)) + } + + if err := waitForDocDBGlobalClusterDeletion(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) deletion: %w", d.Id(), err)) + } + + return nil +} + +func flattenDocDBGlobalClusterMembers(apiObjects []*docdb.GlobalClusterMember) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "db_cluster_arn": aws.StringValue(apiObject.DBClusterArn), + "is_writer": aws.BoolValue(apiObject.IsWriter), + } + + tfList = append(tfList, tfMap) + } + + return tfList +} + +func docDBDescribeGlobalCluster(ctx context.Context, conn *docdb.DocDB, globalClusterID string) (*docdb.GlobalCluster, error) { + var globalCluster *docdb.GlobalCluster + + input := &docdb.DescribeGlobalClustersInput{ + GlobalClusterIdentifier: aws.String(globalClusterID), + } + + log.Printf("[DEBUG] Reading DocDB Global Cluster (%s): %s", globalClusterID, input) + err := conn.DescribeGlobalClustersPagesWithContext(ctx, input, func(page *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, gc := range page.GlobalClusters { + if gc == nil { + continue + } + + if aws.StringValue(gc.GlobalClusterIdentifier) == globalClusterID { + globalCluster = gc + return false + } + } + + return !lastPage + }) + + return globalCluster, err +} + +func docDBDescribeGlobalClusterFromDbClusterARN(ctx context.Context, conn *docdb.DocDB, dbClusterARN string) (*docdb.GlobalCluster, error) { + var globalCluster *docdb.GlobalCluster + + input := &docdb.DescribeGlobalClustersInput{ + Filters: []*docdb.Filter{ + { + Name: aws.String("db-cluster-id"), + Values: []*string{aws.String(dbClusterARN)}, + }, + }, + } + + log.Printf("[DEBUG] Reading DocDB Global Clusters: %s", input) + err := conn.DescribeGlobalClustersPagesWithContext(ctx, input, func(page *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, gc := range page.GlobalClusters { + if gc == nil { + continue + } + + for _, globalClusterMember := range gc.GlobalClusterMembers { + if aws.StringValue(globalClusterMember.DBClusterArn) == dbClusterARN { + globalCluster = gc + return false + } + } + } + + return !lastPage + }) + + return globalCluster, err +} + +func docDBGlobalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClusterID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + globalCluster, err := docDBDescribeGlobalCluster(ctx, conn, globalClusterID) + + if isAWSErr(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { + return nil, "deleted", nil + } + + if err != nil { + return nil, "", fmt.Errorf("error reading DocDB Global Cluster (%s): %w", globalClusterID, err) + } + + if globalCluster == nil { + return nil, "deleted", nil + } + + return globalCluster, aws.StringValue(globalCluster.Status), nil + } +} + +func waitForDocDBGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating"}, + Target: []string{"available"}, + Refresh: docDBGlobalClusterRefreshFunc(ctx, conn, globalClusterID), + Timeout: 10 * time.Minute, + } + + log.Printf("[DEBUG] Waiting for DocDB Global Cluster (%s) availability", globalClusterID) + _, err := stateConf.WaitForStateContext(ctx) + + return err +} + +func waitForDocDBGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{"modifying", "upgrading"}, + Target: []string{"available"}, + Refresh: docDBGlobalClusterRefreshFunc(ctx, conn, globalClusterID), + Timeout: 10 * time.Minute, + Delay: 30 * time.Second, + } + + log.Printf("[DEBUG] Waiting for DocDB Global Cluster (%s) availability", globalClusterID) + _, err := stateConf.WaitForStateContext(ctx) + + return err +} + +func waitForDocDBGlobalClusterDeletion(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + "available", + "deleting", + }, + Target: []string{"deleted"}, + Refresh: docDBGlobalClusterRefreshFunc(ctx, conn, globalClusterID), + Timeout: 10 * time.Minute, + NotFoundChecks: 1, + } + + log.Printf("[DEBUG] Waiting for DocDB Global Cluster (%s) deletion", globalClusterID) + _, err := stateConf.WaitForStateContext(ctx) + + if isResourceNotFoundError(err) { + return nil + } + + return err +} + +func waitForDocDBGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClusterIdentifier string) error { + var globalCluster *docdb.GlobalCluster + stillExistsErr := fmt.Errorf("DocDB Cluster still exists in DocDB Global Cluster") + + err := resource.RetryContext(ctx, docDBGlobalClusterRemovalTimeout, func() *resource.RetryError { + var err error + + globalCluster, err = docDBDescribeGlobalClusterFromDbClusterARN(ctx, conn, dbClusterIdentifier) + + if err != nil { + return resource.NonRetryableError(err) + } + + if globalCluster != nil { + return resource.RetryableError(stillExistsErr) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = docDBDescribeGlobalClusterFromDbClusterARN(ctx, conn, dbClusterIdentifier) + } + + if err != nil { + return err + } + + if globalCluster != nil { + return stillExistsErr + } + + return nil +} + +// Updating major versions is not supported by documentDB +// To support minor version upgrades, we will upgrade all cluster members +func resourceAwsDocDBGlobalClusterUpgradeEngineVersion(ctx context.Context, d *schema.ResourceData, conn *docdb.DocDB) error { + log.Printf("[DEBUG] Upgrading DocDB Global Cluster (%s) engine version: %s", d.Id(), d.Get("engine_version")) + err := resourceAwsDocDBGlobalClusterUpgradeMinorEngineVersion(ctx, d.Get("global_cluster_members").(*schema.Set), d.Get("engine_version").(string), conn) + if err != nil { + return err + } + globalCluster, err := docDBDescribeGlobalCluster(ctx, conn, d.Id()) + if err != nil { + return err + } + for _, clusterMember := range globalCluster.GlobalClusterMembers { + err := waitForDocDBClusterUpdate(conn, resourceAwsDocDBGlobalClusterGetIdByArn(ctx, conn, aws.StringValue(clusterMember.DBClusterArn)), d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return err + } + } + return nil +} + +func resourceAwsDocDBGlobalClusterGetIdByArn(ctx context.Context, conn *docdb.DocDB, arn string) string { + result, err := conn.DescribeDBClustersWithContext(ctx, &docdb.DescribeDBClustersInput{}) + if err != nil { + return "" + } + for _, cluster := range result.DBClusters { + if aws.StringValue(cluster.DBClusterArn) == arn { + return aws.StringValue(cluster.DBClusterIdentifier) + } + } + return "" +} + +func resourceAwsDocDBGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, clusterMembers *schema.Set, engineVersion string, conn *docdb.DocDB) error { + for _, clusterMemberRaw := range clusterMembers.List() { + clusterMember := clusterMemberRaw.(map[string]interface{}) + if clusterMemberArn, ok := clusterMember["db_cluster_arn"]; ok && clusterMemberArn.(string) != "" { + modInput := &docdb.ModifyDBClusterInput{ + ApplyImmediately: aws.Bool(true), + DBClusterIdentifier: aws.String(clusterMemberArn.(string)), + EngineVersion: aws.String(engineVersion), + } + err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { + _, err := conn.ModifyDBClusterWithContext(ctx, modInput) + if err != nil { + if isAWSErr(err, "InvalidParameterValue", "IAM role ARN value is invalid or does not include the required permissions") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if tfresource.TimedOut(err) { + _, err := conn.ModifyDBClusterWithContext(ctx, modInput) + if err != nil { + return err + } + } + if err != nil { + return fmt.Errorf("failed to update engine_version on global cluster member (%s): %w", clusterMemberArn, err) + } + } + } + return nil +} diff --git a/aws/resource_aws_docdb_global_cluster_test.go b/aws/resource_aws_docdb_global_cluster_test.go new file mode 100644 index 00000000000..d21a1856330 --- /dev/null +++ b/aws/resource_aws_docdb_global_cluster_test.go @@ -0,0 +1,567 @@ +package aws + +import ( + "context" + "errors" + "fmt" + "github.com/aws/aws-sdk-go/service/docdb" + "log" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("aws_docdb_global_cluster", &resource.Sweeper{ + Name: "aws_docdb_global_cluster", + F: testSweepDocDBGlobalClusters, + Dependencies: []string{ + "aws_docdb_cluster", + }, + }) +} + +func testSweepDocDBGlobalClusters(region string) error { + client, err := sharedClientForRegion(region) + + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + + conn := client.(*AWSClient).docdbconn + input := &docdb.DescribeGlobalClustersInput{} + + err = conn.DescribeGlobalClustersPages(input, func(out *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { + for _, globalCluster := range out.GlobalClusters { + id := aws.StringValue(globalCluster.GlobalClusterIdentifier) + input := &docdb.DeleteGlobalClusterInput{ + GlobalClusterIdentifier: globalCluster.GlobalClusterIdentifier, + } + + log.Printf("[INFO] Deleting DocDB Global Cluster: %s", id) + + _, err := conn.DeleteGlobalCluster(input) + + if err != nil { + log.Printf("[ERROR] Failed to delete DocDB Global Cluster (%s): %s", id, err) + continue + } + + if err := waitForDocDBGlobalClusterDeletion(context.TODO(), conn, id); err != nil { + log.Printf("[ERROR] Failure while waiting for DocDB Global Cluster (%s) to be deleted: %s", id, err) + } + } + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DocDB Global Cluster sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error retrieving DocDB Global Clusters: %w", err) + } + + return nil +} + +func TestAccAWSDocDBGlobalCluster_basic(t *testing.T) { + var globalCluster1 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + //This is a rds arn + testAccCheckResourceAttrGlobalARN(resourceName, "arn", "rds", fmt.Sprintf("global-cluster:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "database_name", ""), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "false"), + resource.TestCheckResourceAttrSet(resourceName, "engine"), + resource.TestCheckResourceAttrSet(resourceName, "engine_version"), + resource.TestCheckResourceAttr(resourceName, "global_cluster_identifier", rName), + resource.TestMatchResourceAttr(resourceName, "global_cluster_resource_id", regexp.MustCompile(`cluster-.+`)), + resource.TestCheckResourceAttr(resourceName, "storage_encrypted", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_disappears(t *testing.T) { + var globalCluster1 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + testAccCheckAWSDocDBGlobalClusterDisappears(&globalCluster1), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_DatabaseName(t *testing.T) { + var globalCluster1, globalCluster2 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigDatabaseName(rName, "database1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttr(resourceName, "database_name", "database1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDocDBGlobalClusterConfigDatabaseName(rName, "database2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster2), + testAccCheckAWSDocDBGlobalClusterRecreated(&globalCluster1, &globalCluster2), + resource.TestCheckResourceAttr(resourceName, "database_name", "database2"), + ), + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_DeletionProtection(t *testing.T) { + var globalCluster1, globalCluster2 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigDeletionProtection(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDocDBGlobalClusterConfigDeletionProtection(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster2), + testAccCheckAWSDocDBGlobalClusterNotRecreated(&globalCluster1, &globalCluster2), + resource.TestCheckResourceAttr(resourceName, "deletion_protection", "false"), + ), + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_Engine(t *testing.T) { + var globalCluster1 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigEngine(rName, "docdb"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttr(resourceName, "engine", "docdb"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_EngineVersion(t *testing.T) { + var globalCluster1 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigEngineVersion(rName, "docdb", "4.0.0"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttr(resourceName, "engine_version", "4.0.0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_SourceDbClusterIdentifier(t *testing.T) { + var globalCluster1 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + clusterResourceName := "aws_docdb_cluster.test" + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigSourceDbClusterIdentifier(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttrPair(resourceName, "source_db_cluster_identifier", clusterResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy", "source_db_cluster_identifier"}, + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_SourceDbClusterIdentifier_StorageEncrypted(t *testing.T) { + var globalCluster1 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + clusterResourceName := "aws_docdb_cluster.test" + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigSourceDbClusterIdentifierStorageEncrypted(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttrPair(resourceName, "source_db_cluster_identifier", clusterResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy", "source_db_cluster_identifier"}, + }, + }, + }) +} + +func TestAccAWSDocDBGlobalCluster_StorageEncrypted(t *testing.T) { + var globalCluster1, globalCluster2 docdb.GlobalCluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_docdb_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDocDBGlobalCluster(t) }, + ErrorCheck: testAccErrorCheck(t, docdb.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDocDBGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDocDBGlobalClusterConfigStorageEncrypted(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster1), + resource.TestCheckResourceAttr(resourceName, "storage_encrypted", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSDocDBGlobalClusterConfigStorageEncrypted(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDocDBGlobalClusterExists(resourceName, &globalCluster2), + testAccCheckAWSDocDBGlobalClusterRecreated(&globalCluster1, &globalCluster2), + resource.TestCheckResourceAttr(resourceName, "storage_encrypted", "false"), + ), + }, + }, + }) +} + +func testAccCheckAWSDocDBGlobalClusterExists(resourceName string, globalCluster *docdb.GlobalCluster) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no DocDB Global Cluster ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).docdbconn + + cluster, err := docDBDescribeGlobalCluster(context.TODO(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + if cluster == nil { + return fmt.Errorf("docDB Global Cluster not found") + } + + if aws.StringValue(cluster.Status) != "available" { + return fmt.Errorf("docDB Global Cluster (%s) exists in non-available (%s) state", rs.Primary.ID, aws.StringValue(cluster.Status)) + } + + *globalCluster = *cluster + + return nil + } +} + +func testAccCheckAWSDocDBGlobalClusterDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).docdbconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_docdb_global_cluster" { + continue + } + + globalCluster, err := docDBDescribeGlobalCluster(context.TODO(), conn, rs.Primary.ID) + + if isAWSErr(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { + continue + } + + if err != nil { + return err + } + + if globalCluster == nil { + continue + } + + return fmt.Errorf("docDB Global Cluster (%s) still exists in non-deleted (%s) state", rs.Primary.ID, aws.StringValue(globalCluster.Status)) + } + + return nil +} + +func testAccCheckAWSDocDBGlobalClusterDisappears(globalCluster *docdb.GlobalCluster) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).docdbconn + + input := &docdb.DeleteGlobalClusterInput{ + GlobalClusterIdentifier: globalCluster.GlobalClusterIdentifier, + } + + _, err := conn.DeleteGlobalCluster(input) + + if err != nil { + return err + } + + return waitForDocDBGlobalClusterDeletion(context.TODO(), conn, aws.StringValue(globalCluster.GlobalClusterIdentifier)) + } +} + +func testAccCheckAWSDocDBGlobalClusterNotRecreated(i, j *docdb.GlobalCluster) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(i.GlobalClusterArn) != aws.StringValue(j.GlobalClusterArn) { + return fmt.Errorf("docDB Global Cluster was recreated. got: %s, expected: %s", aws.StringValue(i.GlobalClusterArn), aws.StringValue(j.GlobalClusterArn)) + } + + return nil + } +} + +func testAccCheckAWSDocDBGlobalClusterRecreated(i, j *docdb.GlobalCluster) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(i.GlobalClusterResourceId) == aws.StringValue(j.GlobalClusterResourceId) { + return errors.New("docDB Global Cluster was not recreated") + } + + return nil + } +} + +func testAccPreCheckAWSDocDBGlobalCluster(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).docdbconn + + input := &docdb.DescribeGlobalClustersInput{} + + _, err := conn.DescribeGlobalClusters(input) + + if testAccPreCheckSkipError(err) || isAWSErr(err, "InvalidParameterValue", "Access Denied to API Version: APIGlobalDatabases") { + // Current Region/Partition does not support DocDB Global Clusters + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccAWSDocDBGlobalClusterConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + global_cluster_identifier = %q +} +`, rName) +} + +func testAccAWSDocDBGlobalClusterConfigDatabaseName(rName, databaseName string) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + database_name = %q + global_cluster_identifier = %q +} +`, databaseName, rName) +} + +func testAccAWSDocDBGlobalClusterConfigDeletionProtection(rName string, deletionProtection bool) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + deletion_protection = %t + global_cluster_identifier = %q +} +`, deletionProtection, rName) +} + +func testAccAWSDocDBGlobalClusterConfigEngine(rName, engine string) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + engine = %q + global_cluster_identifier = %q +} +`, engine, rName) +} + +func testAccAWSDocDBGlobalClusterConfigEngineVersion(rName, engine, engineVersion string) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + engine = %q + engine_version = %q + global_cluster_identifier = %q +} +`, engine, engineVersion, rName) +} + +func testAccAWSDocDBGlobalClusterConfigSourceDbClusterIdentifier(rName string) string { + return fmt.Sprintf(` +resource "aws_docdb_cluster" "test" { + cluster_identifier = %[1]q + engine = "docdb" + engine_version = "4.0.0" # Minimum supported version for Global Clusters + master_password = "mustbeeightcharacters" + master_username = "test" + skip_final_snapshot = true + + # global_cluster_identifier cannot be Computed + + lifecycle { + ignore_changes = [global_cluster_identifier] + } +} + +resource "aws_docdb_global_cluster" "test" { + force_destroy = true + global_cluster_identifier = %[1]q + source_db_cluster_identifier = aws_docdb_cluster.test.arn +} +`, rName) +} + +func testAccAWSDocDBGlobalClusterConfigSourceDbClusterIdentifierStorageEncrypted(rName string) string { + return fmt.Sprintf(` +resource "aws_docdb_cluster" "test" { + cluster_identifier = %[1]q + engine = "docdb" + engine_version = "4.0.0" # Minimum supported version for Global Clusters + master_password = "mustbeeightcharacters" + master_username = "test" + skip_final_snapshot = true + storage_encrypted = true + + # global_cluster_identifier cannot be Computed + + lifecycle { + ignore_changes = [global_cluster_identifier] + } +} + +resource "aws_docdb_global_cluster" "test" { + force_destroy = true + global_cluster_identifier = %[1]q + source_db_cluster_identifier = aws_docdb_cluster.test.arn +} +`, rName) +} + +func testAccAWSDocDBGlobalClusterConfigStorageEncrypted(rName string, storageEncrypted bool) string { + return fmt.Sprintf(` +resource "aws_docdb_global_cluster" "test" { + global_cluster_identifier = %q + storage_encrypted = %t +} +`, rName, storageEncrypted) +} diff --git a/website/docs/r/docdb_cluster.html.markdown b/website/docs/r/docdb_cluster.html.markdown index 37860e00363..4315a34613b 100644 --- a/website/docs/r/docdb_cluster.html.markdown +++ b/website/docs/r/docdb_cluster.html.markdown @@ -60,6 +60,7 @@ The following arguments are supported: * `final_snapshot_identifier` - (Optional) The name of your final DB snapshot when this DB cluster is deleted. If omitted, no final snapshot will be made. +* `global_cluster_identifier` - (Optional) The global cluster identifier specified on [`aws_docdb_global_cluster`](/docs/providers/aws/r/docdb_global_cluster.html). * `kms_key_id` - (Optional) The ARN for the KMS encryption key. When specifying `kms_key_id`, `storage_encrypted` needs to be set to true. * `master_password` - (Required unless a `snapshot_identifier` is provided) Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file. Please refer to the DocDB Naming Constraints. diff --git a/website/docs/r/docdb_global_cluster.html.markdown b/website/docs/r/docdb_global_cluster.html.markdown new file mode 100644 index 00000000000..5d2bdd0185e --- /dev/null +++ b/website/docs/r/docdb_global_cluster.html.markdown @@ -0,0 +1,148 @@ +--- +subcategory: "DocumentDB" +layout: "aws" +page_title: "AWS: aws_docdb" +description: |- + Manages a DocDB Global Cluster +--- + +# Resource: aws_docdb_global_cluster + +Manages an DocumentDB Global Cluster, which is an DocumentDB global database spread across multiple regions. The global database contains a single primary cluster with read-write capability, and a read-only secondary cluster that receives data from the primary cluster. + +More information about DocumentDB global databases can be found in the [DocumentDB Developer Guide](https://docs.aws.amazon.com/documentdb/latest/developerguide/global-clusters.html). + +## Example Usage + +### New DocumentDB Global Cluster + +```terraform +provider "aws" { + alias = "primary" + region = "us-east-2" +} + +provider "aws" { + alias = "secondary" + region = "us-east-1" +} + +resource "aws_docdb_global_cluster" "example" { + global_cluster_identifier = "global-test" + engine = "docdb" + engine_version = "4.0.0" +} + +resource "aws_docdb_cluster" "primary" { + provider = aws.primary + engine = aws_docdb_global_cluster.example.engine + engine_version = aws_docdb_global_cluster.example.engine_version + cluster_identifier = "test-primary-cluster" + master_username = "username" + master_password = "somepass123" + global_cluster_identifier = aws_docdb_global_cluster.example.id + db_subnet_group_name = "default" +} + +resource "aws_docdb_cluster_instance" "primary" { + provider = aws.primary + engine = aws_docdb_global_cluster.example.engine + engine_version = aws_docdb_global_cluster.example.engine_version + identifier = "test-primary-cluster-instance" + cluster_identifier = aws_docdb_cluster.primary.id + instance_class = "db.r5.large" + db_subnet_group_name = "default" +} + +resource "aws_docdb_cluster" "secondary" { + provider = aws.secondary + engine = aws_docdb_global_cluster.example.engine + engine_version = aws_docdb_global_cluster.example.engine_version + cluster_identifier = "test-secondary-cluster" + global_cluster_identifier = aws_docdb_global_cluster.example.id + db_subnet_group_name = "default" +} + +resource "aws_docdb_cluster_instance" "secondary" { + provider = aws.secondary + engine = aws_docdb_global_cluster.example.engine + engine_version = aws_docdb_global_cluster.example.engine_version + identifier = "test-secondary-cluster-instance" + cluster_identifier = aws_docdb_cluster.secondary.id + instance_class = "db.r5.large" + db_subnet_group_name = "default" + + depends_on = [ + aws_docdb_cluster_instance.primary + ] +} +``` + +### New Global Cluster From Existing DB Cluster + +```terraform +resource "aws_docdb_cluster" "example" { + # ... other configuration ... + + # NOTE: Using this DB Cluster to create a Global Cluster, the + # global_cluster_identifier attribute will become populated and + # Terraform will begin showing it as a difference. Do not configure: + # global_cluster_identifier = aws_docdb_global_cluster.example.id + # as it creates a circular reference. Use ignore_changes instead. + lifecycle { + ignore_changes = [global_cluster_identifier] + } +} + +resource "aws_docdb_global_cluster" "example" { + force_destroy = true + global_cluster_identifier = "example" + source_db_cluster_identifier = aws_docdb_cluster.example.arn +} +``` + +## Argument Reference + +The following arguments are supported: + +* `global_cluster_identifier` - (Required, Forces new resources) The global cluster identifier. +* `deletion_protection` - (Optional) If the Global Cluster should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`. +* `engine` - (Optional, Forces new resources) Name of the database engine to be used for this DB cluster. Terraform will only perform drift detection if a configuration value is provided. Current Valid values: `docdb`. Defaults to `docdb`. Conflicts with `source_db_cluster_identifier`. +* `engine_version` - (Optional) Engine version of the global database. Upgrading the engine version will result in all cluster members being immediately updated. + * **NOTE:** Upgrading major versions is not supported. +* `force_destroy` - (Optional) Enable to remove DB Cluster members from Global Cluster on destroy. Required with `source_db_cluster_identifier`. +* `source_db_cluster_identifier` - (Optional) Amazon Resource Name (ARN) to use as the primary DB Cluster of the Global Cluster on creation. Terraform cannot perform drift detection of this value. +* `storage_encrypted` - (Optional, Forces new resources) Specifies whether the DB cluster is encrypted. The default is `false` unless `source_db_cluster_identifier` is specified and encrypted. Terraform will only perform drift detection if a configuration value is provided. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Global Cluster Amazon Resource Name (ARN) +* `global_cluster_members` - Set of objects containing Global Cluster members. + * `db_cluster_arn` - Amazon Resource Name (ARN) of member DB Cluster + * `is_writer` - Whether the member is the primary DB Cluster +* `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the global database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed +* `id` - DocDB Global Cluster identifier + +## Import + +`aws_docdb_global_cluster` can be imported by using the Global Cluster identifier, e.g. + +``` +$ terraform import aws_docdb_global_cluster.example example +``` + +Certain resource arguments, like `force_destroy`, only exist within Terraform. If the argument is set in the Terraform configuration on an imported resource, Terraform will show a difference on the first plan after import to update the state value. This change is safe to apply immediately so the state matches the desired configuration. +Certain resource arguments, like `source_db_cluster_identifier`, do not have an API method for reading the information after creation. If the argument is set in the Terraform configuration on an imported resource, Terraform will always show a difference. To workaround this behavior, either omit the argument from the Terraform configuration or use [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to hide the difference, e.g. + +```terraform +resource "aws_docdb_global_cluster" "example" { + # ... other configuration ... + + # There is no API for reading source_db_cluster_identifier + lifecycle { + ignore_changes = [source_db_cluster_identifier] + } +} +``` From f24ad81a6b0bb27a643df8070834fd8d839556af Mon Sep 17 00:00:00 2001 From: "snellman.lars" Date: Tue, 21 Sep 2021 17:40:06 -0400 Subject: [PATCH 02/31] Improved the documentation surrounding the master_password and master_username argument for the aws_docdb_cluster resource. --- website/docs/r/docdb_cluster.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/docdb_cluster.html.markdown b/website/docs/r/docdb_cluster.html.markdown index 4315a34613b..ed35bcbe82e 100644 --- a/website/docs/r/docdb_cluster.html.markdown +++ b/website/docs/r/docdb_cluster.html.markdown @@ -62,9 +62,9 @@ The following arguments are supported: made. * `global_cluster_identifier` - (Optional) The global cluster identifier specified on [`aws_docdb_global_cluster`](/docs/providers/aws/r/docdb_global_cluster.html). * `kms_key_id` - (Optional) The ARN for the KMS encryption key. When specifying `kms_key_id`, `storage_encrypted` needs to be set to true. -* `master_password` - (Required unless a `snapshot_identifier` is provided) Password for the master DB user. Note that this may +* `master_password` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file. Please refer to the DocDB Naming Constraints. -* `master_username` - (Required unless a `snapshot_identifier` is provided) Username for the master DB user. +* `master_username` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Username for the master DB user. * `port` - (Optional) The port on which the DB accepts connections * `preferred_backup_window` - (Optional) The daily time range during which automated backups are created if automated backups are enabled using the BackupRetentionPeriod parameter.Time in UTC Default: A 30-minute window selected at random from an 8-hour block of time per region. e.g. 04:00-09:00 From 795413d01ef4184fbb0be980a149a43b436fade1 Mon Sep 17 00:00:00 2001 From: "snellman.lars" Date: Tue, 21 Sep 2021 18:14:03 -0400 Subject: [PATCH 03/31] added changelog --- .changelog/20978.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/20978.txt diff --git a/.changelog/20978.txt b/.changelog/20978.txt new file mode 100644 index 00000000000..400b93c0cd9 --- /dev/null +++ b/.changelog/20978.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_docdb_global_cluster +``` + +```release-note:enhancement +resource/aws_docdb_cluster: Add global_cluster_identifier argument +``` \ No newline at end of file From ef2474d3e561c5247b633ca62e8d9512b7386549 Mon Sep 17 00:00:00 2001 From: "snellman.lars" Date: Tue, 21 Sep 2021 18:19:24 -0400 Subject: [PATCH 04/31] added changelog --- .changelog/20978.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/20978.txt diff --git a/.changelog/20978.txt b/.changelog/20978.txt new file mode 100644 index 00000000000..d9dfdeef605 --- /dev/null +++ b/.changelog/20978.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_docdb_global_cluster +``` + +```release-note:enhancement +resource/aws_docdb_cluster: Add global_cluster_identifier argument +``` From 5373b58c9fe8022f63b3e8bc4e926df5fadb8878 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Sat, 16 Oct 2021 18:37:46 -0400 Subject: [PATCH 05/31] updated attribute validation, removed force_delete (deletion_protection provides protection) --- aws/resource_aws_docdb_cluster.go | 4 +++- aws/resource_aws_docdb_global_cluster.go | 22 +++++++++------------- aws/validators.go | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_docdb_cluster.go b/aws/resource_aws_docdb_cluster.go index 8a2ab1c6012..f15c90e505e 100644 --- a/aws/resource_aws_docdb_cluster.go +++ b/aws/resource_aws_docdb_cluster.go @@ -4,12 +4,13 @@ import ( "context" "errors" "fmt" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" "log" "regexp" "strings" "time" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -96,6 +97,7 @@ func resourceAwsDocDBCluster() *schema.Resource { "global_cluster_identifier": { Type: schema.TypeString, Optional: true, + ValidateFunc: validateDocDBGlobalCusterIdentifier, }, "reader_endpoint": { diff --git a/aws/resource_aws_docdb_global_cluster.go b/aws/resource_aws_docdb_global_cluster.go index 0aa68a85ef3..afc8f3a8029 100644 --- a/aws/resource_aws_docdb_global_cluster.go +++ b/aws/resource_aws_docdb_global_cluster.go @@ -3,17 +3,17 @@ package aws import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" "log" "time" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) const ( @@ -51,23 +51,18 @@ func resourceAwsDocDBGlobalCluster() *schema.Resource { Computed: true, ForceNew: true, ConflictsWith: []string{"source_db_cluster_identifier"}, - ValidateFunc: validation.StringInSlice([]string{ - "docdb", - }, false), + ValidateFunc: validateDocDBEngine(), }, "engine_version": { Type: schema.TypeString, Optional: true, Computed: true, }, - "force_destroy": { - Type: schema.TypeBool, - Optional: true, - }, "global_cluster_identifier": { Type: schema.TypeString, Required: true, ForceNew: true, + ValidateFunc: validateDocDBGlobalCusterIdentifier, }, "global_cluster_members": { Type: schema.TypeSet, @@ -95,7 +90,10 @@ func resourceAwsDocDBGlobalCluster() *schema.Resource { Computed: true, ForceNew: true, ConflictsWith: []string{"engine"}, - RequiredWith: []string{"force_destroy"}, + }, + "status": { + Type: schema.TypeString, + Computed: true, }, "storage_encrypted": { Type: schema.TypeBool, @@ -239,7 +237,6 @@ func resourceAwsDocDBGlobalClusterUpdate(ctx context.Context, d *schema.Resource func resourceAwsDocDBGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).docdbconn - if d.Get("force_destroy").(bool) { for _, globalClusterMemberRaw := range d.Get("global_cluster_members").(*schema.Set).List() { globalClusterMember, ok := globalClusterMemberRaw.(map[string]interface{}) @@ -272,7 +269,6 @@ func resourceAwsDocDBGlobalClusterDelete(ctx context.Context, d *schema.Resource return diag.FromErr(fmt.Errorf("error waiting for DocDB Cluster (%s) removal from DocDB Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) } } - } input := &docdb.DeleteGlobalClusterInput{ GlobalClusterIdentifier: aws.String(d.Id()), diff --git a/aws/validators.go b/aws/validators.go index 4a4b2bf416e..d7b4a30a6a9 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -268,6 +268,28 @@ func validateDbParamGroupNamePrefix(v interface{}, k string) (ws []string, error return } +func validateDocDBGlobalCusterIdentifier(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only alphanumeric characters and hyphens allowed in %q", k)) + } + if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "first character of %q must be a letter", k)) + } + if regexp.MustCompile(`--`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot contain two consecutive hyphens", k)) + } + if len(value) > 255 { + errors = append(errors, fmt.Errorf( + "%q cannot be greater than 255 characters", k)) + } + return +} + + func validateDocDBIdentifier(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { From dd116b7054428f5bab0bc538aa75aed925f094c1 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Sat, 16 Oct 2021 18:40:28 -0400 Subject: [PATCH 06/31] removed force_destroy as deletion_protection is enough. --- aws/resource_aws_docdb_global_cluster_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_docdb_global_cluster_test.go b/aws/resource_aws_docdb_global_cluster_test.go index d21a1856330..b98464f330a 100644 --- a/aws/resource_aws_docdb_global_cluster_test.go +++ b/aws/resource_aws_docdb_global_cluster_test.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" - "github.com/aws/aws-sdk-go/service/docdb" "log" "regexp" "testing" + "github.com/aws/aws-sdk-go/service/docdb" + "github.com/aws/aws-sdk-go/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -275,7 +276,7 @@ func TestAccAWSDocDBGlobalCluster_SourceDbClusterIdentifier(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"force_destroy", "source_db_cluster_identifier"}, + ImportStateVerifyIgnore: []string{"source_db_cluster_identifier"}, }, }, }) @@ -304,7 +305,7 @@ func TestAccAWSDocDBGlobalCluster_SourceDbClusterIdentifier_StorageEncrypted(t * ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"force_destroy", "source_db_cluster_identifier"}, + ImportStateVerifyIgnore: []string{"source_db_cluster_identifier"}, }, }, }) @@ -524,7 +525,6 @@ resource "aws_docdb_cluster" "test" { } resource "aws_docdb_global_cluster" "test" { - force_destroy = true global_cluster_identifier = %[1]q source_db_cluster_identifier = aws_docdb_cluster.test.arn } @@ -550,7 +550,6 @@ resource "aws_docdb_cluster" "test" { } resource "aws_docdb_global_cluster" "test" { - force_destroy = true global_cluster_identifier = %[1]q source_db_cluster_identifier = aws_docdb_cluster.test.arn } From d0dc4c212744885eb8a60b8837bcb947cb46efcd Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Sun, 17 Oct 2021 09:28:38 -0400 Subject: [PATCH 07/31] formatting files --- aws/resource_aws_docdb_cluster.go | 6 +-- aws/resource_aws_docdb_global_cluster.go | 56 ++++++++++++------------ aws/validators.go | 1 - 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/aws/resource_aws_docdb_cluster.go b/aws/resource_aws_docdb_cluster.go index f15c90e505e..0246e56b7d7 100644 --- a/aws/resource_aws_docdb_cluster.go +++ b/aws/resource_aws_docdb_cluster.go @@ -95,9 +95,9 @@ func resourceAwsDocDBCluster() *schema.Resource { }, "global_cluster_identifier": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateDocDBGlobalCusterIdentifier, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateDocDBGlobalCusterIdentifier, }, "reader_endpoint": { diff --git a/aws/resource_aws_docdb_global_cluster.go b/aws/resource_aws_docdb_global_cluster.go index afc8f3a8029..44dd2e62f4c 100644 --- a/aws/resource_aws_docdb_global_cluster.go +++ b/aws/resource_aws_docdb_global_cluster.go @@ -51,7 +51,7 @@ func resourceAwsDocDBGlobalCluster() *schema.Resource { Computed: true, ForceNew: true, ConflictsWith: []string{"source_db_cluster_identifier"}, - ValidateFunc: validateDocDBEngine(), + ValidateFunc: validateDocDBEngine(), }, "engine_version": { Type: schema.TypeString, @@ -59,10 +59,10 @@ func resourceAwsDocDBGlobalCluster() *schema.Resource { Computed: true, }, "global_cluster_identifier": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validateDocDBGlobalCusterIdentifier, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateDocDBGlobalCusterIdentifier, }, "global_cluster_members": { Type: schema.TypeSet, @@ -237,38 +237,38 @@ func resourceAwsDocDBGlobalClusterUpdate(ctx context.Context, d *schema.Resource func resourceAwsDocDBGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).docdbconn - for _, globalClusterMemberRaw := range d.Get("global_cluster_members").(*schema.Set).List() { - globalClusterMember, ok := globalClusterMemberRaw.(map[string]interface{}) + for _, globalClusterMemberRaw := range d.Get("global_cluster_members").(*schema.Set).List() { + globalClusterMember, ok := globalClusterMemberRaw.(map[string]interface{}) - if !ok { - continue - } + if !ok { + continue + } - dbClusterArn, ok := globalClusterMember["db_cluster_arn"].(string) + dbClusterArn, ok := globalClusterMember["db_cluster_arn"].(string) - if !ok { - continue - } + if !ok { + continue + } - input := &docdb.RemoveFromGlobalClusterInput{ - DbClusterIdentifier: aws.String(dbClusterArn), - GlobalClusterIdentifier: aws.String(d.Id()), - } + input := &docdb.RemoveFromGlobalClusterInput{ + DbClusterIdentifier: aws.String(dbClusterArn), + GlobalClusterIdentifier: aws.String(d.Id()), + } - _, err := conn.RemoveFromGlobalClusterWithContext(ctx, input) + _, err := conn.RemoveFromGlobalClusterWithContext(ctx, input) - if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "is not found in global cluster") { - continue - } + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "is not found in global cluster") { + continue + } - if err != nil { - return diag.FromErr(fmt.Errorf("error removing DocDB Cluster (%s) from Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) - } + if err != nil { + return diag.FromErr(fmt.Errorf("error removing DocDB Cluster (%s) from Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) + } - if err := waitForDocDBGlobalClusterRemoval(ctx, conn, dbClusterArn); err != nil { - return diag.FromErr(fmt.Errorf("error waiting for DocDB Cluster (%s) removal from DocDB Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) - } + if err := waitForDocDBGlobalClusterRemoval(ctx, conn, dbClusterArn); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for DocDB Cluster (%s) removal from DocDB Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) } + } input := &docdb.DeleteGlobalClusterInput{ GlobalClusterIdentifier: aws.String(d.Id()), diff --git a/aws/validators.go b/aws/validators.go index d7b4a30a6a9..8d1b806239e 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -289,7 +289,6 @@ func validateDocDBGlobalCusterIdentifier(v interface{}, k string) (ws []string, return } - func validateDocDBIdentifier(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { From eed50f7adda0aee658b2d6e2327eba39b8666d0e Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Sun, 17 Oct 2021 16:16:58 -0400 Subject: [PATCH 08/31] modified attribute validation. --- aws/resource_aws_docdb_global_cluster.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_docdb_global_cluster.go b/aws/resource_aws_docdb_global_cluster.go index 44dd2e62f4c..66fdb3fecbe 100644 --- a/aws/resource_aws_docdb_global_cluster.go +++ b/aws/resource_aws_docdb_global_cluster.go @@ -50,6 +50,7 @@ func resourceAwsDocDBGlobalCluster() *schema.Resource { Optional: true, Computed: true, ForceNew: true, + AtLeastOneOf: []string{"engine", "source_db_cluster_identifier"}, ConflictsWith: []string{"source_db_cluster_identifier"}, ValidateFunc: validateDocDBEngine(), }, @@ -89,6 +90,7 @@ func resourceAwsDocDBGlobalCluster() *schema.Resource { Optional: true, Computed: true, ForceNew: true, + AtLeastOneOf: []string{"engine", "source_db_cluster_identifier"}, ConflictsWith: []string{"engine"}, }, "status": { @@ -136,13 +138,6 @@ func resourceAwsDocDBGlobalClusterCreate(ctx context.Context, d *schema.Resource input.StorageEncrypted = aws.Bool(v.(bool)) } - // Prevent the following error and keep the previous default, - // since we cannot have Engine default after adding SourceDBClusterIdentifier: - // InvalidParameterValue: When creating standalone global cluster, value for engineName should be specified - if input.Engine == nil && input.SourceDBClusterIdentifier == nil { - input.Engine = aws.String("docdb") - } - output, err := conn.CreateGlobalClusterWithContext(ctx, input) if err != nil { return diag.FromErr(fmt.Errorf("error creating DocDB Global Cluster: %w", err)) From c495a4b7e981ebc7ba5145a341cf289e1179c330 Mon Sep 17 00:00:00 2001 From: "snellman.lars" Date: Tue, 26 Oct 2021 02:08:43 -0400 Subject: [PATCH 09/31] Fixes failing tests and updates the docdb global documentation to include information on the argument database_name --- aws/resource_aws_docdb_cluster_test.go | 6 ++---- aws/resource_aws_docdb_global_cluster_test.go | 3 +++ website/docs/r/docdb_global_cluster.html.markdown | 10 ++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/aws/resource_aws_docdb_cluster_test.go b/aws/resource_aws_docdb_cluster_test.go index 5b86e6975ea..4e5cdb2f3f6 100644 --- a/aws/resource_aws_docdb_cluster_test.go +++ b/aws/resource_aws_docdb_cluster_test.go @@ -750,7 +750,7 @@ func testAccAWSDocDBClusterConfigGlobalClusterIdentifier_Update(rName, globalClu return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { count = 2 - + engine = "docdb" engine_version = "4.0.0" # version compatible with global global_cluster_identifier = "%[1]s-${count.index}" } @@ -778,14 +778,12 @@ resource "aws_docdb_cluster" "test" { `, rName) } -// engine_version = "4.0.0" # version compatible -// force_destroy = true # Partial configuration removal ordering fix for after Terraform 0.12 func testAccAWSDocDBClusterConfigGlobalClusterIdentifier(rName string) string { return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { engine_version = "4.0.0" # version compatible + engine = "docdb" global_cluster_identifier = %[1]q - force_destroy = true # Partial configuration removal ordering fix for after Terraform 0.12 } resource "aws_docdb_cluster" "test" { diff --git a/aws/resource_aws_docdb_global_cluster_test.go b/aws/resource_aws_docdb_global_cluster_test.go index b98464f330a..d719f935071 100644 --- a/aws/resource_aws_docdb_global_cluster_test.go +++ b/aws/resource_aws_docdb_global_cluster_test.go @@ -465,6 +465,7 @@ func testAccPreCheckAWSDocDBGlobalCluster(t *testing.T) { func testAccAWSDocDBGlobalClusterConfig(rName string) string { return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { + engine = "docdb" global_cluster_identifier = %q } `, rName) @@ -473,6 +474,7 @@ resource "aws_docdb_global_cluster" "test" { func testAccAWSDocDBGlobalClusterConfigDatabaseName(rName, databaseName string) string { return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { + engine = "docdb" database_name = %q global_cluster_identifier = %q } @@ -482,6 +484,7 @@ resource "aws_docdb_global_cluster" "test" { func testAccAWSDocDBGlobalClusterConfigDeletionProtection(rName string, deletionProtection bool) string { return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { + engine = "docdb" deletion_protection = %t global_cluster_identifier = %q } diff --git a/website/docs/r/docdb_global_cluster.html.markdown b/website/docs/r/docdb_global_cluster.html.markdown index 5d2bdd0185e..419c8063e14 100644 --- a/website/docs/r/docdb_global_cluster.html.markdown +++ b/website/docs/r/docdb_global_cluster.html.markdown @@ -95,7 +95,6 @@ resource "aws_docdb_cluster" "example" { } resource "aws_docdb_global_cluster" "example" { - force_destroy = true global_cluster_identifier = "example" source_db_cluster_identifier = aws_docdb_cluster.example.arn } @@ -106,12 +105,12 @@ resource "aws_docdb_global_cluster" "example" { The following arguments are supported: * `global_cluster_identifier` - (Required, Forces new resources) The global cluster identifier. +* `database_name` - (Optional, Forces new resources) Name for an automatically created database on cluster creation. * `deletion_protection` - (Optional) If the Global Cluster should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`. -* `engine` - (Optional, Forces new resources) Name of the database engine to be used for this DB cluster. Terraform will only perform drift detection if a configuration value is provided. Current Valid values: `docdb`. Defaults to `docdb`. Conflicts with `source_db_cluster_identifier`. +* `engine` - (Optional, Forces new resources) Name of the database engine to be used for this DB cluster. Current Valid values: `docdb`. Conflicts with `source_db_cluster_identifier`. * `engine_version` - (Optional) Engine version of the global database. Upgrading the engine version will result in all cluster members being immediately updated. * **NOTE:** Upgrading major versions is not supported. -* `force_destroy` - (Optional) Enable to remove DB Cluster members from Global Cluster on destroy. Required with `source_db_cluster_identifier`. -* `source_db_cluster_identifier` - (Optional) Amazon Resource Name (ARN) to use as the primary DB Cluster of the Global Cluster on creation. Terraform cannot perform drift detection of this value. +* `source_db_cluster_identifier` - (Optional, Forces new resources) Amazon Resource Name (ARN) to use as the primary DB Cluster of the Global Cluster on creation. Terraform cannot perform drift detection of this value. * `storage_encrypted` - (Optional, Forces new resources) Specifies whether the DB cluster is encrypted. The default is `false` unless `source_db_cluster_identifier` is specified and encrypted. Terraform will only perform drift detection if a configuration value is provided. ## Attributes Reference @@ -122,7 +121,7 @@ In addition to all arguments above, the following attributes are exported: * `global_cluster_members` - Set of objects containing Global Cluster members. * `db_cluster_arn` - Amazon Resource Name (ARN) of member DB Cluster * `is_writer` - Whether the member is the primary DB Cluster -* `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the global database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed +* `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the globalq database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed * `id` - DocDB Global Cluster identifier ## Import @@ -133,7 +132,6 @@ In addition to all arguments above, the following attributes are exported: $ terraform import aws_docdb_global_cluster.example example ``` -Certain resource arguments, like `force_destroy`, only exist within Terraform. If the argument is set in the Terraform configuration on an imported resource, Terraform will show a difference on the first plan after import to update the state value. This change is safe to apply immediately so the state matches the desired configuration. Certain resource arguments, like `source_db_cluster_identifier`, do not have an API method for reading the information after creation. If the argument is set in the Terraform configuration on an imported resource, Terraform will always show a difference. To workaround this behavior, either omit the argument from the Terraform configuration or use [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to hide the difference, e.g. ```terraform From 4d1e3ed693f86f964f26c94d4fe297e8f4f4c13d Mon Sep 17 00:00:00 2001 From: larssnellman Date: Tue, 26 Oct 2021 13:28:30 -0400 Subject: [PATCH 10/31] Fixes two failing tests --- internal/service/docdb/global_cluster_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/docdb/global_cluster_test.go b/internal/service/docdb/global_cluster_test.go index a3d33a141f2..f3564ff5129 100644 --- a/internal/service/docdb/global_cluster_test.go +++ b/internal/service/docdb/global_cluster_test.go @@ -36,7 +36,7 @@ func TestAccDocDBGlobalCluster_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckDocDBGlobalClusterExists(resourceName, &globalCluster1), //This is a rds arn - acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "rds", fmt.Sprintf("global-cluster:%s", rName)), + acctest.CheckResourceAttrGlobalARN(resourceName, "arn", "rds", fmt.Sprintf("global-cluster:%s", rName)), resource.TestCheckResourceAttr(resourceName, "database_name", ""), resource.TestCheckResourceAttr(resourceName, "deletion_protection", "false"), resource.TestCheckResourceAttrSet(resourceName, "engine"), @@ -512,6 +512,7 @@ func testAccDocDBGlobalClusterConfigStorageEncrypted(rName string, storageEncryp return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { global_cluster_identifier = %q + engine = "docdb" storage_encrypted = %t } `, rName, storageEncrypted) From 89681177c1446f4cbe320f0c630593eeafaef68d Mon Sep 17 00:00:00 2001 From: larssnellman Date: Mon, 8 Nov 2021 12:04:32 -0500 Subject: [PATCH 11/31] Fixes function names to match existing naming standards. --- internal/service/docdb/global_cluster.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index bf365252985..c65bcc922f0 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -146,7 +146,7 @@ func resourceGlobalClusterCreate(ctx context.Context, d *schema.ResourceData, me d.SetId(aws.StringValue(output.GlobalCluster.GlobalClusterIdentifier)) - if err := waitForDocDBGlobalClusterCreation(ctx, conn, d.Id()); err != nil { + if err := waitForGlobalClusterCreation(ctx, conn, d.Id()); err != nil { return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) availability: %w", d.Id(), err)) } @@ -187,7 +187,7 @@ func resourceGlobalClusterRead(ctx context.Context, d *schema.ResourceData, meta _ = d.Set("engine_version", globalCluster.EngineVersion) _ = d.Set("global_cluster_identifier", globalCluster.GlobalClusterIdentifier) - if err := d.Set("global_cluster_members", flattenDocDBGlobalClusterMembers(globalCluster.GlobalClusterMembers)); err != nil { + if err := d.Set("global_cluster_members", flattenGlobalClusterMembers(globalCluster.GlobalClusterMembers)); err != nil { return diag.FromErr(fmt.Errorf("error setting global_cluster_members: %w", err)) } @@ -306,7 +306,7 @@ func resourceGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, me return nil } -func flattenDocDBGlobalClusterMembers(apiObjects []*docdb.GlobalClusterMember) []interface{} { +func flattenGlobalClusterMembers(apiObjects []*docdb.GlobalClusterMember) []interface{} { if len(apiObjects) == 0 { return nil } @@ -412,7 +412,7 @@ func globalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClus } } -func waitForDocDBGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { +func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { stateConf := &resource.StateChangeConf{ Pending: []string{"creating"}, Target: []string{"available"}, From d020d4076d1437a3dec8728c0e93c24d6d60c940 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Mon, 8 Nov 2021 12:46:22 -0500 Subject: [PATCH 12/31] Linting fixed for DocDB --- internal/provider/provider.go | 4 ++-- internal/service/docdb/cluster.go | 8 ++++---- internal/service/docdb/cluster_test.go | 2 +- internal/service/docdb/sweep.go | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a4a476d5dbc..4a55c2675e9 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -980,8 +980,8 @@ func Provider() *schema.Provider { "aws_docdb_cluster_parameter_group": docdb.ResourceClusterParameterGroup(), "aws_docdb_cluster_snapshot": docdb.ResourceClusterSnapshot(), - "aws_docdb_global_cluster": docdb.ResourceGlobalCluster(), - "aws_docdb_subnet_group": docdb.ResourceSubnetGroup(), + "aws_docdb_global_cluster": docdb.ResourceGlobalCluster(), + "aws_docdb_subnet_group": docdb.ResourceSubnetGroup(), "aws_directory_service_conditional_forwarder": ds.ResourceConditionalForwarder(), "aws_directory_service_directory": ds.ResourceDirectory(), diff --git a/internal/service/docdb/cluster.go b/internal/service/docdb/cluster.go index e47f49cad0f..86f02fa7873 100644 --- a/internal/service/docdb/cluster.go +++ b/internal/service/docdb/cluster.go @@ -4,10 +4,6 @@ import ( "context" "errors" "fmt" - "log" - "regexp" - "strings" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" @@ -20,6 +16,10 @@ import ( 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" + "log" + "regexp" + "strings" + "time" ) func ResourceCluster() *schema.Resource { diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index c25c066c465..decb90f25ab 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -309,7 +309,7 @@ func TestAccDocDBCluster_GlobalClusterIdentifier_PrimarySecondaryClusters(t *tes ErrorCheck: acctest.ErrorCheck(t, docdb.EndpointsID), ProviderFactories: acctest.FactoriesAlternate(&providers), - CheckDestroy: testAccCheckDocDBClusterDestroy, + CheckDestroy: testAccCheckDocDBClusterDestroy, Steps: []resource.TestStep{ { Config: testAccDocDBClusterConfigGlobalClusterIdentifierPrimarySecondaryClusters(rNameGlobal, rNamePrimary, rNameSecondary), diff --git a/internal/service/docdb/sweep.go b/internal/service/docdb/sweep.go index 276a9e1124d..a55ab2df4bd 100644 --- a/internal/service/docdb/sweep.go +++ b/internal/service/docdb/sweep.go @@ -66,4 +66,4 @@ func sweepGlobalClusters(region string) error { } return nil -} \ No newline at end of file +} From c0fa27318fdca5b229214027ca026ecc6d6087cf Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 10:58:43 -0500 Subject: [PATCH 13/31] Refactoring Find/Wait/Status: const/enums --- internal/service/docdb/find.go | 52 ++++++++++ internal/service/docdb/global_cluster.go | 96 ++++--------------- internal/service/docdb/global_cluster_test.go | 4 +- internal/service/docdb/wait.go | 38 ++++++++ 4 files changed, 108 insertions(+), 82 deletions(-) create mode 100644 internal/service/docdb/find.go create mode 100644 internal/service/docdb/wait.go diff --git a/internal/service/docdb/find.go b/internal/service/docdb/find.go new file mode 100644 index 00000000000..e8cd0af1759 --- /dev/null +++ b/internal/service/docdb/find.go @@ -0,0 +1,52 @@ +package docdb + +import ( + "context" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/docdb" +) + +func FindGlobalClusterById(ctx context.Context, conn *docdb.DocDB, globalClusterID string) (*docdb.GlobalCluster, error) { + var globalCluster *docdb.GlobalCluster + + input := &docdb.DescribeGlobalClustersInput{ + GlobalClusterIdentifier: aws.String(globalClusterID), + } + + log.Printf("[DEBUG] Reading DocDB Global Cluster (%s): %s", globalClusterID, input) + err := conn.DescribeGlobalClustersPagesWithContext(ctx, input, func(page *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, gc := range page.GlobalClusters { + if gc == nil { + continue + } + + if aws.StringValue(gc.GlobalClusterIdentifier) == globalClusterID { + globalCluster = gc + return false + } + } + + return !lastPage + }) + + return globalCluster, err +} + +func FindGlobalClusterIdByArn(ctx context.Context, conn *docdb.DocDB, arn string) string { + result, err := conn.DescribeDBClustersWithContext(ctx, &docdb.DescribeDBClustersInput{}) + if err != nil { + return "" + } + for _, cluster := range result.DBClusters { + if aws.StringValue(cluster.DBClusterArn) == arn { + return aws.StringValue(cluster.DBClusterIdentifier) + } + } + return "" +} \ No newline at end of file diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index c65bcc922f0..6115ac11d3a 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -3,10 +3,11 @@ package docdb import ( "context" "fmt" - "github.com/hashicorp/terraform-provider-aws/internal/conns" "log" "time" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -156,7 +157,7 @@ func resourceGlobalClusterCreate(ctx context.Context, d *schema.ResourceData, me func resourceGlobalClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).DocDBConn - globalCluster, err := DescribeGlobalCluster(ctx, conn, d.Id()) + globalCluster, err := FindGlobalClusterById(ctx, conn, d.Id()) if tfawserr.ErrMessageContains(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { log.Printf("[WARN] DocDB Global Cluster (%s) not found, removing from state", d.Id()) @@ -325,36 +326,6 @@ func flattenGlobalClusterMembers(apiObjects []*docdb.GlobalClusterMember) []inte return tfList } -func DescribeGlobalCluster(ctx context.Context, conn *docdb.DocDB, globalClusterID string) (*docdb.GlobalCluster, error) { - var globalCluster *docdb.GlobalCluster - - input := &docdb.DescribeGlobalClustersInput{ - GlobalClusterIdentifier: aws.String(globalClusterID), - } - - log.Printf("[DEBUG] Reading DocDB Global Cluster (%s): %s", globalClusterID, input) - err := conn.DescribeGlobalClustersPagesWithContext(ctx, input, func(page *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, gc := range page.GlobalClusters { - if gc == nil { - continue - } - - if aws.StringValue(gc.GlobalClusterIdentifier) == globalClusterID { - globalCluster = gc - return false - } - } - - return !lastPage - }) - - return globalCluster, err -} - func describeGlobalClusterFromDbClusterARN(ctx context.Context, conn *docdb.DocDB, dbClusterARN string) (*docdb.GlobalCluster, error) { var globalCluster *docdb.GlobalCluster @@ -392,12 +363,12 @@ func describeGlobalClusterFromDbClusterARN(ctx context.Context, conn *docdb.DocD return globalCluster, err } -func globalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClusterID string) resource.StateRefreshFunc { +func statusGlobalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClusterID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - globalCluster, err := DescribeGlobalCluster(ctx, conn, globalClusterID) + globalCluster, err := FindGlobalClusterById(ctx, conn, globalClusterID) if tfawserr.ErrMessageContains(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { - return nil, "deleted", nil + return nil, []string{GlobalClusterStatusDeleted}, nil } if err != nil { @@ -405,7 +376,7 @@ func globalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClus } if globalCluster == nil { - return nil, "deleted", nil + return nil, []string{GlobalClusterStatusDeleted}, nil } return globalCluster, aws.StringValue(globalCluster.Status), nil @@ -414,9 +385,9 @@ func globalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClus func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { stateConf := &resource.StateChangeConf{ - Pending: []string{"creating"}, - Target: []string{"available"}, - Refresh: globalClusterRefreshFunc(ctx, conn, globalClusterID), + Pending: []string{GlobalClusterStatusCreating} + Target: []string{GlobalClusterStatusAvailable} + Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), Timeout: 10 * time.Minute, } @@ -428,9 +399,9 @@ func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, global func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { stateConf := &resource.StateChangeConf{ - Pending: []string{"modifying", "upgrading"}, - Target: []string{"available"}, - Refresh: globalClusterRefreshFunc(ctx, conn, globalClusterID), + Pending: []string{GlobalClusterStatusModifying, GlobalClusterStatusUpgrading} + Pending: []string{GlobalClusterStatusAvailable} + Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), Timeout: 10 * time.Minute, Delay: 30 * time.Second, } @@ -441,28 +412,6 @@ func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalCl return err } -func WaitForGlobalClusterDeletion(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{ - "available", - "deleting", - }, - Target: []string{"deleted"}, - Refresh: globalClusterRefreshFunc(ctx, conn, globalClusterID), - Timeout: 10 * time.Minute, - NotFoundChecks: 1, - } - - log.Printf("[DEBUG] Waiting for DocDB Global Cluster (%s) deletion", globalClusterID) - _, err := stateConf.WaitForStateContext(ctx) - - if tfresource.NotFound(err) { - return nil - } - - return err -} - func waitForGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClusterIdentifier string) error { var globalCluster *docdb.GlobalCluster stillExistsErr := fmt.Errorf("DocDB Cluster still exists in DocDB Global Cluster") @@ -506,12 +455,12 @@ func resourceGlobalClusterUpgradeEngineVersion(ctx context.Context, d *schema.Re if err != nil { return err } - globalCluster, err := DescribeGlobalCluster(ctx, conn, d.Id()) + globalCluster, err := FindGlobalClusterById(ctx, conn, d.Id()) if err != nil { return err } for _, clusterMember := range globalCluster.GlobalClusterMembers { - err := waitForDocDBClusterUpdate(conn, resourceGlobalClusterGetIdByArn(ctx, conn, aws.StringValue(clusterMember.DBClusterArn)), d.Timeout(schema.TimeoutUpdate)) + err := waitForDocDBClusterUpdate(conn, FindGlobalClusterIdByArn(ctx, conn, aws.StringValue(clusterMember.DBClusterArn)), d.Timeout(schema.TimeoutUpdate)) if err != nil { return err } @@ -519,19 +468,6 @@ func resourceGlobalClusterUpgradeEngineVersion(ctx context.Context, d *schema.Re return nil } -func resourceGlobalClusterGetIdByArn(ctx context.Context, conn *docdb.DocDB, arn string) string { - result, err := conn.DescribeDBClustersWithContext(ctx, &docdb.DescribeDBClustersInput{}) - if err != nil { - return "" - } - for _, cluster := range result.DBClusters { - if aws.StringValue(cluster.DBClusterArn) == arn { - return aws.StringValue(cluster.DBClusterIdentifier) - } - } - return "" -} - func resourceGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, clusterMembers *schema.Set, engineVersion string, conn *docdb.DocDB) error { for _, clusterMemberRaw := range clusterMembers.List() { clusterMember := clusterMemberRaw.(map[string]interface{}) @@ -563,4 +499,4 @@ func resourceGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, cluster } } return nil -} +} \ No newline at end of file diff --git a/internal/service/docdb/global_cluster_test.go b/internal/service/docdb/global_cluster_test.go index f3564ff5129..92cbe3720f6 100644 --- a/internal/service/docdb/global_cluster_test.go +++ b/internal/service/docdb/global_cluster_test.go @@ -308,7 +308,7 @@ func testAccCheckDocDBGlobalClusterExists(resourceName string, globalCluster *do conn := acctest.Provider.Meta().(*conns.AWSClient).DocDBConn - cluster, err := tfdocdb.DescribeGlobalCluster(context.TODO(), conn, rs.Primary.ID) + cluster, err := tfdocdb.FindGlobalClusterById(context.TODO(), conn, rs.Primary.ID) if err != nil { return err @@ -336,7 +336,7 @@ func testAccCheckDocDBGlobalClusterDestroy(s *terraform.State) error { continue } - globalCluster, err := tfdocdb.DescribeGlobalCluster(context.TODO(), conn, rs.Primary.ID) + globalCluster, err := tfdocdb.FindGlobalClusterById(context.TODO(), conn, rs.Primary.ID) if tfawserr.ErrMessageContains(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { continue diff --git a/internal/service/docdb/wait.go b/internal/service/docdb/wait.go new file mode 100644 index 00000000000..969cd6030ef --- /dev/null +++ b/internal/service/docdb/wait.go @@ -0,0 +1,38 @@ +package docdb + +import ( + "context" + "log" + "time" + + "github.com/aws/aws-sdk-go/service/docdb" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +const ( + GlobalClusterStatusAvailable = "available" + GlobalClusterStatusDeleted = "deleted" + GlobalClusterStatusDeleting = "deleting" + GlobalClusterStatusModifying = "modifying" + GlobalClusterStatusUpgrading = "upgrading" +) + +func WaitForGlobalClusterDeletion(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{GlobalClusterStatusAvailable, GlobalClusterStatusDeleting}, + Target: []string{GlobalClusterStatusDeleted}, + Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), + Timeout: 10 * time.Minute, + NotFoundChecks: 1, + } + + log.Printf("[DEBUG] Waiting for DocDB Global Cluster (%s) deletion", globalClusterID) + _, err := stateConf.WaitForStateContext(ctx) + + if tfresource.NotFound(err) { + return nil + } + + return err +} \ No newline at end of file From 458f0f9303501f4ff0eb6b7456f1832494d8a36c Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 12:58:45 -0500 Subject: [PATCH 14/31] refactor to enums. --- internal/service/docdb/global_cluster.go | 18 +++++++----------- internal/service/docdb/wait.go | 1 + 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 6115ac11d3a..75c0d17320e 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -367,26 +367,22 @@ func statusGlobalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, glob return func() (interface{}, string, error) { globalCluster, err := FindGlobalClusterById(ctx, conn, globalClusterID) - if tfawserr.ErrMessageContains(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") { - return nil, []string{GlobalClusterStatusDeleted}, nil + if tfawserr.ErrMessageContains(err, docdb.ErrCodeGlobalClusterNotFoundFault, "") || globalCluster == nil { + return nil, GlobalClusterStatusDeleted, nil } if err != nil { return nil, "", fmt.Errorf("error reading DocDB Global Cluster (%s): %w", globalClusterID, err) } - - if globalCluster == nil { - return nil, []string{GlobalClusterStatusDeleted}, nil - } - + return globalCluster, aws.StringValue(globalCluster.Status), nil } } func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { stateConf := &resource.StateChangeConf{ - Pending: []string{GlobalClusterStatusCreating} - Target: []string{GlobalClusterStatusAvailable} + Pending: []string{GlobalClusterStatusCreating}, + Target: []string{GlobalClusterStatusAvailable}, Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), Timeout: 10 * time.Minute, } @@ -399,8 +395,8 @@ func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, global func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { stateConf := &resource.StateChangeConf{ - Pending: []string{GlobalClusterStatusModifying, GlobalClusterStatusUpgrading} - Pending: []string{GlobalClusterStatusAvailable} + Pending: []string{GlobalClusterStatusModifying, GlobalClusterStatusUpgrading}, + Target: []string{GlobalClusterStatusAvailable}, Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), Timeout: 10 * time.Minute, Delay: 30 * time.Second, diff --git a/internal/service/docdb/wait.go b/internal/service/docdb/wait.go index 969cd6030ef..9edcbebae18 100644 --- a/internal/service/docdb/wait.go +++ b/internal/service/docdb/wait.go @@ -12,6 +12,7 @@ import ( const ( GlobalClusterStatusAvailable = "available" + GlobalClusterStatusCreating = "creating" GlobalClusterStatusDeleted = "deleted" GlobalClusterStatusDeleting = "deleting" GlobalClusterStatusModifying = "modifying" From 5cad7e0b72c65650fe1a3610a3eed74febaa1bd2 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 14:46:55 -0500 Subject: [PATCH 15/31] Implementing timeouts. Will scale per number of resources. --- internal/service/docdb/global_cluster.go | 49 ++++++++++--------- internal/service/docdb/global_cluster_test.go | 2 +- internal/service/docdb/wait.go | 10 +++- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 75c0d17320e..3cfeb2cc5d2 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -18,20 +18,24 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -const ( - docDBGlobalClusterRemovalTimeout = 2 * time.Minute -) - func ResourceGlobalCluster() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceGlobalClusterCreate, ReadWithoutTimeout: resourceGlobalClusterRead, UpdateWithoutTimeout: resourceGlobalClusterUpdate, DeleteWithoutTimeout: resourceGlobalClusterDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + //Timeouts will scale per number of resources in the cluster. Timeouts implemented on each resource action. + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(GlobalClusterCreateTimeout), + Update: schema.DefaultTimeout(GlobalClusterUpdateTimeout), + Delete: schema.DefaultTimeout(GlobalClusterDeleteTimeout), + }, + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -147,7 +151,7 @@ func resourceGlobalClusterCreate(ctx context.Context, d *schema.ResourceData, me d.SetId(aws.StringValue(output.GlobalCluster.GlobalClusterIdentifier)) - if err := waitForGlobalClusterCreation(ctx, conn, d.Id()); err != nil { + if err := waitForGlobalClusterCreation(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) availability: %w", d.Id(), err)) } @@ -175,7 +179,7 @@ func resourceGlobalClusterRead(ctx context.Context, d *schema.ResourceData, meta return nil } - if aws.StringValue(globalCluster.Status) == "deleting" || aws.StringValue(globalCluster.Status) == "deleted" { + if aws.StringValue(globalCluster.Status) == GlobalClusterStatusDeleting || aws.StringValue(globalCluster.Status) == GlobalClusterStatusDeleted { log.Printf("[WARN] DocDB Global Cluster (%s) in deleted state (%s), removing from state", d.Id(), aws.StringValue(globalCluster.Status)) d.SetId("") return nil @@ -224,7 +228,7 @@ func resourceGlobalClusterUpdate(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(fmt.Errorf("error updating DocDB Global Cluster: %w", err)) } - if err := waitForGlobalClusterUpdate(ctx, conn, d.Id()); err != nil { + if err := waitForGlobalClusterUpdate(ctx, conn, d.Id(),d.Timeout(schema.TimeoutUpdate)); err != nil { return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) update: %w", d.Id(), err)) } @@ -236,13 +240,11 @@ func resourceGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, me for _, globalClusterMemberRaw := range d.Get("global_cluster_members").(*schema.Set).List() { globalClusterMember, ok := globalClusterMemberRaw.(map[string]interface{}) - if !ok { continue } dbClusterArn, ok := globalClusterMember["db_cluster_arn"].(string) - if !ok { continue } @@ -253,16 +255,14 @@ func resourceGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, me } _, err := conn.RemoveFromGlobalClusterWithContext(ctx, input) - if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "is not found in global cluster") { continue } - if err != nil { return diag.FromErr(fmt.Errorf("error removing DocDB Cluster (%s) from Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) } - if err := waitForGlobalClusterRemoval(ctx, conn, dbClusterArn); err != nil { + if err := waitForGlobalClusterRemoval(ctx, conn, dbClusterArn, d.Timeout(schema.TimeoutDelete)); err != nil { return diag.FromErr(fmt.Errorf("error waiting for DocDB Cluster (%s) removal from DocDB Global Cluster (%s): %w", dbClusterArn, d.Id(), err)) } } @@ -274,7 +274,7 @@ func resourceGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, me log.Printf("[DEBUG] Deleting DocDB Global Cluster (%s): %s", d.Id(), input) // Allow for eventual consistency - err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { + err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { _, err := conn.DeleteGlobalClusterWithContext(ctx, input) if tfawserr.ErrMessageContains(err, docdb.ErrCodeInvalidGlobalClusterStateFault, "is not empty") { @@ -300,7 +300,7 @@ func resourceGlobalClusterDelete(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(fmt.Errorf("error deleting DocDB Global Cluster: %w", err)) } - if err := WaitForGlobalClusterDeletion(ctx, conn, d.Id()); err != nil { + if err := WaitForGlobalClusterDeletion(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) deletion: %w", d.Id(), err)) } @@ -326,6 +326,7 @@ func flattenGlobalClusterMembers(apiObjects []*docdb.GlobalClusterMember) []inte return tfList } +//TODO rename and move to FIND func describeGlobalClusterFromDbClusterARN(ctx context.Context, conn *docdb.DocDB, dbClusterARN string) (*docdb.GlobalCluster, error) { var globalCluster *docdb.GlobalCluster @@ -374,17 +375,17 @@ func statusGlobalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, glob if err != nil { return nil, "", fmt.Errorf("error reading DocDB Global Cluster (%s): %w", globalClusterID, err) } - + return globalCluster, aws.StringValue(globalCluster.Status), nil } } -func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { +func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{GlobalClusterStatusCreating}, Target: []string{GlobalClusterStatusAvailable}, Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), - Timeout: 10 * time.Minute, + Timeout: timeout, } log.Printf("[DEBUG] Waiting for DocDB Global Cluster (%s) availability", globalClusterID) @@ -393,12 +394,12 @@ func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, global return err } -func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { +func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{GlobalClusterStatusModifying, GlobalClusterStatusUpgrading}, Target: []string{GlobalClusterStatusAvailable}, Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), - Timeout: 10 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, } @@ -408,11 +409,11 @@ func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalCl return err } -func waitForGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClusterIdentifier string) error { +func waitForGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClusterIdentifier string, timeout time.Duration) error { var globalCluster *docdb.GlobalCluster stillExistsErr := fmt.Errorf("DocDB Cluster still exists in DocDB Global Cluster") - err := resource.RetryContext(ctx, docDBGlobalClusterRemovalTimeout, func() *resource.RetryError { + err := resource.RetryContext(ctx, timeout, func() *resource.RetryError { var err error globalCluster, err = describeGlobalClusterFromDbClusterARN(ctx, conn, dbClusterIdentifier) @@ -447,7 +448,7 @@ func waitForGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClust // To support minor version upgrades, we will upgrade all cluster members func resourceGlobalClusterUpgradeEngineVersion(ctx context.Context, d *schema.ResourceData, conn *docdb.DocDB) error { log.Printf("[DEBUG] Upgrading DocDB Global Cluster (%s) engine version: %s", d.Id(), d.Get("engine_version")) - err := resourceGlobalClusterUpgradeMinorEngineVersion(ctx, d.Get("global_cluster_members").(*schema.Set), d.Get("engine_version").(string), conn) + err := resourceGlobalClusterUpgradeMinorEngineVersion(ctx, d.Get("global_cluster_members").(*schema.Set), d.Get("engine_version").(string), conn, d.Timeout(schema.TimeoutUpdate)) if err != nil { return err } @@ -464,7 +465,7 @@ func resourceGlobalClusterUpgradeEngineVersion(ctx context.Context, d *schema.Re return nil } -func resourceGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, clusterMembers *schema.Set, engineVersion string, conn *docdb.DocDB) error { +func resourceGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, clusterMembers *schema.Set, engineVersion string, conn *docdb.DocDB, timeout time.Duration) error { for _, clusterMemberRaw := range clusterMembers.List() { clusterMember := clusterMemberRaw.(map[string]interface{}) if clusterMemberArn, ok := clusterMember["db_cluster_arn"]; ok && clusterMemberArn.(string) != "" { @@ -473,7 +474,7 @@ func resourceGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, cluster DBClusterIdentifier: aws.String(clusterMemberArn.(string)), EngineVersion: aws.String(engineVersion), } - err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { + err := resource.RetryContext(ctx, timeout, func() *resource.RetryError { _, err := conn.ModifyDBClusterWithContext(ctx, modInput) if err != nil { if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "IAM role ARN value is invalid or does not include the required permissions") { diff --git a/internal/service/docdb/global_cluster_test.go b/internal/service/docdb/global_cluster_test.go index 92cbe3720f6..5c2197dbfbe 100644 --- a/internal/service/docdb/global_cluster_test.go +++ b/internal/service/docdb/global_cluster_test.go @@ -370,7 +370,7 @@ func testAccCheckDocDBGlobalClusterDisappears(globalCluster *docdb.GlobalCluster return err } - return tfdocdb.WaitForGlobalClusterDeletion(context.TODO(), conn, aws.StringValue(globalCluster.GlobalClusterIdentifier)) + return tfdocdb.WaitForGlobalClusterDeletion(context.TODO(), conn, aws.StringValue(globalCluster.GlobalClusterIdentifier), tfdocdb.GlobalClusterDeleteTimeout) } } diff --git a/internal/service/docdb/wait.go b/internal/service/docdb/wait.go index 9edcbebae18..88063a582d9 100644 --- a/internal/service/docdb/wait.go +++ b/internal/service/docdb/wait.go @@ -10,6 +10,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) +const ( + GlobalClusterCreateTimeout = 5 * time.Minute + GlobalClusterDeleteTimeout = 5 * time.Minute + GlobalClusterUpdateTimeout = 5 * time.Minute +) + const ( GlobalClusterStatusAvailable = "available" GlobalClusterStatusCreating = "creating" @@ -19,12 +25,12 @@ const ( GlobalClusterStatusUpgrading = "upgrading" ) -func WaitForGlobalClusterDeletion(ctx context.Context, conn *docdb.DocDB, globalClusterID string) error { +func WaitForGlobalClusterDeletion(ctx context.Context, conn *docdb.DocDB, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{GlobalClusterStatusAvailable, GlobalClusterStatusDeleting}, Target: []string{GlobalClusterStatusDeleted}, Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), - Timeout: 10 * time.Minute, + Timeout: timeout, NotFoundChecks: 1, } From 6cecbae26e2a391242bff426d497a69f100beb52 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 14:56:00 -0500 Subject: [PATCH 16/31] moving find functions --- internal/service/docdb/cluster.go | 2 +- internal/service/docdb/global_cluster.go | 42 ++---------------------- 2 files changed, 3 insertions(+), 41 deletions(-) diff --git a/internal/service/docdb/cluster.go b/internal/service/docdb/cluster.go index 86f02fa7873..d859773564a 100644 --- a/internal/service/docdb/cluster.go +++ b/internal/service/docdb/cluster.go @@ -549,7 +549,7 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { return nil } - globalCluster, err := describeGlobalClusterFromDbClusterARN(context.TODO(), conn, aws.StringValue(dbc.DBClusterArn)) + globalCluster, err := findGlobalClusterByArn(context.TODO(), conn, aws.StringValue(dbc.DBClusterArn)) // Ignore the following API error for regions/partitions that do not support DocDB Global Clusters: // InvalidParameterValue: Access Denied to API Version: APIGlobalDatabases diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 3cfeb2cc5d2..24e9cab886d 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -326,44 +326,6 @@ func flattenGlobalClusterMembers(apiObjects []*docdb.GlobalClusterMember) []inte return tfList } -//TODO rename and move to FIND -func describeGlobalClusterFromDbClusterARN(ctx context.Context, conn *docdb.DocDB, dbClusterARN string) (*docdb.GlobalCluster, error) { - var globalCluster *docdb.GlobalCluster - - input := &docdb.DescribeGlobalClustersInput{ - Filters: []*docdb.Filter{ - { - Name: aws.String("db-cluster-id"), - Values: []*string{aws.String(dbClusterARN)}, - }, - }, - } - - log.Printf("[DEBUG] Reading DocDB Global Clusters: %s", input) - err := conn.DescribeGlobalClustersPagesWithContext(ctx, input, func(page *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, gc := range page.GlobalClusters { - if gc == nil { - continue - } - - for _, globalClusterMember := range gc.GlobalClusterMembers { - if aws.StringValue(globalClusterMember.DBClusterArn) == dbClusterARN { - globalCluster = gc - return false - } - } - } - - return !lastPage - }) - - return globalCluster, err -} - func statusGlobalClusterRefreshFunc(ctx context.Context, conn *docdb.DocDB, globalClusterID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { globalCluster, err := FindGlobalClusterById(ctx, conn, globalClusterID) @@ -416,7 +378,7 @@ func waitForGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClust err := resource.RetryContext(ctx, timeout, func() *resource.RetryError { var err error - globalCluster, err = describeGlobalClusterFromDbClusterARN(ctx, conn, dbClusterIdentifier) + globalCluster, err = findGlobalClusterByArn(ctx, conn, dbClusterIdentifier) if err != nil { return resource.NonRetryableError(err) @@ -430,7 +392,7 @@ func waitForGlobalClusterRemoval(ctx context.Context, conn *docdb.DocDB, dbClust }) if tfresource.TimedOut(err) { - _, err = describeGlobalClusterFromDbClusterARN(ctx, conn, dbClusterIdentifier) + _, err = findGlobalClusterByArn(ctx, conn, dbClusterIdentifier) } if err != nil { From 4d455ec5389d2cf8c66ba95a6e9b5959e1e7c114 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 14:59:25 -0500 Subject: [PATCH 17/31] moving thisngs to find --- internal/service/docdb/find.go | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/internal/service/docdb/find.go b/internal/service/docdb/find.go index e8cd0af1759..be5a9143282 100644 --- a/internal/service/docdb/find.go +++ b/internal/service/docdb/find.go @@ -8,6 +8,43 @@ import ( "github.com/aws/aws-sdk-go/service/docdb" ) +func findGlobalClusterByArn(ctx context.Context, conn *docdb.DocDB, dbClusterARN string) (*docdb.GlobalCluster, error) { + var globalCluster *docdb.GlobalCluster + + input := &docdb.DescribeGlobalClustersInput{ + Filters: []*docdb.Filter{ + { + Name: aws.String("db-cluster-id"), + Values: []*string{aws.String(dbClusterARN)}, + }, + }, + } + + log.Printf("[DEBUG] Reading DocDB Global Clusters: %s", input) + err := conn.DescribeGlobalClustersPagesWithContext(ctx, input, func(page *docdb.DescribeGlobalClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, gc := range page.GlobalClusters { + if gc == nil { + continue + } + + for _, globalClusterMember := range gc.GlobalClusterMembers { + if aws.StringValue(globalClusterMember.DBClusterArn) == dbClusterARN { + globalCluster = gc + return false + } + } + } + + return !lastPage + }) + + return globalCluster, err +} + func FindGlobalClusterById(ctx context.Context, conn *docdb.DocDB, globalClusterID string) (*docdb.GlobalCluster, error) { var globalCluster *docdb.GlobalCluster @@ -49,4 +86,5 @@ func FindGlobalClusterIdByArn(ctx context.Context, conn *docdb.DocDB, arn string } } return "" -} \ No newline at end of file +} + From 139e17c29f30dcedd6f4e26654b27a29acb1bb2b Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 15:01:53 -0500 Subject: [PATCH 18/31] Find reactor (operator error) --- internal/service/docdb/find.go | 2 +- internal/service/docdb/global_cluster.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/docdb/find.go b/internal/service/docdb/find.go index be5a9143282..15d2c48b832 100644 --- a/internal/service/docdb/find.go +++ b/internal/service/docdb/find.go @@ -75,7 +75,7 @@ func FindGlobalClusterById(ctx context.Context, conn *docdb.DocDB, globalCluster return globalCluster, err } -func FindGlobalClusterIdByArn(ctx context.Context, conn *docdb.DocDB, arn string) string { +func findGlobalClusterIdByArn(ctx context.Context, conn *docdb.DocDB, arn string) string { result, err := conn.DescribeDBClustersWithContext(ctx, &docdb.DescribeDBClustersInput{}) if err != nil { return "" diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 24e9cab886d..1247430bb64 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -419,7 +419,7 @@ func resourceGlobalClusterUpgradeEngineVersion(ctx context.Context, d *schema.Re return err } for _, clusterMember := range globalCluster.GlobalClusterMembers { - err := waitForDocDBClusterUpdate(conn, FindGlobalClusterIdByArn(ctx, conn, aws.StringValue(clusterMember.DBClusterArn)), d.Timeout(schema.TimeoutUpdate)) + err := waitForDocDBClusterUpdate(conn, findGlobalClusterIdByArn(ctx, conn, aws.StringValue(clusterMember.DBClusterArn)), d.Timeout(schema.TimeoutUpdate)) if err != nil { return err } From 15adb3da860543ebf5c5d00763029dee7473a611 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 15:06:03 -0500 Subject: [PATCH 19/31] Linting fixed --- internal/service/docdb/find.go | 1 - internal/service/docdb/global_cluster.go | 6 +++--- internal/service/docdb/wait.go | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/service/docdb/find.go b/internal/service/docdb/find.go index 15d2c48b832..1d88003f9e5 100644 --- a/internal/service/docdb/find.go +++ b/internal/service/docdb/find.go @@ -87,4 +87,3 @@ func findGlobalClusterIdByArn(ctx context.Context, conn *docdb.DocDB, arn string } return "" } - diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 1247430bb64..2cfad49fa20 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -228,7 +228,7 @@ func resourceGlobalClusterUpdate(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(fmt.Errorf("error updating DocDB Global Cluster: %w", err)) } - if err := waitForGlobalClusterUpdate(ctx, conn, d.Id(),d.Timeout(schema.TimeoutUpdate)); err != nil { + if err := waitForGlobalClusterUpdate(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return diag.FromErr(fmt.Errorf("error waiting for DocDB Global Cluster (%s) update: %w", d.Id(), err)) } @@ -359,7 +359,7 @@ func waitForGlobalClusterCreation(ctx context.Context, conn *docdb.DocDB, global func waitForGlobalClusterUpdate(ctx context.Context, conn *docdb.DocDB, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{GlobalClusterStatusModifying, GlobalClusterStatusUpgrading}, - Target: []string{GlobalClusterStatusAvailable}, + Target: []string{GlobalClusterStatusAvailable}, Refresh: statusGlobalClusterRefreshFunc(ctx, conn, globalClusterID), Timeout: timeout, Delay: 30 * time.Second, @@ -458,4 +458,4 @@ func resourceGlobalClusterUpgradeMinorEngineVersion(ctx context.Context, cluster } } return nil -} \ No newline at end of file +} diff --git a/internal/service/docdb/wait.go b/internal/service/docdb/wait.go index 88063a582d9..73848780eb7 100644 --- a/internal/service/docdb/wait.go +++ b/internal/service/docdb/wait.go @@ -42,4 +42,4 @@ func WaitForGlobalClusterDeletion(ctx context.Context, conn *docdb.DocDB, global } return err -} \ No newline at end of file +} From 07a18ef6dfcc835fa682bbd66d8acb461a10c8f7 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 15:29:19 -0500 Subject: [PATCH 20/31] terrafmt of AccTests --- internal/service/docdb/cluster_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index decb90f25ab..3fff2dc056a 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -752,7 +752,7 @@ resource "aws_docdb_cluster_instance" "secondary" { func testAccDocDBClusterConfigGlobalClusterIdentifier_Update(rName, globalClusterIdentifierResourceName string) string { return fmt.Sprintf(` resource "aws_docdb_global_cluster" "test" { - count = 2 + count = 2 engine = "docdb" engine_version = "4.0.0" # version compatible with global global_cluster_identifier = "%[1]s-${count.index}" From 1bcb555707936a398c02e0d0733099baa173a14b Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 15:39:38 -0500 Subject: [PATCH 21/31] Updating docdb global docs (removed force_destroy, added timeouts) --- website/docs/r/docdb_global_cluster.html.markdown | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/website/docs/r/docdb_global_cluster.html.markdown b/website/docs/r/docdb_global_cluster.html.markdown index 5d2bdd0185e..bf53476fafc 100644 --- a/website/docs/r/docdb_global_cluster.html.markdown +++ b/website/docs/r/docdb_global_cluster.html.markdown @@ -95,7 +95,6 @@ resource "aws_docdb_cluster" "example" { } resource "aws_docdb_global_cluster" "example" { - force_destroy = true global_cluster_identifier = "example" source_db_cluster_identifier = aws_docdb_cluster.example.arn } @@ -110,10 +109,16 @@ The following arguments are supported: * `engine` - (Optional, Forces new resources) Name of the database engine to be used for this DB cluster. Terraform will only perform drift detection if a configuration value is provided. Current Valid values: `docdb`. Defaults to `docdb`. Conflicts with `source_db_cluster_identifier`. * `engine_version` - (Optional) Engine version of the global database. Upgrading the engine version will result in all cluster members being immediately updated. * **NOTE:** Upgrading major versions is not supported. -* `force_destroy` - (Optional) Enable to remove DB Cluster members from Global Cluster on destroy. Required with `source_db_cluster_identifier`. * `source_db_cluster_identifier` - (Optional) Amazon Resource Name (ARN) to use as the primary DB Cluster of the Global Cluster on creation. Terraform cannot perform drift detection of this value. * `storage_encrypted` - (Optional, Forces new resources) Specifies whether the DB cluster is encrypted. The default is `false` unless `source_db_cluster_identifier` is specified and encrypted. Terraform will only perform drift detection if a configuration value is provided. +### Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) for certain actions: + +* `create` - (Defaults to 5 mins) Used when creating the Global Cluster +* `update` - (Defaults to 5 mins) Used when updating the Global Cluster members (time is per member) +* `delete` - (Defaults to 5 mins) Used when deleting the Global Cluster members (time is per member) ## Attributes Reference In addition to all arguments above, the following attributes are exported: @@ -123,7 +128,7 @@ In addition to all arguments above, the following attributes are exported: * `db_cluster_arn` - Amazon Resource Name (ARN) of member DB Cluster * `is_writer` - Whether the member is the primary DB Cluster * `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the global database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed -* `id` - DocDB Global Cluster identifier +* `id` - DocDB Global Cluster ## Import @@ -133,7 +138,6 @@ In addition to all arguments above, the following attributes are exported: $ terraform import aws_docdb_global_cluster.example example ``` -Certain resource arguments, like `force_destroy`, only exist within Terraform. If the argument is set in the Terraform configuration on an imported resource, Terraform will show a difference on the first plan after import to update the state value. This change is safe to apply immediately so the state matches the desired configuration. Certain resource arguments, like `source_db_cluster_identifier`, do not have an API method for reading the information after creation. If the argument is set in the Terraform configuration on an imported resource, Terraform will always show a difference. To workaround this behavior, either omit the argument from the Terraform configuration or use [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to hide the difference, e.g. ```terraform From 6644b57c2b56459bf1956dd60130524514189b46 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 16:34:47 -0500 Subject: [PATCH 22/31] Updating docdb global docs --- website/docs/r/docdb_global_cluster.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/docdb_global_cluster.html.markdown b/website/docs/r/docdb_global_cluster.html.markdown index bf53476fafc..3757c345f84 100644 --- a/website/docs/r/docdb_global_cluster.html.markdown +++ b/website/docs/r/docdb_global_cluster.html.markdown @@ -8,9 +8,9 @@ description: |- # Resource: aws_docdb_global_cluster -Manages an DocumentDB Global Cluster, which is an DocumentDB global database spread across multiple regions. The global database contains a single primary cluster with read-write capability, and a read-only secondary cluster that receives data from the primary cluster. +Manages an DocumentDB Global Cluster. A global cluster consists of one primary region and up to five read-only secondary regions. You issue write operations directly to the primary cluster in the primary region and Amazon DocumentDB automatically replicates the data to the secondary regions using dedicated infrastructure. -More information about DocumentDB global databases can be found in the [DocumentDB Developer Guide](https://docs.aws.amazon.com/documentdb/latest/developerguide/global-clusters.html). +More information about DocumentDB Global Clusters can be found in the [DocumentDB Developer Guide](https://docs.aws.amazon.com/documentdb/latest/developerguide/global-clusters.html). ## Example Usage @@ -107,7 +107,7 @@ The following arguments are supported: * `global_cluster_identifier` - (Required, Forces new resources) The global cluster identifier. * `deletion_protection` - (Optional) If the Global Cluster should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`. * `engine` - (Optional, Forces new resources) Name of the database engine to be used for this DB cluster. Terraform will only perform drift detection if a configuration value is provided. Current Valid values: `docdb`. Defaults to `docdb`. Conflicts with `source_db_cluster_identifier`. -* `engine_version` - (Optional) Engine version of the global database. Upgrading the engine version will result in all cluster members being immediately updated. +* `engine_version` - (Optional) Engine version of the global database. Upgrading the engine version will result in all cluster members being immediately updated and will. * **NOTE:** Upgrading major versions is not supported. * `source_db_cluster_identifier` - (Optional) Amazon Resource Name (ARN) to use as the primary DB Cluster of the Global Cluster on creation. Terraform cannot perform drift detection of this value. * `storage_encrypted` - (Optional, Forces new resources) Specifies whether the DB cluster is encrypted. The default is `false` unless `source_db_cluster_identifier` is specified and encrypted. Terraform will only perform drift detection if a configuration value is provided. From 7a7581d5cd94682d14a32281e63d4ef4eccc063b Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 17:04:40 -0500 Subject: [PATCH 23/31] fixing markdown lint issues --- website/docs/r/docdb_global_cluster.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/r/docdb_global_cluster.html.markdown b/website/docs/r/docdb_global_cluster.html.markdown index 13717f30c92..196d7993891 100644 --- a/website/docs/r/docdb_global_cluster.html.markdown +++ b/website/docs/r/docdb_global_cluster.html.markdown @@ -120,6 +120,7 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d * `create` - (Defaults to 5 mins) Used when creating the Global Cluster * `update` - (Defaults to 5 mins) Used when updating the Global Cluster members (time is per member) * `delete` - (Defaults to 5 mins) Used when deleting the Global Cluster members (time is per member) + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 38a6822fced32175a50637ea36cf79b6a3ebd6c5 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 17:06:06 -0500 Subject: [PATCH 24/31] fixed grammar --- website/docs/r/docdb_global_cluster.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/r/docdb_global_cluster.html.markdown b/website/docs/r/docdb_global_cluster.html.markdown index 196d7993891..2f597e16e3c 100644 --- a/website/docs/r/docdb_global_cluster.html.markdown +++ b/website/docs/r/docdb_global_cluster.html.markdown @@ -127,10 +127,10 @@ In addition to all arguments above, the following attributes are exported: * `arn` - Global Cluster Amazon Resource Name (ARN) * `global_cluster_members` - Set of objects containing Global Cluster members. - * `db_cluster_arn` - Amazon Resource Name (ARN) of member DB Cluster - * `is_writer` - Whether the member is the primary DB Cluster -* `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the global database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed -* `id` - DocDB Global Cluster + * `db_cluster_arn` - Amazon Resource Name (ARN) of member DB Cluster. + * `is_writer` - Whether the member is the primary DB Cluster. +* `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the global database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed. +* `id` - DocDB Global Cluster. ## Import From f0a50227773c4a449c26a0866870ceda80342ed2 Mon Sep 17 00:00:00 2001 From: larssnellman Date: Wed, 10 Nov 2021 18:55:00 -0500 Subject: [PATCH 25/31] Fixes importlint pipeline failures --- internal/service/docdb/cluster.go | 9 +++++---- internal/service/docdb/global_cluster.go | 8 +++----- internal/service/docdb/global_cluster_test.go | 9 ++++----- internal/service/docdb/sweep.go | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/internal/service/docdb/cluster.go b/internal/service/docdb/cluster.go index d859773564a..cbe9ce0946d 100644 --- a/internal/service/docdb/cluster.go +++ b/internal/service/docdb/cluster.go @@ -4,6 +4,11 @@ import ( "context" "errors" "fmt" + "log" + "regexp" + "strings" + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" @@ -16,10 +21,6 @@ import ( 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" - "log" - "regexp" - "strings" - "time" ) func ResourceCluster() *schema.Resource { diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 2cfad49fa20..11eca4fc0c0 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -6,16 +6,14 @@ import ( "log" "time" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceGlobalCluster() *schema.Resource { diff --git a/internal/service/docdb/global_cluster_test.go b/internal/service/docdb/global_cluster_test.go index 5c2197dbfbe..57d9cc65504 100644 --- a/internal/service/docdb/global_cluster_test.go +++ b/internal/service/docdb/global_cluster_test.go @@ -4,18 +4,17 @@ import ( "context" "errors" "fmt" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-provider-aws/internal/conns" "regexp" "testing" - "github.com/aws/aws-sdk-go/service/docdb" - "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/docdb" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" tfdocdb "github.com/hashicorp/terraform-provider-aws/internal/service/docdb" ) diff --git a/internal/service/docdb/sweep.go b/internal/service/docdb/sweep.go index a55ab2df4bd..fc38bcb877e 100644 --- a/internal/service/docdb/sweep.go +++ b/internal/service/docdb/sweep.go @@ -5,12 +5,13 @@ package docdb import ( "fmt" + "log" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/sweep" - "log" ) func init() { From 590c81074b34994632bce9decbd0d6b513b8599e Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 18:59:52 -0500 Subject: [PATCH 26/31] fixing linting issues 001 --- internal/service/docdb/cluster.go | 10 ++++++---- internal/service/docdb/global_cluster.go | 8 +++----- internal/service/docdb/global_cluster_test.go | 5 +++-- internal/service/docdb/sweep.go | 5 ++++- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/internal/service/docdb/cluster.go b/internal/service/docdb/cluster.go index d859773564a..8e8dad60351 100644 --- a/internal/service/docdb/cluster.go +++ b/internal/service/docdb/cluster.go @@ -4,6 +4,12 @@ import ( "context" "errors" "fmt" + + "log" + "regexp" + "strings" + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" @@ -16,10 +22,6 @@ import ( 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" - "log" - "regexp" - "strings" - "time" ) func ResourceCluster() *schema.Resource { diff --git a/internal/service/docdb/global_cluster.go b/internal/service/docdb/global_cluster.go index 2cfad49fa20..11eca4fc0c0 100644 --- a/internal/service/docdb/global_cluster.go +++ b/internal/service/docdb/global_cluster.go @@ -6,16 +6,14 @@ import ( "log" "time" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceGlobalCluster() *schema.Resource { diff --git a/internal/service/docdb/global_cluster_test.go b/internal/service/docdb/global_cluster_test.go index 5c2197dbfbe..ad7872b7a08 100644 --- a/internal/service/docdb/global_cluster_test.go +++ b/internal/service/docdb/global_cluster_test.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" + "regexp" + "testing" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "regexp" - "testing" "github.com/aws/aws-sdk-go/service/docdb" diff --git a/internal/service/docdb/sweep.go b/internal/service/docdb/sweep.go index a55ab2df4bd..429605b4df4 100644 --- a/internal/service/docdb/sweep.go +++ b/internal/service/docdb/sweep.go @@ -5,12 +5,15 @@ package docdb import ( "fmt" + + "log" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/sweep" - "log" + ) func init() { From 3844721aa1ade0d0abbc9b367d39e45e33aa1064 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Wed, 10 Nov 2021 19:12:34 -0500 Subject: [PATCH 27/31] fixing linting issues 002 --- internal/service/docdb/cluster.go | 1 - internal/service/docdb/global_cluster_test.go | 9 +++------ internal/service/docdb/sweep.go | 6 ++---- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/internal/service/docdb/cluster.go b/internal/service/docdb/cluster.go index 8e8dad60351..cbe9ce0946d 100644 --- a/internal/service/docdb/cluster.go +++ b/internal/service/docdb/cluster.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "log" "regexp" "strings" diff --git a/internal/service/docdb/global_cluster_test.go b/internal/service/docdb/global_cluster_test.go index ad7872b7a08..550c36d3155 100644 --- a/internal/service/docdb/global_cluster_test.go +++ b/internal/service/docdb/global_cluster_test.go @@ -7,16 +7,14 @@ import ( "regexp" "testing" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/aws-sdk-go-base/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - - "github.com/aws/aws-sdk-go/service/docdb" - - "github.com/aws/aws-sdk-go/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" tfdocdb "github.com/hashicorp/terraform-provider-aws/internal/service/docdb" ) @@ -308,7 +306,6 @@ func testAccCheckDocDBGlobalClusterExists(resourceName string, globalCluster *do } conn := acctest.Provider.Meta().(*conns.AWSClient).DocDBConn - cluster, err := tfdocdb.FindGlobalClusterById(context.TODO(), conn, rs.Primary.ID) if err != nil { diff --git a/internal/service/docdb/sweep.go b/internal/service/docdb/sweep.go index 429605b4df4..238eee8e4a7 100644 --- a/internal/service/docdb/sweep.go +++ b/internal/service/docdb/sweep.go @@ -7,13 +7,11 @@ import ( "fmt" "log" - + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/sweep" - + "github.com/hashicorp/terraform-provider-aws/internal/conns" ) func init() { From cb0f187abee5e597f67f654b80b65696727b9d47 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 11 Nov 2021 10:49:24 -0500 Subject: [PATCH 28/31] Cosmetics. --- .changelog/20978.txt | 2 +- internal/provider/provider.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.changelog/20978.txt b/.changelog/20978.txt index d9dfdeef605..8ed8d07561d 100644 --- a/.changelog/20978.txt +++ b/.changelog/20978.txt @@ -3,5 +3,5 @@ aws_docdb_global_cluster ``` ```release-note:enhancement -resource/aws_docdb_cluster: Add global_cluster_identifier argument +resource/aws_docdb_cluster: Add `global_cluster_identifier` argument ``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7bfb0022c13..fccb5fdbe12 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -987,9 +987,8 @@ func Provider() *schema.Provider { "aws_docdb_cluster_instance": docdb.ResourceClusterInstance(), "aws_docdb_cluster_parameter_group": docdb.ResourceClusterParameterGroup(), "aws_docdb_cluster_snapshot": docdb.ResourceClusterSnapshot(), - - "aws_docdb_global_cluster": docdb.ResourceGlobalCluster(), - "aws_docdb_subnet_group": docdb.ResourceSubnetGroup(), + "aws_docdb_global_cluster": docdb.ResourceGlobalCluster(), + "aws_docdb_subnet_group": docdb.ResourceSubnetGroup(), "aws_directory_service_conditional_forwarder": ds.ResourceConditionalForwarder(), "aws_directory_service_directory": ds.ResourceDirectory(), From bf608c5175b4574a4c431c34168f931f9b44602f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 11 Nov 2021 10:52:44 -0500 Subject: [PATCH 29/31] Run 'make fmt'. --- internal/service/docdb/sweep.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/service/docdb/sweep.go b/internal/service/docdb/sweep.go index 238eee8e4a7..f3e74fab42f 100644 --- a/internal/service/docdb/sweep.go +++ b/internal/service/docdb/sweep.go @@ -5,13 +5,12 @@ package docdb import ( "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/docdb" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/conns" ) func init() { From a9440470c2eaac8968cb25757e0c9e908df8b505 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 11 Nov 2021 11:37:10 -0500 Subject: [PATCH 30/31] Skip DocDB Global Cluster acceptance tests in GovCloud. --- internal/service/docdb/cluster_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index 3fff2dc056a..a68e4d29e82 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -18,6 +18,17 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" ) +func init() { + acctest.RegisterServiceErrorCheckFunc(docdb.EndpointsID, testAccErrorCheckSkipDocDB) + +} + +func testAccErrorCheckSkipDocDB(t *testing.T) resource.ErrorCheckFunc { + return acctest.ErrorCheckSkipMessagesContaining(t, + "Global clusters are not supported", + ) +} + func TestAccDocDBCluster_basic(t *testing.T) { var dbCluster docdb.DBCluster rInt := sdkacctest.RandInt() From fcc3ff0e2f927d1df08518c536a7e7f7c01001e4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 11 Nov 2021 11:55:49 -0500 Subject: [PATCH 31/31] Additional, hacky GovCloud test skip. --- internal/service/docdb/cluster_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index a68e4d29e82..963ca615a11 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -183,6 +183,10 @@ func TestAccDocDBCluster_GlobalClusterIdentifier_Add(t *testing.T) { rName := sdkacctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_docdb_cluster.test" + if acctest.Partition() == "aws-us-gov" { + t.Skip("DocDB Global Cluster is not supported in GovCloud partition") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheckDocDBGlobalCluster(t) }, ErrorCheck: acctest.ErrorCheck(t, docdb.EndpointsID),