diff --git a/aws/dx_vif.go b/aws/dx_vif.go index ebf0a54e045..e610c38f5b7 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -14,7 +14,7 @@ import ( 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) + return nil, fmt.Errorf("error reading Direct Connect virtual interface (%s): %s", id, err) } if state == directconnect.VirtualInterfaceStateDeleted { return nil, nil @@ -26,26 +26,21 @@ func dxVirtualInterfaceRead(id string, conn *directconnect.DirectConnect) (*dire func dxVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - req := &directconnect.UpdateVirtualInterfaceAttributesInput{ - VirtualInterfaceId: aws.String(d.Id()), - } - - requestUpdate := false if d.HasChange("mtu") { - req.Mtu = aws.Int64(int64(d.Get("mtu").(int))) - requestUpdate = true - } + req := &directconnect.UpdateVirtualInterfaceAttributesInput{ + Mtu: aws.Int64(int64(d.Get("mtu").(int))), + VirtualInterfaceId: aws.String(d.Id()), + } - if requestUpdate { log.Printf("[DEBUG] Modifying Direct Connect virtual interface attributes: %#v", req) _, err := conn.UpdateVirtualInterfaceAttributes(req) if err != nil { - return fmt.Errorf("Error modifying Direct Connect virtual interface (%s) attributes, error: %s", d.Id(), err) + return fmt.Errorf("error modifying Direct Connect virtual interface (%s) attributes, error: %s", d.Id(), err) } } if err := setTagsDX(conn, d, d.Get("arn").(string)); err != nil { - return err + return fmt.Errorf("error setting Direct Connect virtual interface (%s) tags: %s", d.Id(), err) } return nil @@ -62,7 +57,7 @@ func dxVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { if isAWSErr(err, directconnect.ErrCodeClientException, "does not exist") { return nil } - return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err) + return fmt.Errorf("error deleting Direct Connect virtual interface (%s): %s", d.Id(), err) } deleteStateConf := &resource.StateChangeConf{ @@ -85,7 +80,7 @@ func dxVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { } _, 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 fmt.Errorf("error waiting for Direct Connect virtual interface (%s) to be deleted: %s", d.Id(), err) } return nil @@ -125,7 +120,7 @@ func dxVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vif 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", vifId, err) + return fmt.Errorf("error waiting for Direct Connect virtual interface (%s) to become available: %s", vifId, err) } return nil diff --git a/aws/provider.go b/aws/provider.go index 802c49b1dc5..a9efe4b41ed 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -441,6 +441,7 @@ func Provider() terraform.ResourceProvider { "aws_dx_lag": resourceAwsDxLag(), "aws_dx_private_virtual_interface": resourceAwsDxPrivateVirtualInterface(), "aws_dx_public_virtual_interface": resourceAwsDxPublicVirtualInterface(), + "aws_dx_transit_virtual_interface": resourceAwsDxTransitVirtualInterface(), "aws_dynamodb_table": resourceAwsDynamoDbTable(), "aws_dynamodb_table_item": resourceAwsDynamoDbTableItem(), "aws_dynamodb_global_table": resourceAwsDynamoDbGlobalTable(), diff --git a/aws/resource_aws_dx_transit_virtual_interface.go b/aws/resource_aws_dx_transit_virtual_interface.go new file mode 100644 index 00000000000..9f7dab01563 --- /dev/null +++ b/aws/resource_aws_dx_transit_virtual_interface.go @@ -0,0 +1,228 @@ +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/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsDxTransitVirtualInterface() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDxTransitVirtualInterfaceCreate, + Read: resourceAwsDxTransitVirtualInterfaceRead, + Update: resourceAwsDxTransitVirtualInterfaceUpdate, + Delete: resourceAwsDxTransitVirtualInterfaceDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsDxTransitVirtualInterfaceImport, + }, + + Schema: map[string]*schema.Schema{ + "address_family": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + directconnect.AddressFamilyIpv4, + directconnect.AddressFamilyIpv6, + }, false), + }, + "amazon_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "aws_device": { + Type: schema.TypeString, + Computed: true, + }, + "bgp_asn": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "bgp_auth_key": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "connection_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "customer_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "dx_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "jumbo_frame_capable": { + Type: schema.TypeBool, + Computed: true, + }, + "mtu": { + Type: schema.TypeInt, + Default: 1500, + Optional: true, + ValidateFunc: validation.IntInSlice([]int{1500, 8500}), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tags": tagsSchema(), + "vlan": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 4094), + }, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + } +} + +func resourceAwsDxTransitVirtualInterfaceCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).dxconn + + req := &directconnect.CreateTransitVirtualInterfaceInput{ + ConnectionId: aws.String(d.Get("connection_id").(string)), + NewTransitVirtualInterface: &directconnect.NewTransitVirtualInterface{ + AddressFamily: aws.String(d.Get("address_family").(string)), + Asn: aws.Int64(int64(d.Get("bgp_asn").(int))), + DirectConnectGatewayId: aws.String(d.Get("dx_gateway_id").(string)), + Mtu: aws.Int64(int64(d.Get("mtu").(int))), + VirtualInterfaceName: aws.String(d.Get("name").(string)), + Vlan: aws.Int64(int64(d.Get("vlan").(int))), + }, + } + if v, ok := d.GetOk("amazon_address"); ok && v.(string) != "" { + req.NewTransitVirtualInterface.AmazonAddress = aws.String(v.(string)) + } + if v, ok := d.GetOk("bgp_auth_key"); ok { + req.NewTransitVirtualInterface.AuthKey = aws.String(v.(string)) + } + if v, ok := d.GetOk("customer_address"); ok && v.(string) != "" { + req.NewTransitVirtualInterface.CustomerAddress = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Direct Connect transit virtual interface: %#v", req) + resp, err := conn.CreateTransitVirtualInterface(req) + if err != nil { + return fmt.Errorf("error creating Direct Connect transit virtual interface: %s", err) + } + + d.SetId(aws.StringValue(resp.VirtualInterface.VirtualInterfaceId)) + 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) + + if err := dxTransitVirtualInterfaceWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return err + } + + return resourceAwsDxTransitVirtualInterfaceUpdate(d, meta) +} + +func resourceAwsDxTransitVirtualInterfaceRead(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 transit virtual interface (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("address_family", vif.AddressFamily) + d.Set("amazon_address", vif.AmazonAddress) + d.Set("aws_device", vif.AwsDeviceV2) + d.Set("bgp_asn", vif.Asn) + d.Set("bgp_auth_key", vif.AuthKey) + d.Set("connection_id", vif.ConnectionId) + d.Set("customer_address", vif.CustomerAddress) + d.Set("dx_gateway_id", vif.DirectConnectGatewayId) + d.Set("jumbo_frame_capable", vif.JumboFrameCapable) + d.Set("mtu", vif.Mtu) + d.Set("name", vif.VirtualInterfaceName) + d.Set("vlan", vif.Vlan) + if err := getTagsDX(conn, d, d.Get("arn").(string)); err != nil { + return fmt.Errorf("error getting Direct Connect transit virtual interface (%s) tags: %s", d.Id(), err) + } + + return nil +} + +func resourceAwsDxTransitVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { + if err := dxVirtualInterfaceUpdate(d, meta); err != nil { + return err + } + + if err := dxTransitVirtualInterfaceWaitUntilAvailable(meta.(*AWSClient).dxconn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return err + } + + return resourceAwsDxTransitVirtualInterfaceRead(d, meta) +} + +func resourceAwsDxTransitVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { + return dxVirtualInterfaceDelete(d, meta) +} + +func resourceAwsDxTransitVirtualInterfaceImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + 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 []*schema.ResourceData{d}, nil +} + +func dxTransitVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { + return dxVirtualInterfaceWaitUntilAvailable( + conn, + vifId, + timeout, + []string{ + directconnect.VirtualInterfaceStatePending, + }, + []string{ + directconnect.VirtualInterfaceStateAvailable, + directconnect.VirtualInterfaceStateDown, + }) +} diff --git a/aws/resource_aws_dx_transit_virtual_interface_test.go b/aws/resource_aws_dx_transit_virtual_interface_test.go new file mode 100644 index 00000000000..140fdc479de --- /dev/null +++ b/aws/resource_aws_dx_transit_virtual_interface_test.go @@ -0,0 +1,140 @@ +package aws + +import ( + "fmt" + "os" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsDxTransitVirtualInterface_basic(t *testing.T) { + key := "DX_CONNECTION_ID" + connectionId := os.Getenv(key) + if connectionId == "" { + t.Skipf("Environment variable %s is not set", key) + } + + resourceName := "aws_dx_transit_virtual_interface.test" + rName := fmt.Sprintf("tf-testacc-transit-vif-%s", acctest.RandString(9)) + amzAsn := randIntRange(64512, 65534) + bgpAsn := randIntRange(64512, 65534) + vlan := randIntRange(2049, 4094) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxTransitVirtualInterfaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxTransitVirtualInterfaceConfig_basic(connectionId, rName, amzAsn, bgpAsn, vlan), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxTransitVirtualInterfaceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "mtu", "1500"), + resource.TestCheckResourceAttr(resourceName, "jumbo_frame_capable", "true"), + ), + }, + { + Config: testAccDxTransitVirtualInterfaceConfig_updated(connectionId, rName, amzAsn, bgpAsn, vlan), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxTransitVirtualInterfaceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Environment", "test"), + resource.TestCheckResourceAttr(resourceName, "mtu", "8500"), + ), + }, + // Test import. + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsDxTransitVirtualInterfaceDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).dxconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_dx_transit_virtual_interface" { + continue + } + + input := &directconnect.DescribeVirtualInterfacesInput{ + VirtualInterfaceId: aws.String(rs.Primary.ID), + } + + resp, err := conn.DescribeVirtualInterfaces(input) + if err != nil { + return err + } + for _, v := range resp.VirtualInterfaces { + if *v.VirtualInterfaceId == rs.Primary.ID && !(*v.VirtualInterfaceState == directconnect.VirtualInterfaceStateDeleted) { + return fmt.Errorf("[DESTROY ERROR] Dx Transit VIF (%s) not deleted", rs.Primary.ID) + } + } + } + return nil +} + +func testAccCheckAwsDxTransitVirtualInterfaceExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + return nil + } +} + +func testAccDxTransitVirtualInterfaceConfig_basic(cid, rName string, amzAsn, bgpAsn, vlan int) string { + return fmt.Sprintf(` +resource "aws_dx_gateway" "test" { + name = %[2]q + amazon_side_asn = %[3]d +} + +resource "aws_dx_transit_virtual_interface" "test" { + connection_id = %[1]q + + dx_gateway_id = "${aws_dx_gateway.test.id}" + name = %[2]q + vlan = %[5]d + address_family = "ipv4" + bgp_asn = %[4]d +} +`, cid, rName, amzAsn, bgpAsn, vlan) +} + +func testAccDxTransitVirtualInterfaceConfig_updated(cid, rName string, amzAsn, bgpAsn, vlan int) string { + return fmt.Sprintf(` +resource "aws_dx_gateway" "test" { + name = %[2]q + amazon_side_asn = %[3]d +} + +resource "aws_dx_transit_virtual_interface" "test" { + connection_id = %[1]q + + dx_gateway_id = "${aws_dx_gateway.test.id}" + name = %[2]q + vlan = %[5]d + address_family = "ipv4" + bgp_asn = %[4]d + mtu = 8500 + + tags = { + Environment = "test" + } +} +`, cid, rName, amzAsn, bgpAsn, vlan) +} diff --git a/website/aws.erb b/website/aws.erb index eae02116af9..0590ef7616b 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -876,6 +876,9 @@