Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dms/repl_subnet_grp: Fix inconsistent result after apply #28748

Merged
merged 5 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/28748.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_dms_replication_subnet_group: Fix error ("Provider produced inconsistent result") when an error is encountered during creation
```
70 changes: 47 additions & 23 deletions internal/service/dms/replication_subnet_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dms
import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
Expand All @@ -11,10 +12,12 @@ import (
"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/create"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
"github.com/hashicorp/terraform-provider-aws/names"
)

func ResourceReplicationSubnetGroup() *schema.Resource {
Expand All @@ -24,6 +27,12 @@ func ResourceReplicationSubnetGroup() *schema.Resource {
Update: resourceReplicationSubnetGroupUpdate,
Delete: resourceReplicationSubnetGroupDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(15 * time.Minute),
Update: schema.DefaultTimeout(15 * time.Minute),
Delete: schema.DefaultTimeout(15 * time.Minute),
},

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Expand All @@ -47,6 +56,7 @@ func ResourceReplicationSubnetGroup() *schema.Resource {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
MinItems: 2,
Required: true,
},
"tags": tftags.TagsSchema(),
Expand All @@ -61,6 +71,10 @@ func ResourceReplicationSubnetGroup() *schema.Resource {
}
}

const (
ResNameReplicationSubnetGroup = "Replication Subnet Group"
)

func resourceReplicationSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).DMSConn()
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
Expand Down Expand Up @@ -93,10 +107,14 @@ func resourceReplicationSubnetGroupCreate(d *schema.ResourceData, meta interface
_, err = conn.CreateReplicationSubnetGroup(request)

if err != nil {
return err
return create.Error(names.DMS, create.ErrActionCreating, ResNameReplicationSubnetGroup, d.Get("replication_subnet_group_id").(string), err)
}
}

if err != nil {
return create.Error(names.DMS, create.ErrActionCreating, ResNameReplicationSubnetGroup, d.Get("replication_subnet_group_id").(string), err)
}

d.SetId(d.Get("replication_subnet_group_id").(string))
return resourceReplicationSubnetGroupRead(d, meta)
}
Expand All @@ -114,9 +132,17 @@ func resourceReplicationSubnetGroupRead(d *schema.ResourceData, meta interface{}
},
},
})

if !d.IsNewResource() && tfresource.NotFound(err) {
create.LogNotFoundRemoveState(names.DMS, create.ErrActionReading, ResNameReplicationSubnetGroup, d.Id())
d.SetId("")
return nil
}

if err != nil {
return err
return create.Error(names.DMS, create.ErrActionReading, ResNameReplicationSubnetGroup, d.Id(), err)
}

if len(response.ReplicationSubnetGroups) == 0 {
d.SetId("")
return nil
Expand All @@ -133,26 +159,35 @@ func resourceReplicationSubnetGroupRead(d *schema.ResourceData, meta interface{}
}.String()
d.Set("replication_subnet_group_arn", arn)

err = resourceReplicationSubnetGroupSetState(d, response.ReplicationSubnetGroups[0])
if err != nil {
return err
group := response.ReplicationSubnetGroups[0]

d.SetId(aws.StringValue(group.ReplicationSubnetGroupIdentifier))

subnet_ids := []string{}
for _, subnet := range group.Subnets {
subnet_ids = append(subnet_ids, aws.StringValue(subnet.SubnetIdentifier))
}

d.Set("replication_subnet_group_description", group.ReplicationSubnetGroupDescription)
d.Set("replication_subnet_group_id", group.ReplicationSubnetGroupIdentifier)
d.Set("subnet_ids", subnet_ids)
d.Set("vpc_id", group.VpcId)

tags, err := ListTags(conn, arn)

if err != nil {
return fmt.Errorf("error listing tags for DMS Replication Subnet Group (%s): %s", arn, err)
return create.Error(names.DMS, create.ErrActionReading, ResNameReplicationSubnetGroup, d.Id(), err)
}

tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
return create.SettingError(names.DMS, ResNameReplicationSubnetGroup, d.Id(), "tags", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
return create.SettingError(names.DMS, ResNameReplicationSubnetGroup, d.Id(), "tags_all", err)
}

return nil
Expand All @@ -177,15 +212,15 @@ func resourceReplicationSubnetGroupUpdate(d *schema.ResourceData, meta interface
o, n := d.GetChange("tags_all")

if err := UpdateTags(conn, arn, o, n); err != nil {
return fmt.Errorf("error updating DMS Replication Subnet Group (%s) tags: %s", arn, err)
return create.Error(names.DMS, create.ErrActionUpdating, ResNameReplicationSubnetGroup, d.Id(), err)
}
}

log.Println("[DEBUG] DMS update replication subnet group:", request)

_, err := conn.ModifyReplicationSubnetGroup(request)
if err != nil {
return err
return create.Error(names.DMS, create.ErrActionUpdating, ResNameReplicationSubnetGroup, d.Id(), err)
}

return resourceReplicationSubnetGroupRead(d, meta)
Expand All @@ -201,21 +236,10 @@ func resourceReplicationSubnetGroupDelete(d *schema.ResourceData, meta interface
log.Printf("[DEBUG] DMS delete replication subnet group: %#v", request)

_, err := conn.DeleteReplicationSubnetGroup(request)
return err
}

func resourceReplicationSubnetGroupSetState(d *schema.ResourceData, group *dms.ReplicationSubnetGroup) error {
d.SetId(aws.StringValue(group.ReplicationSubnetGroupIdentifier))

subnet_ids := []string{}
for _, subnet := range group.Subnets {
subnet_ids = append(subnet_ids, aws.StringValue(subnet.SubnetIdentifier))
if err != nil {
return create.Error(names.DMS, create.ErrActionDeleting, ResNameReplicationSubnetGroup, d.Id(), err)
}

d.Set("replication_subnet_group_description", group.ReplicationSubnetGroupDescription)
d.Set("replication_subnet_group_id", group.ReplicationSubnetGroupIdentifier)
d.Set("subnet_ids", subnet_ids)
d.Set("vpc_id", group.VpcId)

return nil
}
78 changes: 66 additions & 12 deletions website/docs/r/dms_replication_subnet_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,82 @@ description: |-

Provides a DMS (Data Migration Service) replication subnet group resource. DMS replication subnet groups can be created, updated, deleted, and imported.

~> **Note:** AWS requires a special IAM role called `dms-vpc-role` when using this resource. See the example below to create it as part of your configuration.

## Example Usage

### Basic

```terraform
# Create a new replication subnet group
resource "aws_dms_replication_subnet_group" "test" {
replication_subnet_group_description = "Test replication subnet group"
replication_subnet_group_id = "test-dms-replication-subnet-group-tf"
resource "aws_dms_replication_subnet_group" "example" {
replication_subnet_group_description = "Example replication subnet group"
replication_subnet_group_id = "example-dms-replication-subnet-group-tf"

subnet_ids = [
"subnet-12345678",
"subnet-12345679",
]

tags = {
Name = "test"
Name = "example"
}
}
```

## Argument Reference
### Creating special IAM role

The following arguments are supported:
If your account does not already include the `dms-vpc-role` IAM role, you will need to create it to allow DMS to manage subnets in the VPC.

* `replication_subnet_group_description` - (Required) The description for the subnet group.
* `replication_subnet_group_id` - (Required) The name for the replication subnet group. This value is stored as a lowercase string.
```terraform
resource "aws_iam_role" "dms-vpc-role" {
name = "dms-vpc-role"
description = "Allows DMS to manage VPC"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "dms.amazonaws.com"
}
Action = "sts:AssumeRole"
},
]
})
}

- Must contain no more than 255 alphanumeric characters, periods, spaces, underscores, or hyphens.
- Must not be "default".
resource "aws_iam_role_policy_attachment" "example" {
role = aws_iam_role.dms-vpc-role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}

* `subnet_ids` - (Required) A list of the EC2 subnet IDs for the subnet group.
* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
resource "aws_dms_replication_subnet_group" "example" {
replication_subnet_group_description = "Example"
replication_subnet_group_id = "example-id"

subnet_ids = [
"subnet-12345678",
"subnet-12345679",
]

tags = {
Name = "example-id"
}

# explicit depends_on is needed since this resource doesn't reference the role or policy attachment
depends_on = [aws_iam_role_policy_attachment.example]
}
```

## Argument Reference

The following arguments are supported:

* `replication_subnet_group_description` - (Required) Description for the subnet group.
* `replication_subnet_group_id` - (Required) Name for the replication subnet group. This value is stored as a lowercase string. It must contain no more than 255 alphanumeric characters, periods, spaces, underscores, or hyphens and cannot be `default`.
* `subnet_ids` - (Required) List of at least 2 EC2 subnet IDs for the subnet group. The subnets must cover at least 2 availability zones.
* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.

## Attributes Reference

Expand All @@ -48,6 +94,14 @@ In addition to all arguments above, the following attributes are exported:
* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block).
* `vpc_id` - The ID of the VPC the subnet group is in.

## Timeouts

[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts):

- `create` - (Default `15m`)
- `update` - (Default `15m`)
- `delete` - (Default `15m`)

## Import

Replication subnet groups can be imported using the `replication_subnet_group_id`, e.g.,
Expand Down