Skip to content

Commit

Permalink
Merge branch 'issue-10044' of ssh://github.com/ewbankkit/terraform-pr…
Browse files Browse the repository at this point in the history
…ovider-aws into ewbankkit-issue-10044
  • Loading branch information
bflad committed Oct 1, 2019
2 parents 688c305 + 0257db7 commit 8cf35a5
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 80 deletions.
125 changes: 125 additions & 0 deletions aws/resource_aws_network_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import (
"github.com/hashicorp/terraform/helper/schema"
)

const (
networkInterfaceStatusDeleted = "deleted"
)

func resourceAwsNetworkInterface() *schema.Resource {
return &schema.Resource{
Create: resourceAwsNetworkInterfaceCreate,
Expand Down Expand Up @@ -444,3 +448,124 @@ func resourceAwsEniAttachmentHash(v interface{}) int {
buf.WriteString(fmt.Sprintf("%d-", m["device_index"].(int)))
return hashcode.String(buf.String())
}

func deleteNetworkInterface(conn *ec2.EC2, eniId string) error {
_, err := conn.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{
NetworkInterfaceId: aws.String(eniId),
})

if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") {
return nil
}

if err != nil {
return fmt.Errorf("error deleting ENI (%s): %s", eniId, err)
}

return nil
}

func detachNetworkInterface(conn *ec2.EC2, eni *ec2.NetworkInterface, timeout time.Duration) error {
eniId := aws.StringValue(eni.NetworkInterfaceId)
if eni.Attachment == nil {
log.Printf("[DEBUG] ENI %s is already detached", eniId)
return nil
}

_, err := conn.DetachNetworkInterface(&ec2.DetachNetworkInterfaceInput{
AttachmentId: eni.Attachment.AttachmentId,
Force: aws.Bool(true),
})

if isAWSErr(err, "InvalidAttachmentID.NotFound", "") {
return nil
}

if err != nil {
return fmt.Errorf("error detaching ENI (%s): %s", eniId, err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{
ec2.AttachmentStatusAttaching,
ec2.AttachmentStatusAttached,
ec2.AttachmentStatusDetaching,
},
Target: []string{
ec2.AttachmentStatusDetached,
},
Refresh: networkInterfaceAttachmentStateRefresh(conn, eniId),
Timeout: timeout,
Delay: 10 * time.Second,
MinTimeout: 5 * time.Second,
}

log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", eniId)
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("error waiting for ENI (%s) to become detached: %s", eniId, err)
}

return nil
}

func networkInterfaceAttachmentStateRefresh(conn *ec2.EC2, eniId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: aws.StringSlice([]string{eniId}),
})

if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") {
return "", ec2.AttachmentStatusDetached, nil
}

if err != nil {
return nil, "", fmt.Errorf("error describing ENI (%s): %s", eniId, err)
}

n := len(resp.NetworkInterfaces)
switch n {
case 0:
return "", ec2.AttachmentStatusDetached, nil

case 1:
attachment := resp.NetworkInterfaces[0].Attachment
if attachment == nil {
return "", ec2.AttachmentStatusDetached, nil
}
return attachment, aws.StringValue(attachment.Status), nil

default:
return nil, "", fmt.Errorf("found %d ENIs for %s, expected 1", n, eniId)
}
}
}

func networkInterfaceStateRefresh(conn *ec2.EC2, eniId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: aws.StringSlice([]string{eniId}),
})

if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") {
return "", networkInterfaceStatusDeleted, nil
}

if err != nil {
return nil, "", fmt.Errorf("error describing ENI (%s): %s", eniId, err)
}

n := len(resp.NetworkInterfaces)
switch n {
case 0:
return "", networkInterfaceStatusDeleted, nil

case 1:
eni := resp.NetworkInterfaces[0]
return eni, aws.StringValue(eni.Status), nil

default:
return nil, "", fmt.Errorf("found %d ENIs for %s, expected 1", n, eniId)
}
}
}
116 changes: 40 additions & 76 deletions aws/resource_aws_security_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func resourceAwsSecurityGroup() *schema.Resource {

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

SchemaVersion: 1,
Expand Down Expand Up @@ -448,8 +448,8 @@ func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) er

log.Printf("[DEBUG] Security Group destroy: %v", d.Id())

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

// conditionally revoke rules first before attempting to delete the group
Expand Down Expand Up @@ -1402,98 +1402,62 @@ func sgProtocolIntegers() map[string]int {

// The AWS Lambda service creates ENIs behind the scenes and keeps these around for a while
// which would prevent SGs attached to such ENIs from being destroyed
func deleteLingeringLambdaENIs(conn *ec2.EC2, d *schema.ResourceData, filterName string) error {
// Here we carefully find the offenders
params := &ec2.DescribeNetworkInterfacesInput{
Filters: []*ec2.Filter{
{
Name: aws.String(filterName),
Values: []*string{aws.String(d.Id())},
},
{
Name: aws.String("description"),
Values: []*string{aws.String("AWS Lambda VPC ENI: *")},
},
},
}
networkInterfaceResp, err := conn.DescribeNetworkInterfaces(params)

if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") {
return nil
}
func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, timeout time.Duration) error {
resp, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
Filters: buildEC2AttributeFilterList(map[string]string{
filterName: resourceId,
"description": "AWS Lambda VPC ENI*",
}),
})

if err != nil {
return err
return fmt.Errorf("error describing ENIs: %s", err)
}

// Then we detach and finally delete those
v := networkInterfaceResp.NetworkInterfaces
for _, eni := range v {
if eni.Attachment != nil {
detachNetworkInterfaceParams := &ec2.DetachNetworkInterfaceInput{
AttachmentId: eni.Attachment.AttachmentId,
}
_, detachNetworkInterfaceErr := conn.DetachNetworkInterface(detachNetworkInterfaceParams)
for _, eni := range resp.NetworkInterfaces {
eniId := aws.StringValue(eni.NetworkInterfaceId)

if isAWSErr(detachNetworkInterfaceErr, "InvalidNetworkInterfaceID.NotFound", "") {
return nil
}

if detachNetworkInterfaceErr != nil {
return detachNetworkInterfaceErr
}

log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", *eni.NetworkInterfaceId)
if eni.Attachment != nil && aws.StringValue(eni.Attachment.InstanceOwnerId) == "amazon-aws" {
// Hyperplane attached ENI.
// Wait for it to be moved into a removable state.
stateConf := &resource.StateChangeConf{
Pending: []string{"true"},
Target: []string{"false"},
Refresh: networkInterfaceAttachedRefreshFunc(conn, *eni.NetworkInterfaceId),
Timeout: d.Timeout(schema.TimeoutDelete),
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for ENI (%s) to become detached: %s", *eni.NetworkInterfaceId, err)
Pending: []string{
ec2.NetworkInterfaceStatusInUse,
},
Target: []string{
ec2.NetworkInterfaceStatusAvailable,
networkInterfaceStatusDeleted,
},
Refresh: networkInterfaceStateRefresh(conn, eniId),
Timeout: timeout,
Delay: 10 * time.Second,
MinTimeout: 5 * time.Second,
ContinuousTargetOccurence: 10,
}
}

deleteNetworkInterfaceParams := &ec2.DeleteNetworkInterfaceInput{
NetworkInterfaceId: eni.NetworkInterfaceId,
}
_, deleteNetworkInterfaceErr := conn.DeleteNetworkInterface(deleteNetworkInterfaceParams)
eniRaw, err := stateConf.WaitForState()

if isAWSErr(deleteNetworkInterfaceErr, "InvalidNetworkInterfaceID.NotFound", "") {
return nil
}
if err != nil {
return fmt.Errorf("error waiting for ENI (%s) to become available: %s", eniId, err)
}

if deleteNetworkInterfaceErr != nil {
return deleteNetworkInterfaceErr
eni = eniRaw.(*ec2.NetworkInterface)
}
}

return nil
}

func networkInterfaceAttachedRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
err = detachNetworkInterface(conn, eni, timeout)

describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: []*string{aws.String(id)},
if err != nil {
return err
}
describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request)

if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") {
return 42, "false", nil
}
err = deleteNetworkInterface(conn, eniId)

if err != nil {
return nil, "", err
return err
}

eni := describeResp.NetworkInterfaces[0]
hasAttachment := strconv.FormatBool(eni.Attachment != nil)
log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment)
return eni, hasAttachment, nil
}

return nil
}

func initSecurityGroupRule(ruleMap map[string]map[string]interface{}, perm *ec2.IpPermission, desc string) map[string]interface{} {
Expand Down
6 changes: 3 additions & 3 deletions aws/resource_aws_subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func resourceAwsSubnet() *schema.Resource {

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(20 * time.Minute),
Delete: schema.DefaultTimeout(30 * time.Minute),
},

SchemaVersion: 1,
Expand Down Expand Up @@ -319,8 +319,8 @@ func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error {

log.Printf("[INFO] Deleting subnet: %s", d.Id())

if err := deleteLingeringLambdaENIs(conn, d, "subnet-id"); err != nil {
return fmt.Errorf("Failed to delete Lambda ENIs: %s", err)
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)
}

req := &ec2.DeleteSubnetInput{
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/security_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ In addition to all arguments above, the following attributes are exported:
configuration options:

- `create` - (Default `10 minutes`) How long to wait for a security group to be created.
- `delete` - (Default `10 minutes`) How long to wait for a security group to be deleted.
- `delete` - (Default `30 minutes`) How long to wait for a security group to be deleted.

## Import

Expand Down
8 changes: 8 additions & 0 deletions website/docs/r/subnet.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ In addition to all arguments above, the following attributes are exported:
* `ipv6_cidr_block_association_id` - The association ID for the IPv6 CIDR block.
* `owner_id` - The ID of the AWS account that owns the subnet.

## Timeouts

`aws_subnet` provides the following [Timeouts](/docs/configuration/resources.html#timeouts)
configuration options:

- `create` - (Default `10 minutes`) How long to wait for a subnet to be created.
- `delete` - (Default `30 minutes`) How long to wait for a subnet to be deleted.

## Import

Subnets can be imported using the `subnet id`, e.g.
Expand Down

0 comments on commit 8cf35a5

Please sign in to comment.