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_dx_private_virtual_interface #3253

Merged
merged 7 commits into from
Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions aws/dx_vif.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/directconnect"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func dxVirtualInterfaceRead(id string, conn *directconnect.DirectConnect) (*directconnect.VirtualInterface, error) {
resp, state, err := dxVirtualInterfaceStateRefresh(conn, id)()
if err != nil {
return nil, fmt.Errorf("Error reading Direct Connect virtual interface: %s", err)
}
if state == directconnect.VirtualInterfaceStateDeleted {
return nil, nil
}

return resp.(*directconnect.VirtualInterface), nil
}

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

arn := arn.ARN{
Copy link
Contributor

Choose a reason for hiding this comment

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

We should prefer to create the arn manually once during create or read, set it in the Terraform state via d.Set("arn", ...), then use d.Get("arn").(string) or pass by value as necessary.

Once that's done, this function basically becomes nothing and the setTagsDX() bits can be duplicated where necessary to remove the dxVirtualInterfaceUpdate function. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I'll Set the ARN after creation, once we have the Id and Get it elsewhere.

Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Service: "directconnect",
AccountID: meta.(*AWSClient).accountid,
Resource: fmt.Sprintf("dxvif/%s", d.Id()),
}.String()
if err := setTagsDX(conn, d, arn); err != nil {
return err
}

return nil
}

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

log.Printf("[DEBUG] Deleting Direct Connect virtual interface: %s", d.Id())
_, err := conn.DeleteVirtualInterface(&directconnect.DeleteVirtualInterfaceInput{
VirtualInterfaceId: aws.String(d.Id()),
})
if err != nil {
if isAWSErr(err, "DirectConnectClientException", "does not exist") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nitpick: looks like the SDK has an available constant for this: directconnect.ErrCodeClientException

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

return nil
}
return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err)
}

deleteStateConf := &resource.StateChangeConf{
Pending: []string{
directconnect.VirtualInterfaceStateAvailable,
directconnect.VirtualInterfaceStateConfirming,
directconnect.VirtualInterfaceStateDeleting,
directconnect.VirtualInterfaceStateDown,
directconnect.VirtualInterfaceStatePending,
directconnect.VirtualInterfaceStateRejected,
directconnect.VirtualInterfaceStateVerifying,
},
Target: []string{
directconnect.VirtualInterfaceStateDeleted,
},
Refresh: dxVirtualInterfaceStateRefresh(conn, d.Id()),
Timeout: d.Timeout(schema.TimeoutDelete),
Delay: 10 * time.Second,
MinTimeout: 5 * time.Second,
}
_, err = deleteStateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for Direct Connect virtual interface (%s) to be deleted: %s", d.Id(), err)
}

return nil
}

func dxVirtualInterfaceStateRefresh(conn *directconnect.DirectConnect, vifId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeVirtualInterfaces(&directconnect.DescribeVirtualInterfacesInput{
VirtualInterfaceId: aws.String(vifId),
})
if err != nil {
return nil, "", err
}

n := len(resp.VirtualInterfaces)
switch n {
case 0:
return "", directconnect.VirtualInterfaceStateDeleted, nil

case 1:
vif := resp.VirtualInterfaces[0]
return vif, aws.StringValue(vif.VirtualInterfaceState), nil

default:
return nil, "", fmt.Errorf("Found %d Direct Connect virtual interfaces for %s, expected 1", n, vifId)
}
}
}

func dxVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect, pending, target []string) error {
stateConf := &resource.StateChangeConf{
Pending: pending,
Target: target,
Refresh: dxVirtualInterfaceStateRefresh(conn, d.Id()),
Timeout: d.Timeout(schema.TimeoutCreate),
Delay: 10 * time.Second,
MinTimeout: 5 * time.Second,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for Direct Connect virtual interface (%s) to become available: %s", d.Id(), err)
}

return nil
}

// Attributes common to public VIFs and creator side of hosted public VIFs.
func dxPublicVirtualInterfaceAttributes(d *schema.ResourceData, meta interface{}, vif *directconnect.VirtualInterface) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

We generally prefer this logic to be duplicated across resources as necessary for long-term maintainability. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I was thinking of that after removing mergeSchemas...

if err := dxVirtualInterfaceAttributes(d, meta, vif); err != nil {
return err
}
d.Set("route_filter_prefixes", flattenDxRouteFilterPrefixes(vif.RouteFilterPrefixes))

return nil
}

// Attributes common to private VIFs and creator side of hosted private VIFs.
func dxPrivateVirtualInterfaceAttributes(d *schema.ResourceData, meta interface{}, vif *directconnect.VirtualInterface) error {
return dxVirtualInterfaceAttributes(d, meta, vif)
}

// Attributes common to public/private VIFs and creator side of hosted public/private VIFs.
func dxVirtualInterfaceAttributes(d *schema.ResourceData, meta interface{}, vif *directconnect.VirtualInterface) error {
if err := dxVirtualInterfaceArnAttribute(d, meta); err != nil {
return err
}

d.Set("connection_id", vif.ConnectionId)
d.Set("name", vif.VirtualInterfaceName)
d.Set("vlan", vif.Vlan)
d.Set("bgp_asn", vif.Asn)
d.Set("bgp_auth_key", vif.AuthKey)
d.Set("address_family", vif.AddressFamily)
d.Set("customer_address", vif.CustomerAddress)
d.Set("amazon_address", vif.AmazonAddress)

return nil
}

func dxVirtualInterfaceArnAttribute(d *schema.ResourceData, meta interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Potential nitpick: this indirection to save two lines of code seems extraneous when it can just be duplicated to the relevant resources

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll consolidate and inline all the attribute setting code directly into the resource's Read method.

arn := arn.ARN{
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Service: "directconnect",
AccountID: meta.(*AWSClient).accountid,
Resource: fmt.Sprintf("dxvif/%s", d.Id()),
}.String()
d.Set("arn", arn)

return nil
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ func Provider() terraform.ResourceProvider {
"aws_dx_connection_association": resourceAwsDxConnectionAssociation(),
"aws_dx_gateway": resourceAwsDxGateway(),
"aws_dx_gateway_association": resourceAwsDxGatewayAssociation(),
"aws_dx_private_virtual_interface": resourceAwsDxPrivateVirtualInterface(),
"aws_dynamodb_table": resourceAwsDynamoDbTable(),
"aws_dynamodb_table_item": resourceAwsDynamoDbTableItem(),
"aws_dynamodb_global_table": resourceAwsDynamoDbGlobalTable(),
Expand Down
194 changes: 194 additions & 0 deletions aws/resource_aws_dx_private_virtual_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
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/schema"
"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsDxPrivateVirtualInterface() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDxPrivateVirtualInterfaceCreate,
Read: resourceAwsDxPrivateVirtualInterfaceRead,
Update: resourceAwsDxPrivateVirtualInterfaceUpdate,
Delete: resourceAwsDxPrivateVirtualInterfaceDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"connection_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"vpn_gateway_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"dx_gateway_id"},
},
"dx_gateway_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"vpn_gateway_id"},
},
"vlan": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
ValidateFunc: validation.IntBetween(1, 4094),
},
"bgp_asn": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"bgp_auth_key": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"address_family": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{directconnect.AddressFamilyIpv4, directconnect.AddressFamilyIpv6}, false),
},
"customer_address": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"amazon_address": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"tags": tagsSchema(),
},

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

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

vgwIdRaw, vgwOk := d.GetOk("vpn_gateway_id")
dxgwIdRaw, dxgwOk := d.GetOk("dx_gateway_id")
if vgwOk == dxgwOk {
return fmt.Errorf(
"One of ['vpn_gateway_id', 'dx_gateway_id'] must be set to create a Direct Connect private virtual interface")
}

req := &directconnect.CreatePrivateVirtualInterfaceInput{
ConnectionId: aws.String(d.Get("connection_id").(string)),
NewPrivateVirtualInterface: &directconnect.NewPrivateVirtualInterface{
VirtualInterfaceName: aws.String(d.Get("name").(string)),
Vlan: aws.Int64(int64(d.Get("vlan").(int))),
Asn: aws.Int64(int64(d.Get("bgp_asn").(int))),
AddressFamily: aws.String(d.Get("address_family").(string)),
},
}
if vgwOk {
Copy link
Contributor

Choose a reason for hiding this comment

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

For easier module support should this, and the other d.GetOk() conditionals below, also check for != ""?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

req.NewPrivateVirtualInterface.VirtualGatewayId = aws.String(vgwIdRaw.(string))
}
if dxgwOk {
req.NewPrivateVirtualInterface.DirectConnectGatewayId = aws.String(dxgwIdRaw.(string))
}
if v, ok := d.GetOk("bgp_auth_key"); ok {
req.NewPrivateVirtualInterface.AuthKey = aws.String(v.(string))
}
if v, ok := d.GetOk("customer_address"); ok {
req.NewPrivateVirtualInterface.CustomerAddress = aws.String(v.(string))
}
if v, ok := d.GetOk("amazon_address"); ok {
req.NewPrivateVirtualInterface.AmazonAddress = aws.String(v.(string))
}

log.Printf("[DEBUG] Creating Direct Connect private virtual interface: %#v", req)
resp, err := conn.CreatePrivateVirtualInterface(req)
if err != nil {
return fmt.Errorf("Error creating Direct Connect private virtual interface: %s", err.Error())
}

d.SetId(aws.StringValue(resp.VirtualInterfaceId))

if err := dxPrivateVirtualInterfaceWaitUntilAvailable(d, conn); err != nil {
return err
}

return resourceAwsDxPrivateVirtualInterfaceUpdate(d, meta)
}

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

vif, err := dxVirtualInterfaceRead(d.Id(), conn)
if err != nil {
return err
}
if vif == nil {
log.Printf("[WARN] Direct Connect virtual interface (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err := dxPrivateVirtualInterfaceAttributes(d, meta, vif); err != nil {
return err
}
d.Set("vpn_gateway_id", vif.VirtualGatewayId)
d.Set("dx_gateway_id", vif.DirectConnectGatewayId)
if err := getTagsDX(conn, d, d.Get("arn").(string)); err != nil {
return err
}

return nil
}

func resourceAwsDxPrivateVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error {
if err := dxVirtualInterfaceUpdate(d, meta); err != nil {
return err
}

return resourceAwsDxPrivateVirtualInterfaceRead(d, meta)
}

func resourceAwsDxPrivateVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error {
return dxVirtualInterfaceDelete(d, meta)
}

func dxPrivateVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error {
return dxVirtualInterfaceWaitUntilAvailable(
d,
conn,
[]string{
directconnect.VirtualInterfaceStatePending,
},
[]string{
directconnect.VirtualInterfaceStateAvailable,
directconnect.VirtualInterfaceStateDown,
})
}
Loading