Skip to content

Commit

Permalink
resource/aws_subnet: Fix removing ipv6 cidr block from subnet (#12303)
Browse files Browse the repository at this point in the history
Output from acceptance testing:

```
--- PASS: TestAccAWSSubnet_disappears (24.88s)
--- PASS: TestAccAWSSubnet_basic (29.51s)
--- PASS: TestAccAWSSubnet_availabilityZoneId (31.44s)
--- PASS: TestAccAWSSubnet_ignoreTags (43.52s)
--- PASS: TestAccAWSSubnet_tags (60.81s)
--- PASS: TestAccAWSSubnet_enableIpv6 (61.10s)
--- PASS: TestAccAWSSubnet_ipv6 (63.05s)
```
  • Loading branch information
DrFaust92 authored Aug 20, 2020
1 parent ed22f76 commit 136cafe
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 90 deletions.
167 changes: 90 additions & 77 deletions aws/resource_aws_subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -40,15 +39,16 @@ func resourceAwsSubnet() *schema.Resource {
},

"cidr_block": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateIpv4CIDRNetworkAddress,
},

"ipv6_cidr_block": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateIpv6CIDRNetworkAddress,
},

"availability_zone": {
Expand Down Expand Up @@ -129,29 +129,28 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
resp, err := conn.CreateSubnet(createOpts)

if err != nil {
return fmt.Errorf("Error creating subnet: %s", err)
return fmt.Errorf("error creating subnet: %w", err)
}

// Get the ID and store it
subnet := resp.Subnet
d.SetId(*subnet.SubnetId)
log.Printf("[INFO] Subnet ID: %s", *subnet.SubnetId)
subnetId := aws.StringValue(subnet.SubnetId)
d.SetId(subnetId)
log.Printf("[INFO] Subnet ID: %s", subnetId)

// Wait for the Subnet to become available
log.Printf("[DEBUG] Waiting for subnet (%s) to become available", *subnet.SubnetId)
log.Printf("[DEBUG] Waiting for subnet (%s) to become available", subnetId)
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: []string{"available"},
Refresh: SubnetStateRefreshFunc(conn, *subnet.SubnetId),
Pending: []string{ec2.SubnetStatePending},
Target: []string{ec2.SubnetStateAvailable},
Refresh: SubnetStateRefreshFunc(conn, subnetId),
Timeout: d.Timeout(schema.TimeoutCreate),
}

_, err = stateConf.WaitForState()

if err != nil {
return fmt.Errorf(
"Error waiting for subnet (%s) to become ready: %s",
d.Id(), err)
return fmt.Errorf("error waiting for subnet (%s) to become ready: %w", d.Id(), err)
}

// You cannot modify multiple subnet attributes in the same request.
Expand All @@ -166,7 +165,7 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
}

if _, err := conn.ModifySubnetAttribute(input); err != nil {
return fmt.Errorf("error enabling EC2 Subnet (%s) assign IPv6 address on creation: %s", d.Id(), err)
return fmt.Errorf("error enabling EC2 Subnet (%s) assign IPv6 address on creation: %w", d.Id(), err)
}
}

Expand All @@ -179,7 +178,7 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
}

if _, err := conn.ModifySubnetAttribute(input); err != nil {
return fmt.Errorf("error enabling EC2 Subnet (%s) map public IP on launch: %s", d.Id(), err)
return fmt.Errorf("error enabling EC2 Subnet (%s) map public IP on launch: %w", d.Id(), err)
}
}

Expand All @@ -195,8 +194,8 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
})

if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
// Update state to indicate the subnet no longer exists.
if isAWSErr(err, "InvalidSubnetID.NotFound", "") {
log.Printf("[WARN] Subnet (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
Expand All @@ -221,7 +220,7 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
d.Set("ipv6_cidr_block", "")

for _, a := range subnet.Ipv6CidrBlockAssociationSet {
if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once
if aws.StringValue(a.Ipv6CidrBlockState.State) == ec2.SubnetCidrBlockStateCodeAssociated { //we can only ever have 1 IPv6 block associated at once
d.Set("ipv6_cidr_block_association_id", a.AssociationId)
d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
break
Expand All @@ -231,7 +230,7 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
d.Set("arn", subnet.SubnetArn)

if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(subnet.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %s", err)
return fmt.Errorf("error setting tags: %w", err)
}

d.Set("owner_id", subnet.OwnerId)
Expand All @@ -246,7 +245,7 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error {
o, n := d.GetChange("tags")

if err := keyvaluetags.Ec2UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating EC2 Subnet (%s) tags: %s", d.Id(), err)
return fmt.Errorf("error updating EC2 Subnet (%s) tags: %w", d.Id(), err)
}
}

Expand All @@ -268,14 +267,32 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error {
}

if d.HasChange("ipv6_cidr_block") {
// We need to handle that we disassociate the IPv6 CIDR block before we try and associate the new one
// This could be an issue as, we could error out when we try and add the new one
// We need to handle that we disassociate the IPv6 CIDR block before we try to associate the new one
// This could be an issue as, we could error out when we try to add the new one
// We may need to roll back the state and reattach the old one if this is the case

_, new := d.GetChange("ipv6_cidr_block")
newIpv6 := d.Get("ipv6_cidr_block").(string)

if v, ok := d.GetOk("ipv6_cidr_block_association_id"); ok {

ipv6AssignOnCreate := d.Get("assign_ipv6_address_on_creation").(bool)

if !ipv6AssignOnCreate {
modifyOpts := &ec2.ModifySubnetAttributeInput{
SubnetId: aws.String(d.Id()),
AssignIpv6AddressOnCreation: &ec2.AttributeBooleanValue{
Value: aws.Bool(false),
},
}

log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts)

_, err := conn.ModifySubnetAttribute(modifyOpts)

if err != nil {
return fmt.Errorf("error modifying EC2 Subnet (%s) attribute: %w", d.Id(), err)
}
}
//Firstly we have to disassociate the old IPv6 CIDR Block
disassociateOps := &ec2.DisassociateSubnetCidrBlockInput{
AssociationId: aws.String(v.(string)),
Expand All @@ -287,49 +304,48 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error {
}

// Wait for the CIDR to become disassociated
log.Printf(
"[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated",
d.Id())
log.Printf("[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"disassociating", "associated"},
Target: []string{"disassociated"},
Pending: []string{ec2.SubnetCidrBlockStateCodeDisassociating, ec2.SubnetCidrBlockStateCodeAssociated},
Target: []string{ec2.SubnetCidrBlockStateCodeDisassociated},
Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_cidr_block_association_id").(string)),
Timeout: 3 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for IPv6 CIDR (%s) to become disassociated: %s",
d.Id(), err)
return fmt.Errorf("Error waiting for IPv6 CIDR (%s) to become disassociated: %w", d.Id(), err)
}
}

//Now we need to try and associate the new CIDR block
associatesOpts := &ec2.AssociateSubnetCidrBlockInput{
SubnetId: aws.String(d.Id()),
Ipv6CidrBlock: aws.String(new.(string)),
}
if newIpv6 != "" {
//Now we need to try to associate the new CIDR block
associatesOpts := &ec2.AssociateSubnetCidrBlockInput{
SubnetId: aws.String(d.Id()),
Ipv6CidrBlock: aws.String(newIpv6),
}

resp, err := conn.AssociateSubnetCidrBlock(associatesOpts)
if err != nil {
//The big question here is, do we want to try and reassociate the old one??
//If we have a failure here, then we may be in a situation that we have nothing associated
return err
}
resp, err := conn.AssociateSubnetCidrBlock(associatesOpts)
if err != nil {
//The big question here is, do we want to try to reassociate the old one??
//If we have a failure here, then we may be in a situation that we have nothing associated
return fmt.Errorf("error associating EC2 Subnet (%s) CIDR block: %w", d.Id(), err)
}

// Wait for the CIDR to become associated
log.Printf(
"[DEBUG] Waiting for IPv6 CIDR (%s) to become associated",
d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{ec2.SubnetCidrBlockStateCodeAssociating, ec2.SubnetCidrBlockStateCodeDisassociated},
Target: []string{ec2.SubnetCidrBlockStateCodeAssociated},
Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), aws.StringValue(resp.Ipv6CidrBlockAssociation.AssociationId)),
Timeout: 3 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for IPv6 CIDR (%s) to become associated: %w",
d.Id(), err)
}

// Wait for the CIDR to become associated
log.Printf(
"[DEBUG] Waiting for IPv6 CIDR (%s) to become associated",
d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"associating", "disassociated"},
Target: []string{"associated"},
Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId),
Timeout: 3 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for IPv6 CIDR (%s) to become associated: %s",
d.Id(), err)
}
}

Expand Down Expand Up @@ -359,7 +375,7 @@ func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[INFO] Deleting subnet: %s", d.Id())

if err := deleteLingeringLambdaENIs(conn, "subnet-id", d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil {
return fmt.Errorf("error deleting Lambda ENIs using subnet (%s): %s", d.Id(), err)
return fmt.Errorf("error deleting Lambda ENIs using subnet (%s): %w", d.Id(), err)
}

req := &ec2.DeleteSubnetInput{
Expand All @@ -374,16 +390,13 @@ func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error {
Refresh: func() (interface{}, string, error) {
_, err := conn.DeleteSubnet(req)
if err != nil {
if apiErr, ok := err.(awserr.Error); ok {
if apiErr.Code() == "DependencyViolation" {
// There is some pending operation, so just retry
// in a bit.
return 42, "pending", nil
}

if apiErr.Code() == "InvalidSubnetID.NotFound" {
return 42, "destroyed", nil
}
if isAWSErr(err, "DependencyViolation", "") {
// There is some pending operation, so just retry
// in a bit.
return 42, "pending", nil
}
if isAWSErr(err, "InvalidSubnetID.NotFound", "") {
return 42, "destroyed", nil
}

return 42, "failure", err
Expand All @@ -394,7 +407,7 @@ func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error {
}

if _, err := wait.WaitForState(); err != nil {
return fmt.Errorf("error deleting subnet (%s): %s", d.Id(), err)
return fmt.Errorf("error deleting subnet (%s): %w", d.Id(), err)
}

return nil
Expand All @@ -407,7 +420,7 @@ func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc
SubnetIds: []*string{aws.String(id)},
})
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
if isAWSErr(err, "InvalidSubnetID.NotFound", "") {
resp = nil
} else {
log.Printf("Error on SubnetStateRefresh: %s", err)
Expand All @@ -422,7 +435,7 @@ func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc
}

subnet := resp.Subnets[0]
return subnet, *subnet.State, nil
return subnet, aws.StringValue(subnet.State), nil
}
}

Expand All @@ -433,7 +446,7 @@ func SubnetIpv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId stri
}
resp, err := conn.DescribeSubnets(opts)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
if isAWSErr(err, "InvalidSubnetID.NotFound", "") {
resp = nil
} else {
log.Printf("Error on SubnetIpv6CidrStateRefreshFunc: %s", err)
Expand All @@ -452,8 +465,8 @@ func SubnetIpv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId stri
}

for _, association := range resp.Subnets[0].Ipv6CidrBlockAssociationSet {
if *association.AssociationId == associationId {
return association, *association.Ipv6CidrBlockState.State, nil
if aws.StringValue(association.AssociationId) == associationId {
return association, aws.StringValue(association.Ipv6CidrBlockState.State), nil
}
}

Expand Down
Loading

0 comments on commit 136cafe

Please sign in to comment.