Skip to content

Commit

Permalink
Merge pull request #20978 from larssnellman/r-aws_docdb_global_cluster
Browse files Browse the repository at this point in the history
r/aws_docdb_global_cluster: Support for DocDB Global Clusters
  • Loading branch information
ewbankkit authored Nov 11, 2021
2 parents 040e492 + fcc3ff0 commit fb6b6e6
Show file tree
Hide file tree
Showing 12 changed files with 1,788 additions and 8 deletions.
7 changes: 7 additions & 0 deletions .changelog/20978.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_docdb_global_cluster
```

```release-note:enhancement
resource/aws_docdb_cluster: Add `global_cluster_identifier` argument
```
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@ 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_directory_service_conditional_forwarder": ds.ResourceConditionalForwarder(),
Expand Down
88 changes: 82 additions & 6 deletions internal/service/docdb/cluster.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package docdb

import (
"context"
"errors"
"fmt"
"log"
"regexp"
Expand Down Expand Up @@ -95,6 +97,12 @@ func ResourceCluster() *schema.Resource {
Computed: true,
},

"global_cluster_identifier": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validGlobalCusterIdentifier,
},

"reader_endpoint": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -363,12 +371,18 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error {
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{
Expand All @@ -380,6 +394,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error {
Tags: Tags(tags.IgnoreAWS()),
}

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)))
}
Expand Down Expand Up @@ -532,6 +550,20 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

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
if err != nil && !tfawserr.ErrMessageContains(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)
}
Expand All @@ -549,7 +581,6 @@ func resourceClusterRead(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)

Expand Down Expand Up @@ -657,6 +688,32 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
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)
Expand Down Expand Up @@ -707,6 +764,22 @@ func resourceClusterDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.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()),
}
Expand All @@ -724,12 +797,15 @@ func resourceClusterDelete(d *schema.ResourceData, meta interface{}) error {

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 tfawserr.ErrMessageContains(err, docdb.ErrCodeInvalidDBClusterStateFault, "is not currently in the available state") {
return resource.RetryableError(err)
}
if tfawserr.ErrMessageContains(err, docdb.ErrCodeInvalidDBClusterStateFault, "cluster is a part of a global cluster") {
return resource.RetryableError(err)
}
if tfawserr.ErrMessageContains(err, docdb.ErrCodeDBClusterNotFoundFault, "") {
return nil
}
Expand Down
Loading

0 comments on commit fb6b6e6

Please sign in to comment.