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

New resource: aws_network_interface_sg_attachment #860

Merged
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ func Provider() terraform.ResourceProvider {
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_network_interface_sg_attachment": resourceAwsNetworkInterfaceSGAttachment(),
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_simpledb_domain": resourceAwsSimpleDBDomain(),
Expand Down
183 changes: 183 additions & 0 deletions aws/resource_aws_network_interface_sg_attachment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package aws

import (
"fmt"
"log"
"reflect"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsNetworkInterfaceSGAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceAwsNetworkInterfaceSGAttachmentCreate,
Read: resourceAwsNetworkInterfaceSGAttachmentRead,
Delete: resourceAwsNetworkInterfaceSGAttachmentDelete,
Schema: map[string]*schema.Schema{
"security_group_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"network_interface_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceAwsNetworkInterfaceSGAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
// Get a lock to prevent races on other SG attachments/detatchments on this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detatchments -> detachments

// interface ID. This lock is released when the function exits, regardless of
// success or failure.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think when the lock is released is obvious from the code below, so the second sentence seems a bit redundant. Why (the 1st sentence) is more important then what.

//
// The lock here - in the create function - deliberately covers the
// post-creation read as well, which is normally not covered as Read is
// otherwise only performed on refresh. Locking on it here prevents
// inconsistencies that could be caused by other attachments that will be
// operating on the interface, ensuring that Create gets a full lay of the
// land before moving on.
mk := "network_interface_sg_attachment_" + d.Get("network_interface_id").(string)
awsMutexKV.Lock(mk)
defer awsMutexKV.Unlock(mk)

sgID := d.Get("security_group_id").(string)
interfaceID := d.Get("network_interface_id").(string)

conn := meta.(*AWSClient).ec2conn

// Fetch the network interface we will be working with.
iface, err := fetchNetworkInterface(conn, interfaceID)
if err != nil {
return err
}

// Add the security group to the network interface.
log.Printf("[DEBUG] Attaching security group %s to network interface ID %s", sgID, interfaceID)

if sgExistsInENI(sgID, iface) {
return fmt.Errorf("security group %s already attached to interface ID %s", sgID, *iface.NetworkInterfaceId)
}
var groupIDs []string
for _, v := range iface.Groups {
groupIDs = append(groupIDs, *v.GroupId)
}
groupIDs = append(groupIDs, sgID)
params := &ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: iface.NetworkInterfaceId,
Groups: aws.StringSlice(groupIDs),
}

_, err = conn.ModifyNetworkInterfaceAttribute(params)
if err != nil {
return err
}

log.Printf("[DEBUG] Successful attachment of security group %s to network interface ID %s", sgID, interfaceID)

return resourceAwsNetworkInterfaceSGAttachmentRead(d, meta)
}

func resourceAwsNetworkInterfaceSGAttachmentRead(d *schema.ResourceData, meta interface{}) error {
sgID := d.Get("security_group_id").(string)
interfaceID := d.Get("network_interface_id").(string)

log.Printf("[DEBUG] Checking association of security group %s to network interface ID %s", sgID, interfaceID)

conn := meta.(*AWSClient).ec2conn

iface, err := fetchNetworkInterface(conn, interfaceID)
if err != nil {
return err
}

if sgExistsInENI(sgID, iface) {
d.SetId(fmt.Sprintf("%s_%s", sgID, interfaceID))
} else {
// The assocation does not exist when it should, taint this resource.
log.Printf("[WARN] Security group %s not associated with network interface ID %s, tainting", sgID, interfaceID)
d.SetId("")
}
return nil
}

func resourceAwsNetworkInterfaceSGAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
// Get a lock to prevent races on other SG attachments/detatchments on this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detatchments -> detachments

// interface ID. This lock is released when the function exits, regardless of
// success or failure.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think when the lock is released is obvious from the code below, so the second sentence seems a bit redundant.

mk := "network_interface_sg_attachment_" + d.Get("network_interface_id").(string)
awsMutexKV.Lock(mk)
defer awsMutexKV.Unlock(mk)

sgID := d.Get("security_group_id").(string)
interfaceID := d.Get("network_interface_id").(string)

log.Printf("[DEBUG] Removing security group %s from interface ID %s", sgID, interfaceID)

conn := meta.(*AWSClient).ec2conn

iface, err := fetchNetworkInterface(conn, interfaceID)
if err != nil {
return err
}

if err := delSGFromENI(conn, sgID, iface); err != nil {
return err
}

d.SetId("")
return nil
}

// fetchNetworkInterface is a utility function used by Read and Delete to fetch
// the full ENI details for a specific interface ID.
func fetchNetworkInterface(conn *ec2.EC2, ifaceID string) (*ec2.NetworkInterface, error) {
log.Printf("[DEBUG] Fetching information for interface ID %s", ifaceID)
dniParams := &ec2.DescribeNetworkInterfacesInput{
NetworkInterfaceIds: aws.StringSlice([]string{ifaceID}),
}

dniResp, err := conn.DescribeNetworkInterfaces(dniParams)
if err != nil {
return nil, err
}
return dniResp.NetworkInterfaces[0], nil
}

func delSGFromENI(conn *ec2.EC2, sgID string, iface *ec2.NetworkInterface) error {
old := iface.Groups
var new []*string
for _, v := range iface.Groups {
if *v.GroupId == sgID {
continue
}
new = append(new, v.GroupId)
}
if reflect.DeepEqual(old, new) {
// The interface already didn't have the security group, nothing to do
return nil
}

params := &ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: iface.NetworkInterfaceId,
Groups: new,
}

_, err := conn.ModifyNetworkInterfaceAttribute(params)
return err
}

// sgExistsInENI is a utility function that can be used to quickly check to
// see if a security group exists in an *ec2.NetworkInterface.
func sgExistsInENI(sgID string, iface *ec2.NetworkInterface) bool {
for _, v := range iface.Groups {
if *v.GroupId == sgID {
return true
}
}
return false
}
Loading