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

r/subnet - Fix removing ipv6 cidr block from subnet #12303

Merged
merged 15 commits into from
Aug 20, 2020
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