From 34c1fddaa7e5c3201ea1095b1db42d71d24c0b93 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 15 Jan 2018 19:04:06 -0500 Subject: [PATCH 1/9] Add 'aws_dx_public_virtual_interface' resource. --- aws/dx_vif.go | 256 ++++++++++++++++++ aws/provider.go | 1 + ...esource_aws_dx_public_virtual_interface.go | 140 ++++++++++ ...ce_aws_dx_public_virtual_interface_test.go | 133 +++++++++ aws/utils.go | 13 + aws/utils_test.go | 32 ++- website/aws.erb | 3 + .../dx_public_virtual_interface.html.markdown | 69 +++++ 8 files changed, 646 insertions(+), 1 deletion(-) create mode 100644 aws/dx_vif.go create mode 100644 aws/resource_aws_dx_public_virtual_interface.go create mode 100644 aws/resource_aws_dx_public_virtual_interface_test.go create mode 100644 website/docs/r/dx_public_virtual_interface.html.markdown diff --git a/aws/dx_vif.go b/aws/dx_vif.go new file mode 100644 index 000000000000..903c5ca4fed3 --- /dev/null +++ b/aws/dx_vif.go @@ -0,0 +1,256 @@ +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" + "github.com/hashicorp/terraform/helper/validation" +) + +func isNoSuchDxVirtualInterfaceErr(err error) bool { + return isAWSErr(err, "DirectConnectClientException", "does not exist") +} + +func dxVirtualInterfaceRead(d *schema.ResourceData, meta interface{}) (*directconnect.VirtualInterface, error) { + conn := meta.(*AWSClient).dxconn + + resp, state, err := dxVirtualInterfaceStateRefresh(conn, d.Id())() + if err != nil { + return nil, fmt.Errorf("Error reading Direct Connect virtual interface: %s", err.Error()) + } + terminalStates := map[string]bool{ + directconnect.VirtualInterfaceStateDeleted: true, + directconnect.VirtualInterfaceStateDeleting: true, + directconnect.VirtualInterfaceStateRejected: true, + } + if _, ok := terminalStates[state]; ok { + log.Printf("[WARN] Direct Connect virtual interface (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil, nil + } + + return resp.(*directconnect.VirtualInterface), nil +} + +func dxVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).dxconn + + 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() + 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 isNoSuchDxVirtualInterfaceErr(err) { + return nil + } + return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err.Error()) + } + + 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 { + if isNoSuchDxVirtualInterfaceErr(err) { + return nil, directconnect.VirtualInterfaceStateDeleted, nil + } + return nil, "", err + } + + if len(resp.VirtualInterfaces) < 1 { + return nil, directconnect.ConnectionStateDeleted, nil + } + vif := resp.VirtualInterfaces[0] + return vif, aws.StringValue(vif.VirtualInterfaceState), nil + } +} + +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.Error()) + } + + 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 { + 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 { + 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 +} + +func expandDxRouteFilterPrefixes(cfg []interface{}) []*directconnect.RouteFilterPrefix { + prefixes := make([]*directconnect.RouteFilterPrefix, len(cfg), len(cfg)) + for i, p := range cfg { + prefix := &directconnect.RouteFilterPrefix{ + Cidr: aws.String(p.(string)), + } + prefixes[i] = prefix + } + return prefixes +} + +func flattenDxRouteFilterPrefixes(prefixes []*directconnect.RouteFilterPrefix) *schema.Set { + out := make([]interface{}, 0) + for _, prefix := range prefixes { + out = append(out, aws.StringValue(prefix.Cidr)) + } + return schema.NewSet(schema.HashString, out) +} + +// Schemas common to all (public/private, hosted or not) virtual interfaces. +var dxVirtualInterfaceSchemaWithTags = mergeSchemas( + dxVirtualInterfaceSchema, + map[string]*schema.Schema{ + "tags": tagsSchema(), + }, +) +var dxVirtualInterfaceSchema = 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, + }, + "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, + }, +} diff --git a/aws/provider.go b/aws/provider.go index 1939f4dd146a..4e51e4eb8ed6 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -371,6 +371,7 @@ func Provider() terraform.ResourceProvider { "aws_dx_connection_association": resourceAwsDxConnectionAssociation(), "aws_dx_gateway": resourceAwsDxGateway(), "aws_dx_gateway_association": resourceAwsDxGatewayAssociation(), + "aws_dx_public_virtual_interface": resourceAwsDxPublicVirtualInterface(), "aws_dynamodb_table": resourceAwsDynamoDbTable(), "aws_dynamodb_table_item": resourceAwsDynamoDbTableItem(), "aws_dynamodb_global_table": resourceAwsDynamoDbGlobalTable(), diff --git a/aws/resource_aws_dx_public_virtual_interface.go b/aws/resource_aws_dx_public_virtual_interface.go new file mode 100644 index 000000000000..731cf7428fcc --- /dev/null +++ b/aws/resource_aws_dx_public_virtual_interface.go @@ -0,0 +1,140 @@ +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" +) + +func resourceAwsDxPublicVirtualInterface() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDxPublicVirtualInterfaceCreate, + Read: resourceAwsDxPublicVirtualInterfaceRead, + Update: resourceAwsDxPublicVirtualInterfaceUpdate, + Delete: resourceAwsDxPublicVirtualInterfaceDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: mergeSchemas( + dxVirtualInterfaceSchemaWithTags, + map[string]*schema.Schema{ + "route_filter_prefixes": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + }, + }, + ), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + } +} + +func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).dxconn + + addressFamily := d.Get("address_family").(string) + caRaw, caOk := d.GetOk("customer_address") + aaRaw, aaOk := d.GetOk("amazon_address") + if addressFamily == directconnect.AddressFamilyIpv4 { + if !caOk { + return fmt.Errorf("'customer_address' must be set when 'address_family' is '%s'", addressFamily) + } + if !aaOk { + return fmt.Errorf("'amazon_address' must be set when 'address_family' is '%s'", addressFamily) + } + } + + req := &directconnect.CreatePublicVirtualInterfaceInput{ + ConnectionId: aws.String(d.Get("connection_id").(string)), + NewPublicVirtualInterface: &directconnect.NewPublicVirtualInterface{ + 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(addressFamily), + }, + } + if v, ok := d.GetOk("bgp_auth_key"); ok { + req.NewPublicVirtualInterface.AuthKey = aws.String(v.(string)) + } + if caOk { + req.NewPublicVirtualInterface.CustomerAddress = aws.String(caRaw.(string)) + } + if aaOk { + req.NewPublicVirtualInterface.AmazonAddress = aws.String(aaRaw.(string)) + } + if v, ok := d.GetOk("route_filter_prefixes"); ok { + req.NewPublicVirtualInterface.RouteFilterPrefixes = expandDxRouteFilterPrefixes(v.(*schema.Set).List()) + } + + log.Printf("[DEBUG] Creating Direct Connect public virtual interface: %#v", req) + resp, err := conn.CreatePublicVirtualInterface(req) + if err != nil { + return fmt.Errorf("Error creating Direct Connect public virtual interface: %s", err.Error()) + } + + d.SetId(aws.StringValue(resp.VirtualInterfaceId)) + + if err := dxPublicVirtualInterfaceWaitUntilAvailable(d, conn); err != nil { + return err + } + + return resourceAwsDxPublicVirtualInterfaceUpdate(d, meta) +} + +func resourceAwsDxPublicVirtualInterfaceRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).dxconn + + vif, err := dxVirtualInterfaceRead(d, meta) + if err != nil { + return err + } + if vif == nil { + return nil + } + + if err := dxPublicVirtualInterfaceAttributes(d, meta, vif); err != nil { + return err + } + if err := getTagsDX(conn, d, d.Get("arn").(string)); err != nil { + return err + } + + return nil +} + +func resourceAwsDxPublicVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { + if err := dxVirtualInterfaceUpdate(d, meta); err != nil { + return err + } + + return resourceAwsDxPublicVirtualInterfaceRead(d, meta) +} + +func resourceAwsDxPublicVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { + return dxVirtualInterfaceDelete(d, meta) +} + +func dxPublicVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { + return dxVirtualInterfaceWaitUntilAvailable( + d, + conn, + []string{ + directconnect.VirtualInterfaceStatePending, + }, + []string{ + directconnect.VirtualInterfaceStateAvailable, + directconnect.VirtualInterfaceStateDown, + directconnect.VirtualInterfaceStateVerifying, + }) +} diff --git a/aws/resource_aws_dx_public_virtual_interface_test.go b/aws/resource_aws_dx_public_virtual_interface_test.go new file mode 100644 index 000000000000..f5a951c5245d --- /dev/null +++ b/aws/resource_aws_dx_public_virtual_interface_test.go @@ -0,0 +1,133 @@ +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 TestAccAwsDxPublicVirtualInterface_basic(t *testing.T) { + key := "DX_CONNECTION_ID" + connectionId := os.Getenv(key) + if connectionId == "" { + t.Skipf("Environment variable %s is not set", key) + } + vifName := fmt.Sprintf("tf-dx-vif-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxPublicVirtualInterfaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxPublicVirtualInterfaceConfig_noTags(connectionId, vifName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxPublicVirtualInterfaceExists("aws_dx_public_virtual_interface.foo"), + resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "name", vifName), + resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "tags.%", "0"), + ), + }, + { + Config: testAccDxPublicVirtualInterfaceConfig_tags(connectionId, vifName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxPublicVirtualInterfaceExists("aws_dx_public_virtual_interface.foo"), + resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "name", vifName), + resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "tags.%", "1"), + resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "tags.Environment", "test"), + ), + }, + // Test import. + { + ResourceName: "aws_dx_public_virtual_interface.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsDxPublicVirtualInterfaceDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).dxconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_dx_public_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 Public VIF (%s) not deleted", rs.Primary.ID) + } + } + } + return nil +} + +func testAccCheckAwsDxPublicVirtualInterfaceExists(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 testAccDxPublicVirtualInterfaceConfig_noTags(cid, n string) string { + return fmt.Sprintf(` +resource "aws_dx_public_virtual_interface" "foo" { + connection_id = "%s" + + name = "%s" + vlan = 4094 + address_family = "ipv4" + bgp_asn = 65352 + + customer_address = "175.45.176.1/30" + amazon_address = "175.45.176.2/30" + route_filter_prefixes = [ + "210.52.109.0/24", + "175.45.176.0/22" + ] +} + `, cid, n) +} + +func testAccDxPublicVirtualInterfaceConfig_tags(cid, n string) string { + return fmt.Sprintf(` +resource "aws_dx_public_virtual_interface" "foo" { + connection_id = "%s" + + name = "%s" + vlan = 4094 + address_family = "ipv4" + bgp_asn = 65352 + + customer_address = "175.45.176.1/30" + amazon_address = "175.45.176.2/30" + route_filter_prefixes = [ + "210.52.109.0/24", + "175.45.176.0/22" + ] + + tags { + Environment = "test" + } +} + `, cid, n) +} diff --git a/aws/utils.go b/aws/utils.go index 21c3aab7ef3d..6a120cccff8c 100644 --- a/aws/utils.go +++ b/aws/utils.go @@ -7,6 +7,7 @@ import ( "regexp" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" ) // Base64Encode encodes data if the input isn't already encoded using base64.StdEncoding.EncodeToString. @@ -47,3 +48,15 @@ func isResourceNotFoundError(err error) bool { _, ok := err.(*resource.NotFoundError) return ok } + +func mergeSchemas(schemas ...map[string]*schema.Schema) map[string]*schema.Schema { + merged := make(map[string]*schema.Schema) + + for _, schema := range schemas { + for k, v := range schema { + merged[k] = v + } + } + + return merged +} diff --git a/aws/utils_test.go b/aws/utils_test.go index 8248f4384d27..a5854e7ef4b0 100644 --- a/aws/utils_test.go +++ b/aws/utils_test.go @@ -1,6 +1,10 @@ package aws -import "testing" +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" +) var base64encodingTests = []struct { in []byte @@ -70,3 +74,29 @@ func TestJsonBytesEqualWhitespaceAndNoWhitespace(t *testing.T) { t.Errorf("Expected jsonBytesEqual to return false for %s == %s", noWhitespaceDiff, whitespaceDiff) } } + +func TestMergeSchemas(t *testing.T) { + s1 := map[string]*schema.Schema{ + "aaa": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "zzz": { + Type: schema.TypeInt, + Optional: true, + Default: 42, + }, + } + s2 := map[string]*schema.Schema{ + "xxx": { + Type: schema.TypeFloat, + Computed: true, + }, + } + + s3 := mergeSchemas(s1, s2) + if len(s3) != 3 { + t.Errorf("Expected merge schema to be of size 3, got %d", len(s3)) + } +} diff --git a/website/aws.erb b/website/aws.erb index 0e07c6a0f181..59beaff6c9b9 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -776,6 +776,9 @@ > aws_dx_lag + > + aws_dx_public_virtual_interface + diff --git a/website/docs/r/dx_public_virtual_interface.html.markdown b/website/docs/r/dx_public_virtual_interface.html.markdown new file mode 100644 index 000000000000..05d611d40bb8 --- /dev/null +++ b/website/docs/r/dx_public_virtual_interface.html.markdown @@ -0,0 +1,69 @@ +--- +layout: "aws" +page_title: "AWS: aws_dx_public_virtual_interface" +sidebar_current: "docs-aws-resource-dx-public-virtual-interface" +description: |- + Provides a Direct Connect public virtual interface resource. +--- + +# aws_dx_public_virtual_interface + +Provides a Direct Connect public virtual interface resource. + +## Example Usage + +```hcl +resource "aws_dx_public_virtual_interface" "foo" { + connection_id = "dxcon-zzzzzzzz" + + name = "vif-foo" + vlan = 4094 + address_family = "ipv4" + bgp_asn = 65352 + + customer_address = "175.45.176.1/30" + amazon_address = "175.45.176.2/30" + route_filter_prefixes = [ + "210.52.109.0/24", + "175.45.176.0/22" + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `connection_id` - (Required) The ID of the Direct Connect connection (or LAG) on which to create the virtual interface. +* `name` - (Required) The name for the virtual interface. +* `vlan` - (Required) The VLAN ID. +* `bgp_asn` - (Required) The autonomous system (AS) number for Border Gateway Protocol (BGP) configuration. +* `bgp_auth_key` - (Optional) The authentication key for BGP configuration. +* `address_family` - (Required) The address family for the BGP peer. `ipv4 ` or `ipv6`. +* `customer_address` - (Optional) The IPv4 CIDR destination address to which Amazon should send traffic. Required for IPv4 BGP peers. +* `amazon_address` - (Optional) The IPv4 CIDR address to use to send traffic to Amazon. Required for IPv4 BGP peers. +* `route_filter_prefixes` - (Required) A list of routes to be advertised to the AWS network in this region. +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the virtual interface. +* `arn` - The ARN of the virtual interface. + +## Timeouts + +`aws_dx_public_virtual_interface` provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - (Default `10 minutes`) Used for creating virtual interface +- `delete` - (Default `10 minutes`) Used for destroying virtual interface + +## Import + +Direct Connect public virtual interfaces can be imported using the `vif id`, e.g. + +``` +$ terraform import aws_dx_public_virtual_interface.test dxvif-33cc44dd +``` From dad1c60044b9edc1074ade16da2487a7cc5d128f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Feb 2018 09:45:51 -0500 Subject: [PATCH 2/9] Fix indentation for configuration generation. --- aws/resource_aws_dx_public_virtual_interface_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_dx_public_virtual_interface_test.go b/aws/resource_aws_dx_public_virtual_interface_test.go index f5a951c5245d..13ccf43d5ddd 100644 --- a/aws/resource_aws_dx_public_virtual_interface_test.go +++ b/aws/resource_aws_dx_public_virtual_interface_test.go @@ -105,7 +105,7 @@ resource "aws_dx_public_virtual_interface" "foo" { "175.45.176.0/22" ] } - `, cid, n) +`, cid, n) } func testAccDxPublicVirtualInterfaceConfig_tags(cid, n string) string { @@ -129,5 +129,5 @@ resource "aws_dx_public_virtual_interface" "foo" { Environment = "test" } } - `, cid, n) +`, cid, n) } From 48de7470d9546496818d8ddabd2f1b9fa3e334d3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Feb 2018 09:52:42 -0500 Subject: [PATCH 3/9] Move schema-related code to the top of 'dx_vif.go'. --- aws/dx_vif.go | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/aws/dx_vif.go b/aws/dx_vif.go index 903c5ca4fed3..ca6403f8d2e5 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -13,6 +13,65 @@ import ( "github.com/hashicorp/terraform/helper/validation" ) +// Schemas common to all (public/private, hosted or not) virtual interfaces. +var dxVirtualInterfaceSchemaWithTags = mergeSchemas( + dxVirtualInterfaceSchema, + map[string]*schema.Schema{ + "tags": tagsSchema(), + }, +) +var dxVirtualInterfaceSchema = 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, + }, + "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, + }, +} + func isNoSuchDxVirtualInterfaceErr(err error) bool { return isAWSErr(err, "DirectConnectClientException", "does not exist") } @@ -195,62 +254,3 @@ func flattenDxRouteFilterPrefixes(prefixes []*directconnect.RouteFilterPrefix) * } return schema.NewSet(schema.HashString, out) } - -// Schemas common to all (public/private, hosted or not) virtual interfaces. -var dxVirtualInterfaceSchemaWithTags = mergeSchemas( - dxVirtualInterfaceSchema, - map[string]*schema.Schema{ - "tags": tagsSchema(), - }, -) -var dxVirtualInterfaceSchema = 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, - }, - "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, - }, -} From 317543243c6d66f1882336b360eb6025262ab0fa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Feb 2018 11:20:02 -0500 Subject: [PATCH 4/9] Change interface to 'dxVirtualInterfaceRead'. --- aws/dx_vif.go | 15 +++------------ aws/resource_aws_dx_public_virtual_interface.go | 4 +++- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/aws/dx_vif.go b/aws/dx_vif.go index ca6403f8d2e5..27a83780c503 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -76,21 +76,12 @@ func isNoSuchDxVirtualInterfaceErr(err error) bool { return isAWSErr(err, "DirectConnectClientException", "does not exist") } -func dxVirtualInterfaceRead(d *schema.ResourceData, meta interface{}) (*directconnect.VirtualInterface, error) { - conn := meta.(*AWSClient).dxconn - - resp, state, err := dxVirtualInterfaceStateRefresh(conn, d.Id())() +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.Error()) } - terminalStates := map[string]bool{ - directconnect.VirtualInterfaceStateDeleted: true, - directconnect.VirtualInterfaceStateDeleting: true, - directconnect.VirtualInterfaceStateRejected: true, - } - if _, ok := terminalStates[state]; ok { - log.Printf("[WARN] Direct Connect virtual interface (%s) not found, removing from state", d.Id()) - d.SetId("") + if state == directconnect.VirtualInterfaceStateDeleted { return nil, nil } diff --git a/aws/resource_aws_dx_public_virtual_interface.go b/aws/resource_aws_dx_public_virtual_interface.go index 731cf7428fcc..3cd91f2e6e71 100644 --- a/aws/resource_aws_dx_public_virtual_interface.go +++ b/aws/resource_aws_dx_public_virtual_interface.go @@ -95,11 +95,13 @@ func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta inte func resourceAwsDxPublicVirtualInterfaceRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - vif, err := dxVirtualInterfaceRead(d, meta) + 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 } From bbd895ef6bd9ca347f8d35136f37a55e0388add6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 11 May 2018 15:20:33 -0400 Subject: [PATCH 5/9] Minor refactoring. --- aws/dx_vif.go | 25 ++--------- ...esource_aws_dx_public_virtual_interface.go | 41 +++++++++++-------- aws/structure.go | 20 +++++++++ 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/aws/dx_vif.go b/aws/dx_vif.go index 27a83780c503..787e9d0c6d5e 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -79,7 +79,7 @@ func isNoSuchDxVirtualInterfaceErr(err error) bool { 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.Error()) + return nil, fmt.Errorf("Error reading Direct Connect virtual interface: %s", err) } if state == directconnect.VirtualInterfaceStateDeleted { return nil, nil @@ -116,7 +116,7 @@ func dxVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { if isNoSuchDxVirtualInterfaceErr(err) { return nil } - return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err.Error()) + return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err) } deleteStateConf := &resource.StateChangeConf{ @@ -175,7 +175,7 @@ func dxVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directco 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.Error()) + return fmt.Errorf("Error waiting for Direct Connect virtual interface %s to become available: %s", d.Id(), err) } return nil @@ -226,22 +226,3 @@ func dxVirtualInterfaceArnAttribute(d *schema.ResourceData, meta interface{}) er return nil } - -func expandDxRouteFilterPrefixes(cfg []interface{}) []*directconnect.RouteFilterPrefix { - prefixes := make([]*directconnect.RouteFilterPrefix, len(cfg), len(cfg)) - for i, p := range cfg { - prefix := &directconnect.RouteFilterPrefix{ - Cidr: aws.String(p.(string)), - } - prefixes[i] = prefix - } - return prefixes -} - -func flattenDxRouteFilterPrefixes(prefixes []*directconnect.RouteFilterPrefix) *schema.Set { - out := make([]interface{}, 0) - for _, prefix := range prefixes { - out = append(out, aws.StringValue(prefix.Cidr)) - } - return schema.NewSet(schema.HashString, out) -} diff --git a/aws/resource_aws_dx_public_virtual_interface.go b/aws/resource_aws_dx_public_virtual_interface.go index 3cd91f2e6e71..d23a2a92978a 100644 --- a/aws/resource_aws_dx_public_virtual_interface.go +++ b/aws/resource_aws_dx_public_virtual_interface.go @@ -19,6 +19,7 @@ func resourceAwsDxPublicVirtualInterface() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + CustomizeDiff: resourceAwsDxPublicVirtualInterfaceCustomizeDiff, Schema: mergeSchemas( dxVirtualInterfaceSchemaWithTags, @@ -43,35 +44,23 @@ func resourceAwsDxPublicVirtualInterface() *schema.Resource { func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - addressFamily := d.Get("address_family").(string) - caRaw, caOk := d.GetOk("customer_address") - aaRaw, aaOk := d.GetOk("amazon_address") - if addressFamily == directconnect.AddressFamilyIpv4 { - if !caOk { - return fmt.Errorf("'customer_address' must be set when 'address_family' is '%s'", addressFamily) - } - if !aaOk { - return fmt.Errorf("'amazon_address' must be set when 'address_family' is '%s'", addressFamily) - } - } - req := &directconnect.CreatePublicVirtualInterfaceInput{ ConnectionId: aws.String(d.Get("connection_id").(string)), NewPublicVirtualInterface: &directconnect.NewPublicVirtualInterface{ 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(addressFamily), + AddressFamily: aws.String(d.Get("address_family").(string)), }, } if v, ok := d.GetOk("bgp_auth_key"); ok { req.NewPublicVirtualInterface.AuthKey = aws.String(v.(string)) } - if caOk { - req.NewPublicVirtualInterface.CustomerAddress = aws.String(caRaw.(string)) + if v, ok := d.GetOk("customer_address"); ok { + req.NewPublicVirtualInterface.CustomerAddress = aws.String(v.(string)) } - if aaOk { - req.NewPublicVirtualInterface.AmazonAddress = aws.String(aaRaw.(string)) + if v, ok := d.GetOk("amazon_address"); ok { + req.NewPublicVirtualInterface.AmazonAddress = aws.String(v.(string)) } if v, ok := d.GetOk("route_filter_prefixes"); ok { req.NewPublicVirtualInterface.RouteFilterPrefixes = expandDxRouteFilterPrefixes(v.(*schema.Set).List()) @@ -80,7 +69,7 @@ func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta inte log.Printf("[DEBUG] Creating Direct Connect public virtual interface: %#v", req) resp, err := conn.CreatePublicVirtualInterface(req) if err != nil { - return fmt.Errorf("Error creating Direct Connect public virtual interface: %s", err.Error()) + return fmt.Errorf("Error creating Direct Connect public virtual interface: %s", err) } d.SetId(aws.StringValue(resp.VirtualInterfaceId)) @@ -127,6 +116,22 @@ func resourceAwsDxPublicVirtualInterfaceDelete(d *schema.ResourceData, meta inte return dxVirtualInterfaceDelete(d, meta) } +func resourceAwsDxPublicVirtualInterfaceCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error { + if diff.Id() == "" { + // New resource. + if addressFamily := diff.Get("address_family").(string); addressFamily == directconnect.AddressFamilyIpv4 { + if _, ok := diff.GetOk("customer_address"); !ok { + return fmt.Errorf("'customer_address' must be set when 'address_family' is '%s'", addressFamily) + } + if _, ok := diff.GetOk("amazon_address"); !ok { + return fmt.Errorf("'amazon_address' must be set when 'address_family' is '%s'", addressFamily) + } + } + } + + return nil +} + func dxPublicVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { return dxVirtualInterfaceWaitUntilAvailable( d, diff --git a/aws/structure.go b/aws/structure.go index 11eca3b1efb2..9370d54e4701 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -20,6 +20,7 @@ import ( "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" "github.com/aws/aws-sdk-go/service/configservice" "github.com/aws/aws-sdk-go/service/dax" + "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" @@ -4268,3 +4269,22 @@ func expandVpcPeeringConnectionOptions(m map[string]interface{}) *ec2.PeeringCon return options } + +func expandDxRouteFilterPrefixes(cfg []interface{}) []*directconnect.RouteFilterPrefix { + prefixes := make([]*directconnect.RouteFilterPrefix, len(cfg), len(cfg)) + for i, p := range cfg { + prefix := &directconnect.RouteFilterPrefix{ + Cidr: aws.String(p.(string)), + } + prefixes[i] = prefix + } + return prefixes +} + +func flattenDxRouteFilterPrefixes(prefixes []*directconnect.RouteFilterPrefix) *schema.Set { + out := make([]interface{}, 0) + for _, prefix := range prefixes { + out = append(out, aws.StringValue(prefix.Cidr)) + } + return schema.NewSet(schema.HashString, out) +} From 9c9b6769404a05c61fe826df89655e10b065f3af Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 11 May 2018 16:19:32 -0400 Subject: [PATCH 6/9] Standardize resource name for testing. --- aws/resource_aws_dx_public_virtual_interface_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_dx_public_virtual_interface_test.go b/aws/resource_aws_dx_public_virtual_interface_test.go index 13ccf43d5ddd..28e94108674a 100644 --- a/aws/resource_aws_dx_public_virtual_interface_test.go +++ b/aws/resource_aws_dx_public_virtual_interface_test.go @@ -18,7 +18,7 @@ func TestAccAwsDxPublicVirtualInterface_basic(t *testing.T) { if connectionId == "" { t.Skipf("Environment variable %s is not set", key) } - vifName := fmt.Sprintf("tf-dx-vif-%s", acctest.RandString(5)) + vifName := fmt.Sprintf("terraform-testacc-dx-vif-%s", acctest.RandString(5)) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, From 07324ec13190bdb667662f45a4de8a588b6a9505 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 19 Jun 2018 22:53:25 -0400 Subject: [PATCH 7/9] Make consistent with Direct Connect gateway. --- aws/dx_vif.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/aws/dx_vif.go b/aws/dx_vif.go index 787e9d0c6d5e..0898583598cf 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -72,10 +72,6 @@ var dxVirtualInterfaceSchema = map[string]*schema.Schema{ }, } -func isNoSuchDxVirtualInterfaceErr(err error) bool { - return isAWSErr(err, "DirectConnectClientException", "does not exist") -} - func dxVirtualInterfaceRead(id string, conn *directconnect.DirectConnect) (*directconnect.VirtualInterface, error) { resp, state, err := dxVirtualInterfaceStateRefresh(conn, id)() if err != nil { @@ -113,7 +109,7 @@ func dxVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { VirtualInterfaceId: aws.String(d.Id()), }) if err != nil { - if isNoSuchDxVirtualInterfaceErr(err) { + if isAWSErr(err, "DirectConnectClientException", "does not exist") { return nil } return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err) @@ -151,17 +147,21 @@ func dxVirtualInterfaceStateRefresh(conn *directconnect.DirectConnect, vifId str VirtualInterfaceId: aws.String(vifId), }) if err != nil { - if isNoSuchDxVirtualInterfaceErr(err) { - return nil, directconnect.VirtualInterfaceStateDeleted, nil - } return nil, "", err } - if len(resp.VirtualInterfaces) < 1 { - return nil, directconnect.ConnectionStateDeleted, nil + 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) } - vif := resp.VirtualInterfaces[0] - return vif, aws.StringValue(vif.VirtualInterfaceState), nil } } @@ -175,7 +175,7 @@ func dxVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directco 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 fmt.Errorf("Error waiting for Direct Connect virtual interface (%s) to become available: %s", d.Id(), err) } return nil From 9fde05d0769a7d00d78a9a6e66b26fd505e16b18 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 22 Jun 2018 13:09:25 -0400 Subject: [PATCH 8/9] Remove 'mergeSchemas'. --- aws/dx_vif.go | 60 ---------------- ...esource_aws_dx_public_virtual_interface.go | 70 ++++++++++++++++--- aws/utils.go | 13 ---- aws/utils_test.go | 28 -------- 4 files changed, 59 insertions(+), 112 deletions(-) diff --git a/aws/dx_vif.go b/aws/dx_vif.go index 0898583598cf..92ee5358cc73 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -10,68 +10,8 @@ import ( "github.com/aws/aws-sdk-go/service/directconnect" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/helper/validation" ) -// Schemas common to all (public/private, hosted or not) virtual interfaces. -var dxVirtualInterfaceSchemaWithTags = mergeSchemas( - dxVirtualInterfaceSchema, - map[string]*schema.Schema{ - "tags": tagsSchema(), - }, -) -var dxVirtualInterfaceSchema = 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, - }, - "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, - }, -} - func dxVirtualInterfaceRead(id string, conn *directconnect.DirectConnect) (*directconnect.VirtualInterface, error) { resp, state, err := dxVirtualInterfaceStateRefresh(conn, id)() if err != nil { diff --git a/aws/resource_aws_dx_public_virtual_interface.go b/aws/resource_aws_dx_public_virtual_interface.go index d23a2a92978a..88cfd30a5c1c 100644 --- a/aws/resource_aws_dx_public_virtual_interface.go +++ b/aws/resource_aws_dx_public_virtual_interface.go @@ -8,6 +8,7 @@ import ( "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 resourceAwsDxPublicVirtualInterface() *schema.Resource { @@ -21,18 +22,65 @@ func resourceAwsDxPublicVirtualInterface() *schema.Resource { }, CustomizeDiff: resourceAwsDxPublicVirtualInterfaceCustomizeDiff, - Schema: mergeSchemas( - dxVirtualInterfaceSchemaWithTags, - map[string]*schema.Schema{ - "route_filter_prefixes": &schema.Schema{ - Type: schema.TypeSet, - Required: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - MinItems: 1, - }, + 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, + }, + "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, + }, + "route_filter_prefixes": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + }, + "tags": tagsSchema(), + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), diff --git a/aws/utils.go b/aws/utils.go index 6a120cccff8c..21c3aab7ef3d 100644 --- a/aws/utils.go +++ b/aws/utils.go @@ -7,7 +7,6 @@ import ( "regexp" "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/helper/schema" ) // Base64Encode encodes data if the input isn't already encoded using base64.StdEncoding.EncodeToString. @@ -48,15 +47,3 @@ func isResourceNotFoundError(err error) bool { _, ok := err.(*resource.NotFoundError) return ok } - -func mergeSchemas(schemas ...map[string]*schema.Schema) map[string]*schema.Schema { - merged := make(map[string]*schema.Schema) - - for _, schema := range schemas { - for k, v := range schema { - merged[k] = v - } - } - - return merged -} diff --git a/aws/utils_test.go b/aws/utils_test.go index a5854e7ef4b0..785ad141ed33 100644 --- a/aws/utils_test.go +++ b/aws/utils_test.go @@ -2,8 +2,6 @@ package aws import ( "testing" - - "github.com/hashicorp/terraform/helper/schema" ) var base64encodingTests = []struct { @@ -74,29 +72,3 @@ func TestJsonBytesEqualWhitespaceAndNoWhitespace(t *testing.T) { t.Errorf("Expected jsonBytesEqual to return false for %s == %s", noWhitespaceDiff, whitespaceDiff) } } - -func TestMergeSchemas(t *testing.T) { - s1 := map[string]*schema.Schema{ - "aaa": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "zzz": { - Type: schema.TypeInt, - Optional: true, - Default: 42, - }, - } - s2 := map[string]*schema.Schema{ - "xxx": { - Type: schema.TypeFloat, - Computed: true, - }, - } - - s3 := mergeSchemas(s1, s2) - if len(s3) != 3 { - t.Errorf("Expected merge schema to be of size 3, got %d", len(s3)) - } -} From adb1caa05bd1d73c10a82f26f424d7937cbdb376 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 22 Jun 2018 16:52:19 -0400 Subject: [PATCH 9/9] Changes after code review on https://github.com/terraform-providers/terraform-provider-aws/pull/3253. --- aws/dx_vif.go | 58 +------------------ ...esource_aws_dx_public_virtual_interface.go | 42 +++++++++++--- ...ce_aws_dx_public_virtual_interface_test.go | 19 +++--- .../dx_public_virtual_interface.html.markdown | 6 +- 4 files changed, 50 insertions(+), 75 deletions(-) diff --git a/aws/dx_vif.go b/aws/dx_vif.go index 92ee5358cc73..ea659c7b6c99 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -6,7 +6,6 @@ import ( "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" @@ -27,14 +26,7 @@ func dxVirtualInterfaceRead(id string, conn *directconnect.DirectConnect) (*dire func dxVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn - 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() - if err := setTagsDX(conn, d, arn); err != nil { + if err := setTagsDX(conn, d, d.Get("arn").(string)); err != nil { return err } @@ -49,7 +41,7 @@ func dxVirtualInterfaceDelete(d *schema.ResourceData, meta interface{}) error { VirtualInterfaceId: aws.String(d.Id()), }) if err != nil { - if isAWSErr(err, "DirectConnectClientException", "does not exist") { + if isAWSErr(err, directconnect.ErrCodeClientException, "does not exist") { return nil } return fmt.Errorf("Error deleting Direct Connect virtual interface: %s", err) @@ -120,49 +112,3 @@ func dxVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directco 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 { - 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 { - 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 -} diff --git a/aws/resource_aws_dx_public_virtual_interface.go b/aws/resource_aws_dx_public_virtual_interface.go index 88cfd30a5c1c..c12ba2502f39 100644 --- a/aws/resource_aws_dx_public_virtual_interface.go +++ b/aws/resource_aws_dx_public_virtual_interface.go @@ -6,6 +6,7 @@ import ( "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" @@ -18,7 +19,7 @@ func resourceAwsDxPublicVirtualInterface() *schema.Resource { Update: resourceAwsDxPublicVirtualInterfaceUpdate, Delete: resourceAwsDxPublicVirtualInterfaceDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: resourceAwsDxPublicVirtualInterfaceImport, }, CustomizeDiff: resourceAwsDxPublicVirtualInterfaceCustomizeDiff, @@ -101,13 +102,13 @@ func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta inte AddressFamily: aws.String(d.Get("address_family").(string)), }, } - if v, ok := d.GetOk("bgp_auth_key"); ok { + if v, ok := d.GetOk("bgp_auth_key"); ok && v.(string) != "" { req.NewPublicVirtualInterface.AuthKey = aws.String(v.(string)) } - if v, ok := d.GetOk("customer_address"); ok { + if v, ok := d.GetOk("customer_address"); ok && v.(string) != "" { req.NewPublicVirtualInterface.CustomerAddress = aws.String(v.(string)) } - if v, ok := d.GetOk("amazon_address"); ok { + if v, ok := d.GetOk("amazon_address"); ok && v.(string) != "" { req.NewPublicVirtualInterface.AmazonAddress = aws.String(v.(string)) } if v, ok := d.GetOk("route_filter_prefixes"); ok { @@ -121,6 +122,14 @@ func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta inte } d.SetId(aws.StringValue(resp.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 := dxPublicVirtualInterfaceWaitUntilAvailable(d, conn); err != nil { return err @@ -142,9 +151,15 @@ func resourceAwsDxPublicVirtualInterfaceRead(d *schema.ResourceData, meta interf return nil } - if err := dxPublicVirtualInterfaceAttributes(d, meta, vif); 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) + d.Set("route_filter_prefixes", flattenDxRouteFilterPrefixes(vif.RouteFilterPrefixes)) if err := getTagsDX(conn, d, d.Get("arn").(string)); err != nil { return err } @@ -164,6 +179,19 @@ func resourceAwsDxPublicVirtualInterfaceDelete(d *schema.ResourceData, meta inte return dxVirtualInterfaceDelete(d, meta) } +func resourceAwsDxPublicVirtualInterfaceImport(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 resourceAwsDxPublicVirtualInterfaceCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error { if diff.Id() == "" { // New resource. diff --git a/aws/resource_aws_dx_public_virtual_interface_test.go b/aws/resource_aws_dx_public_virtual_interface_test.go index 28e94108674a..e498651cbb85 100644 --- a/aws/resource_aws_dx_public_virtual_interface_test.go +++ b/aws/resource_aws_dx_public_virtual_interface_test.go @@ -18,7 +18,8 @@ func TestAccAwsDxPublicVirtualInterface_basic(t *testing.T) { if connectionId == "" { t.Skipf("Environment variable %s is not set", key) } - vifName := fmt.Sprintf("terraform-testacc-dx-vif-%s", acctest.RandString(5)) + vifName := fmt.Sprintf("terraform-testacc-dxvif-%s", acctest.RandString(5)) + bgpAsn := randIntRange(64512, 65534) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -26,7 +27,7 @@ func TestAccAwsDxPublicVirtualInterface_basic(t *testing.T) { CheckDestroy: testAccCheckAwsDxPublicVirtualInterfaceDestroy, Steps: []resource.TestStep{ { - Config: testAccDxPublicVirtualInterfaceConfig_noTags(connectionId, vifName), + Config: testAccDxPublicVirtualInterfaceConfig_noTags(connectionId, vifName, bgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxPublicVirtualInterfaceExists("aws_dx_public_virtual_interface.foo"), resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "name", vifName), @@ -34,7 +35,7 @@ func TestAccAwsDxPublicVirtualInterface_basic(t *testing.T) { ), }, { - Config: testAccDxPublicVirtualInterfaceConfig_tags(connectionId, vifName), + Config: testAccDxPublicVirtualInterfaceConfig_tags(connectionId, vifName, bgpAsn), Check: resource.ComposeTestCheckFunc( testAccCheckAwsDxPublicVirtualInterfaceExists("aws_dx_public_virtual_interface.foo"), resource.TestCheckResourceAttr("aws_dx_public_virtual_interface.foo", "name", vifName), @@ -88,7 +89,7 @@ func testAccCheckAwsDxPublicVirtualInterfaceExists(name string) resource.TestChe } } -func testAccDxPublicVirtualInterfaceConfig_noTags(cid, n string) string { +func testAccDxPublicVirtualInterfaceConfig_noTags(cid, n string, bgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_public_virtual_interface" "foo" { connection_id = "%s" @@ -96,7 +97,7 @@ resource "aws_dx_public_virtual_interface" "foo" { name = "%s" vlan = 4094 address_family = "ipv4" - bgp_asn = 65352 + bgp_asn = %d customer_address = "175.45.176.1/30" amazon_address = "175.45.176.2/30" @@ -105,10 +106,10 @@ resource "aws_dx_public_virtual_interface" "foo" { "175.45.176.0/22" ] } -`, cid, n) +`, cid, n, bgpAsn) } -func testAccDxPublicVirtualInterfaceConfig_tags(cid, n string) string { +func testAccDxPublicVirtualInterfaceConfig_tags(cid, n string, bgpAsn int) string { return fmt.Sprintf(` resource "aws_dx_public_virtual_interface" "foo" { connection_id = "%s" @@ -116,7 +117,7 @@ resource "aws_dx_public_virtual_interface" "foo" { name = "%s" vlan = 4094 address_family = "ipv4" - bgp_asn = 65352 + bgp_asn = %d customer_address = "175.45.176.1/30" amazon_address = "175.45.176.2/30" @@ -129,5 +130,5 @@ resource "aws_dx_public_virtual_interface" "foo" { Environment = "test" } } -`, cid, n) +`, cid, n, bgpAsn) } diff --git a/website/docs/r/dx_public_virtual_interface.html.markdown b/website/docs/r/dx_public_virtual_interface.html.markdown index 05d611d40bb8..c8d3705613ae 100644 --- a/website/docs/r/dx_public_virtual_interface.html.markdown +++ b/website/docs/r/dx_public_virtual_interface.html.markdown @@ -34,14 +34,14 @@ resource "aws_dx_public_virtual_interface" "foo" { The following arguments are supported: +* `address_family` - (Required) The address family for the BGP peer. `ipv4 ` or `ipv6`. +* `bgp_asn` - (Required) The autonomous system (AS) number for Border Gateway Protocol (BGP) configuration. * `connection_id` - (Required) The ID of the Direct Connect connection (or LAG) on which to create the virtual interface. * `name` - (Required) The name for the virtual interface. * `vlan` - (Required) The VLAN ID. -* `bgp_asn` - (Required) The autonomous system (AS) number for Border Gateway Protocol (BGP) configuration. +* `amazon_address` - (Optional) The IPv4 CIDR address to use to send traffic to Amazon. Required for IPv4 BGP peers. * `bgp_auth_key` - (Optional) The authentication key for BGP configuration. -* `address_family` - (Required) The address family for the BGP peer. `ipv4 ` or `ipv6`. * `customer_address` - (Optional) The IPv4 CIDR destination address to which Amazon should send traffic. Required for IPv4 BGP peers. -* `amazon_address` - (Optional) The IPv4 CIDR address to use to send traffic to Amazon. Required for IPv4 BGP peers. * `route_filter_prefixes` - (Required) A list of routes to be advertised to the AWS network in this region. * `tags` - (Optional) A mapping of tags to assign to the resource.