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

Add AWS direct connect virtual interface resources #5212

Closed
wants to merge 11 commits into from
3 changes: 3 additions & 0 deletions builtin/providers/aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/codedeploy"
"github.com/aws/aws-sdk-go/service/directconnect"
"github.com/aws/aws-sdk-go/service/directoryservice"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/ec2"
Expand Down Expand Up @@ -101,6 +102,7 @@ type AWSClient struct {
cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs
cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents
dsconn *directoryservice.DirectoryService
dcconn *directconnect.DirectConnect
dynamodbconn *dynamodb.DynamoDB
ec2conn *ec2.EC2
ecrconn *ecr.ECR
Expand Down Expand Up @@ -258,6 +260,7 @@ func (c *Config) Client() (interface{}, error) {
client.codecommitconn = codecommit.New(usEast1Sess)
client.codedeployconn = codedeploy.New(sess)
client.dsconn = directoryservice.New(sess)
client.dcconn = directconnect.New(sess)
client.dynamodbconn = dynamodb.New(dynamoSess)
client.ec2conn = ec2.New(awsEc2Sess)
client.ecrconn = ecr.New(sess)
Expand Down
3 changes: 3 additions & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ func Provider() terraform.ResourceProvider {
"aws_db_parameter_group": resourceAwsDbParameterGroup(),
"aws_db_security_group": resourceAwsDbSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
"aws_dc_virtual_interface": resourceAwsDirectConnectVirtualInterface(),
"aws_dc_intra_virtual_interface": resourceAwsDirectConnectIntraVirtualInterface(),
"aws_dc_intra_virtual_interface_confirm": resourceAwsDirectConnectIntraVirtualInterfaceConfirm(),
Copy link

Choose a reason for hiding this comment

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

AWS refer to direct connects as 'dx' is a fair amount of their documentation (see: https://aws.amazon.com/directconnect/faqs/)

"aws_directory_service_directory": resourceAwsDirectoryServiceDirectory(),
"aws_dynamodb_table": resourceAwsDynamoDbTable(),
"aws_ebs_volume": resourceAwsEbsVolume(),
Expand Down
285 changes: 285 additions & 0 deletions builtin/providers/aws/resource_aws_dc_intra_virtual_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/directconnect"

"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsDirectConnectIntraVirtualInterface() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDirectConnectIntraVirtualInterfaceCreate,
Read: resourceAwsDirectConnectIntraVirtualInterfaceRead,
Delete: resourceAwsDirectConnectIntraVirtualInterfaceDelete,

Schema: map[string]*schema.Schema{
"connection_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"owner_account_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"interface_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"asn": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},

"virtual_interface_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"vlan": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},

"amazon_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

"customer_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

"auth_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},

"route_filter_prefixes": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
},
}
}

func resourceAwsDirectConnectIntraVirtualInterfaceCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dcconn

var err error
var resp *directconnect.VirtualInterface

if v, ok := d.GetOk("interface_type"); ok && v.(string) == "public" {

createOpts := &directconnect.AllocatePublicVirtualInterfaceInput{
ConnectionId: aws.String(d.Get("connection_id").(string)),
NewPublicVirtualInterfaceAllocation: &directconnect.NewPublicVirtualInterfaceAllocation{
Asn: aws.Int64(int64(d.Get("asn").(int))),
VirtualInterfaceName: aws.String(d.Get("virtual_interface_name").(string)),
Vlan: aws.Int64(int64(d.Get("vlan").(int))),
RouteFilterPrefixes: []*directconnect.RouteFilterPrefix{},
},
OwnerAccount: aws.String(d.Get("owner_account_id").(string)),
}

if v, ok := d.GetOk("amazon_address"); ok {
createOpts.NewPublicVirtualInterfaceAllocation.AmazonAddress = aws.String(v.(string))
}

if v, ok := d.GetOk("auth_key"); ok {
createOpts.NewPublicVirtualInterfaceAllocation.AuthKey = aws.String(v.(string))
}

if v, ok := d.GetOk("customer_address"); ok {
createOpts.NewPublicVirtualInterfaceAllocation.CustomerAddress = aws.String(v.(string))
}

if prefixesSet, ok := d.Get("route_filter_prefixes").(*schema.Set); ok {

for _, cidr := range prefixesSet.List() {
createOpts.NewPublicVirtualInterfaceAllocation.RouteFilterPrefixes = append(createOpts.NewPublicVirtualInterfaceAllocation.RouteFilterPrefixes, &directconnect.RouteFilterPrefix{Cidr: aws.String(cidr.(string))})
}

}

log.Printf("[DEBUG] Creating DirectConnect public virtual interface")
resp, err = conn.AllocatePublicVirtualInterface(createOpts)
if err != nil {
return fmt.Errorf("Error creating DirectConnect Virtual Interface: %s", err)
}

} else {

createOpts := &directconnect.AllocatePrivateVirtualInterfaceInput{
ConnectionId: aws.String(d.Get("connection_id").(string)),
NewPrivateVirtualInterfaceAllocation: &directconnect.NewPrivateVirtualInterfaceAllocation{
Asn: aws.Int64(int64(d.Get("asn").(int))),
VirtualInterfaceName: aws.String(d.Get("virtual_interface_name").(string)),
Vlan: aws.Int64(int64(d.Get("vlan").(int))),
},
OwnerAccount: aws.String(d.Get("owner_account_id").(string)),
}

if v, ok := d.GetOk("amazon_address"); ok {
createOpts.NewPrivateVirtualInterfaceAllocation.AmazonAddress = aws.String(v.(string))
}

if v, ok := d.GetOk("auth_key"); ok {
createOpts.NewPrivateVirtualInterfaceAllocation.AuthKey = aws.String(v.(string))
}

if v, ok := d.GetOk("customer_address"); ok {
createOpts.NewPrivateVirtualInterfaceAllocation.CustomerAddress = aws.String(v.(string))
}

log.Printf("[DEBUG] Creating DirectConnect private virtual interface")
resp, err = conn.AllocatePrivateVirtualInterface(createOpts)
if err != nil {
return fmt.Errorf("Error creating DirectConnect Virtual Interface: %s", err)
}

}

// Store the ID
VirtualInterface := resp
d.SetId(*VirtualInterface.VirtualInterfaceId)
log.Printf("[INFO] VirtualInterface ID: %s", *VirtualInterface.VirtualInterfaceId)

stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: []string{"available", "confirming", "verifying", "pending"},
Refresh: DirectConnectIntraVirtualInterfaceRefreshFunc(conn, *VirtualInterface.VirtualInterfaceId),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 10 * time.Second,
}

_, stateErr := stateConf.WaitForState()
if stateErr != nil {
return fmt.Errorf(
"Error waiting for DirectConnect PrivateVirtualInterface (%s) to become ready: %s",
*VirtualInterface.VirtualInterfaceId, err)
}

// Read off the API to populate our RO fields.
return resourceAwsDirectConnectIntraVirtualInterfaceRead(d, meta)
}

func DirectConnectIntraVirtualInterfaceRefreshFunc(conn *directconnect.DirectConnect, virtualinterfaceId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {

resp, err := conn.DescribeVirtualInterfaces(&directconnect.DescribeVirtualInterfacesInput{
VirtualInterfaceId: aws.String(virtualinterfaceId),
})

if err != nil {

log.Printf("Error on DirectConnectPrivateVirtualInterfaceRefresh: %s", err)
return nil, "", err

}

if resp == nil || len(resp.VirtualInterfaces) == 0 {
return nil, "", nil
}

virtualInterface := resp.VirtualInterfaces[0]
return virtualInterface, *virtualInterface.VirtualInterfaceState, nil
}
}

func resourceAwsDirectConnectIntraVirtualInterfaceRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dcconn

resp, err := conn.DescribeVirtualInterfaces(&directconnect.DescribeVirtualInterfacesInput{
VirtualInterfaceId: aws.String(d.Id()),
})

if err != nil {

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a chance that someone can delete the VirtualInterface from the AWS Console? If so, this will error out. We should handle the case of 404 and then remove from state if necessary

Copy link

Choose a reason for hiding this comment

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

Yes, a VirtualInterface can be deleted from the AWS Console.

log.Printf("[ERROR] Error finding DirectConnect PrivateVirtualInterface: %s", err)
return err

}

vifsCount := len(resp.VirtualInterfaces)

if vifsCount != 1 {
return fmt.Errorf("[ERROR] Error finding DirectConnect PrivateVirtualInterface or unexpected number of %d VirtualInterfaces was returned: %s", vifsCount, d.Id())
}

virtualInterface := resp.VirtualInterfaces[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we guarantee that [0] VirtualInterface is the one we want? Shouldn't we range the list here?


// Set attributes under the user's control.
d.Set("connection_id", virtualInterface.ConnectionId)
d.Set("asn", virtualInterface.Asn)
d.Set("virtual_interface_name", virtualInterface.VirtualInterfaceName)
d.Set("vlan", virtualInterface.Vlan)
d.Set("amazon_address", virtualInterface.AmazonAddress)
d.Set("customer_address", virtualInterface.CustomerAddress)
// d.Set("auth_key", *virtualInterface.AuthKey)
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we not set the auth_key?


// Set read only attributes.
d.Set("owner_account_id", virtualInterface.OwnerAccount)

return nil
}

func resourceAwsDirectConnectIntraVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dcconn

_, err := conn.DeleteVirtualInterface(&directconnect.DeleteVirtualInterfaceInput{
VirtualInterfaceId: aws.String(d.Id()),
})

if err != nil {

log.Printf("[ERROR] Error deleting DirectConnect PrivateVirtualInterface connection: %s", err)
return err

}

stateConf := &resource.StateChangeConf{
Pending: []string{"deleting"},
Target: []string{"deleted"},
Refresh: DirectConnectIntraVirtualInterfaceRefreshFunc(conn, d.Id()),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 10 * time.Second,
}

_, stateErr := stateConf.WaitForState()
if stateErr != nil {
return fmt.Errorf(
"Error waiting for DirectConnect PrivateVirtualInterface (%s) to delete: %s", d.Id(), err)
}

return nil
}
Loading