From e1aa889e58097edcfe0a5863b840b425fe2825d3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 16 Feb 2018 08:13:41 -0500 Subject: [PATCH 1/6] Fix issue with parallel calls to 'ModifyVpcEndpoint'. --- ...rce_aws_vpc_endpoint_subnet_association.go | 25 +++++- ...ws_vpc_endpoint_subnet_association_test.go | 87 +++++++++++++++++-- 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_vpc_endpoint_subnet_association.go b/aws/resource_aws_vpc_endpoint_subnet_association.go index 1fe3c4a1898..ee5b753be82 100644 --- a/aws/resource_aws_vpc_endpoint_subnet_association.go +++ b/aws/resource_aws_vpc_endpoint_subnet_association.go @@ -3,11 +3,13 @@ package aws import ( "fmt" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -46,10 +48,25 @@ func resourceAwsVpcEndpointSubnetAssociationCreate(d *schema.ResourceData, meta return err } - _, err = conn.ModifyVpcEndpoint(&ec2.ModifyVpcEndpointInput{ - VpcEndpointId: aws.String(endpointId), - AddSubnetIds: aws.StringSlice([]string{snId}), - }) + // See https://github.com/terraform-providers/terraform-provider-aws/issues/3382. + // Prevent concurrent subnet association requests and delay between requests. + mk := "vpc_endpoint_subnet_association_" + endpointId + awsMutexKV.Lock(mk) + defer awsMutexKV.Unlock(mk) + + c := &resource.StateChangeConf{ + Delay: 1 * time.Minute, + Timeout: 3 * time.Minute, + Target: []string{"ok"}, + Refresh: func() (interface{}, string, error) { + res, err := conn.ModifyVpcEndpoint(&ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(endpointId), + AddSubnetIds: aws.StringSlice([]string{snId}), + }) + return res, "ok", err + }, + } + _, err = c.WaitForState() if err != nil { return fmt.Errorf("Error creating Vpc Endpoint/Subnet association: %s", err.Error()) } diff --git a/aws/resource_aws_vpc_endpoint_subnet_association_test.go b/aws/resource_aws_vpc_endpoint_subnet_association_test.go index eeb5c922135..f5acda6da25 100644 --- a/aws/resource_aws_vpc_endpoint_subnet_association_test.go +++ b/aws/resource_aws_vpc_endpoint_subnet_association_test.go @@ -30,6 +30,29 @@ func TestAccAWSVpcEndpointSubnetAssociation_basic(t *testing.T) { }) } +func TestAccAwsVpcEndpointSubnetAssociation_multiple(t *testing.T) { + var vpce ec2.VpcEndpoint + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcEndpointSubnetAssociationDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpcEndpointSubnetAssociationConfig_multiple, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSubnetAssociationExists( + "aws_vpc_endpoint_subnet_association.a.0", &vpce), + testAccCheckVpcEndpointSubnetAssociationExists( + "aws_vpc_endpoint_subnet_association.a.1", &vpce), + testAccCheckVpcEndpointSubnetAssociationExists( + "aws_vpc_endpoint_subnet_association.a.2", &vpce), + ), + }, + }, + }) +} + func testAccCheckVpcEndpointSubnetAssociationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -116,28 +139,76 @@ resource "aws_vpc" "foo" { data "aws_security_group" "default" { vpc_id = "${aws_vpc.foo.id}" - name = "default" + name = "default" } +data "aws_availability_zones" "available" {} + resource "aws_vpc_endpoint" "ec2" { - vpc_id = "${aws_vpc.foo.id}" - vpc_endpoint_type = "Interface" - service_name = "com.amazonaws.us-west-2.ec2" - security_group_ids = ["${data.aws_security_group.default.id}"] + vpc_id = "${aws_vpc.foo.id}" + vpc_endpoint_type = "Interface" + service_name = "com.amazonaws.us-west-2.ec2" + security_group_ids = ["${data.aws_security_group.default.id}"] private_dns_enabled = false } resource "aws_subnet" "sn" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "${data.aws_availability_zones.available.names[0]}" + cidr_block = "10.0.0.0/17" + tags { + Name = "tf-acc-vpc-endpoint-subnet-association" + } +} + +resource "aws_vpc_endpoint_subnet_association" "a" { + vpc_endpoint_id = "${aws_vpc_endpoint.ec2.id}" + subnet_id = "${aws_subnet.sn.id}" +} +` + +const testAccVpcEndpointSubnetAssociationConfig_multiple = ` +provider "aws" { + region = "us-west-2" +} + +resource "aws_vpc" "foo" { + cidr_block = "10.0.0.0/16" + tags { + Name = "terraform-testacc-vpc-endpoint-subnet-association" + } +} + +data "aws_security_group" "default" { vpc_id = "${aws_vpc.foo.id}" - availability_zone = "us-west-2a" - cidr_block = "10.0.0.0/17" + name = "default" +} + +data "aws_availability_zones" "available" {} + +resource "aws_vpc_endpoint" "ec2" { + vpc_id = "${aws_vpc.foo.id}" + vpc_endpoint_type = "Interface" + service_name = "com.amazonaws.us-west-2.ec2" + security_group_ids = ["${data.aws_security_group.default.id}"] + private_dns_enabled = false +} + +resource "aws_subnet" "sn" { + count = 3 + + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "${data.aws_availability_zones.available.names[count.index]}" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, count.index)}" tags { Name = "tf-acc-vpc-endpoint-subnet-association" } } resource "aws_vpc_endpoint_subnet_association" "a" { + count = 3 + vpc_endpoint_id = "${aws_vpc_endpoint.ec2.id}" - subnet_id = "${aws_subnet.sn.id}" + subnet_id = "${aws_subnet.sn.*.id[count.index]}" } ` From 2222b7f446da9a18dc21f7a37ce9eab387c87540 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 13 Jul 2018 11:16:03 -0400 Subject: [PATCH 2/6] Make aws_vpc_endpoint_subnet_association acceptance tests region-agnostic. --- ...ws_vpc_endpoint_subnet_association_test.go | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/aws/resource_aws_vpc_endpoint_subnet_association_test.go b/aws/resource_aws_vpc_endpoint_subnet_association_test.go index f5acda6da25..174fc5304ce 100644 --- a/aws/resource_aws_vpc_endpoint_subnet_association_test.go +++ b/aws/resource_aws_vpc_endpoint_subnet_association_test.go @@ -30,7 +30,7 @@ func TestAccAWSVpcEndpointSubnetAssociation_basic(t *testing.T) { }) } -func TestAccAwsVpcEndpointSubnetAssociation_multiple(t *testing.T) { +func TestAccAWSVpcEndpointSubnetAssociation_multiple(t *testing.T) { var vpce ec2.VpcEndpoint resource.Test(t, resource.TestCase{ @@ -126,10 +126,6 @@ func testAccCheckVpcEndpointSubnetAssociationExists(n string, vpce *ec2.VpcEndpo } const testAccVpcEndpointSubnetAssociationConfig_basic = ` -provider "aws" { - region = "us-west-2" -} - resource "aws_vpc" "foo" { cidr_block = "10.0.0.0/16" tags { @@ -142,12 +138,14 @@ data "aws_security_group" "default" { name = "default" } +data "aws_region" "current" {} + data "aws_availability_zones" "available" {} resource "aws_vpc_endpoint" "ec2" { vpc_id = "${aws_vpc.foo.id}" vpc_endpoint_type = "Interface" - service_name = "com.amazonaws.us-west-2.ec2" + service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" security_group_ids = ["${data.aws_security_group.default.id}"] private_dns_enabled = false } @@ -155,7 +153,7 @@ resource "aws_vpc_endpoint" "ec2" { resource "aws_subnet" "sn" { vpc_id = "${aws_vpc.foo.id}" availability_zone = "${data.aws_availability_zones.available.names[0]}" - cidr_block = "10.0.0.0/17" + cidr_block = "10.0.0.0/17" tags { Name = "tf-acc-vpc-endpoint-subnet-association" } @@ -168,12 +166,8 @@ resource "aws_vpc_endpoint_subnet_association" "a" { ` const testAccVpcEndpointSubnetAssociationConfig_multiple = ` -provider "aws" { - region = "us-west-2" -} - resource "aws_vpc" "foo" { - cidr_block = "10.0.0.0/16" + cidr_block = "10.0.0.0/16" tags { Name = "terraform-testacc-vpc-endpoint-subnet-association" } @@ -184,12 +178,14 @@ data "aws_security_group" "default" { name = "default" } +data "aws_region" "current" {} + data "aws_availability_zones" "available" {} resource "aws_vpc_endpoint" "ec2" { vpc_id = "${aws_vpc.foo.id}" vpc_endpoint_type = "Interface" - service_name = "com.amazonaws.us-west-2.ec2" + service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" security_group_ids = ["${data.aws_security_group.default.id}"] private_dns_enabled = false } @@ -199,9 +195,9 @@ resource "aws_subnet" "sn" { vpc_id = "${aws_vpc.foo.id}" availability_zone = "${data.aws_availability_zones.available.names[count.index]}" - cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, count.index)}" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, count.index)}" tags { - Name = "tf-acc-vpc-endpoint-subnet-association" + Name = "${format("tf-acc-vpc-endpoint-subnet-association-%d", count.index + 1)}" } } From 6cf9541413bbd11120de47afbd811f3d3f7b7af3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 13 Jul 2018 14:39:11 -0400 Subject: [PATCH 3/6] Make aws_vpc_endpoint acceptance tests region-agnostic. --- aws/resource_aws_vpc_endpoint_test.go | 94 ++++++++++++++++----------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/aws/resource_aws_vpc_endpoint_test.go b/aws/resource_aws_vpc_endpoint_test.go index f8d90d20177..7b7669d0f1d 100644 --- a/aws/resource_aws_vpc_endpoint_test.go +++ b/aws/resource_aws_vpc_endpoint_test.go @@ -309,9 +309,11 @@ resource "aws_subnet" "foo" { } } +data "aws_region" "current" {} + resource "aws_vpc_endpoint" "s3" { vpc_id = "${aws_vpc.foo.id}" - service_name = "com.amazonaws.us-west-2.s3" + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" route_table_ids = ["${aws_route_table.default.id}"] policy = < Date: Fri, 13 Jul 2018 14:55:26 -0400 Subject: [PATCH 4/6] Wait for VPC Endpoint status to return to 'available' when associating subnet. --- aws/resource_aws_vpc_endpoint.go | 58 ++++++++++++------- ...rce_aws_vpc_endpoint_subnet_association.go | 49 +++++++--------- website/docs/r/vpc_endpoint.html.markdown | 9 +++ ..._endpoint_subnet_association.html.markdown | 8 +++ 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/aws/resource_aws_vpc_endpoint.go b/aws/resource_aws_vpc_endpoint.go index 2826f490468..96b072f8454 100644 --- a/aws/resource_aws_vpc_endpoint.go +++ b/aws/resource_aws_vpc_endpoint.go @@ -122,6 +122,12 @@ func resourceAwsVpcEndpoint() *schema.Resource { Optional: true, }, }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, } } @@ -155,7 +161,7 @@ func resourceAwsVpcEndpointCreate(d *schema.ResourceData, meta interface{}) erro log.Printf("[DEBUG] Creating VPC Endpoint: %#v", req) resp, err := conn.CreateVpcEndpoint(req) if err != nil { - return fmt.Errorf("Error creating VPC Endpoint: %s", err.Error()) + return fmt.Errorf("Error creating VPC Endpoint: %s", err) } vpce := resp.VpcEndpoint @@ -167,7 +173,7 @@ func resourceAwsVpcEndpointCreate(d *schema.ResourceData, meta interface{}) erro } } - if err := vpcEndpointWaitUntilAvailable(d, conn); err != nil { + if err := vpcEndpointWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -179,7 +185,7 @@ func resourceAwsVpcEndpointRead(d *schema.ResourceData, meta interface{}) error vpce, state, err := vpcEndpointStateRefresh(conn, d.Id())() if err != nil && state != "failed" { - return fmt.Errorf("Error reading VPC Endpoint: %s", err.Error()) + return fmt.Errorf("Error reading VPC Endpoint: %s", err) } terminalStates := map[string]bool{ @@ -234,10 +240,10 @@ func resourceAwsVpcEndpointUpdate(d *schema.ResourceData, meta interface{}) erro log.Printf("[DEBUG] Updating VPC Endpoint: %#v", req) if _, err := conn.ModifyVpcEndpoint(req); err != nil { - return fmt.Errorf("Error updating VPC Endpoint: %s", err.Error()) + return fmt.Errorf("Error updating VPC Endpoint: %s", err) } - if err := vpcEndpointWaitUntilAvailable(d, conn); err != nil { + if err := vpcEndpointWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return err } @@ -255,7 +261,7 @@ func resourceAwsVpcEndpointDelete(d *schema.ResourceData, meta interface{}) erro if isAWSErr(err, "InvalidVpcEndpointId.NotFound", "") { log.Printf("[DEBUG] VPC Endpoint %s is already gone", d.Id()) } else { - return fmt.Errorf("Error deleting VPC Endpoint: %s", err.Error()) + return fmt.Errorf("Error deleting VPC Endpoint: %s", err) } } @@ -263,12 +269,12 @@ func resourceAwsVpcEndpointDelete(d *schema.ResourceData, meta interface{}) erro Pending: []string{"available", "pending", "deleting"}, Target: []string{"deleted"}, Refresh: vpcEndpointStateRefresh(conn, d.Id()), - Timeout: 10 * time.Minute, + Timeout: d.Timeout(schema.TimeoutDelete), Delay: 5 * time.Second, MinTimeout: 5 * time.Second, } if _, err = stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for VPC Endpoint %s to delete: %s", d.Id(), err.Error()) + return fmt.Errorf("Error waiting for VPC Endpoint (%s) to delete: %s", d.Id(), err) } return nil @@ -284,7 +290,7 @@ func vpcEndpointAccept(conn *ec2.EC2, vpceId, svcName string) error { describeSvcResp, err := conn.DescribeVpcEndpointServiceConfigurations(describeSvcReq) if err != nil { - return fmt.Errorf("Error reading VPC Endpoint Service: %s", err.Error()) + return fmt.Errorf("Error reading VPC Endpoint Service: %s", err) } if describeSvcResp == nil || len(describeSvcResp.ServiceConfigurations) == 0 { return fmt.Errorf("No matching VPC Endpoint Service found") @@ -298,7 +304,7 @@ func vpcEndpointAccept(conn *ec2.EC2, vpceId, svcName string) error { log.Printf("[DEBUG] Accepting VPC Endpoint connection: %#v", acceptEpReq) _, err = conn.AcceptVpcEndpointConnections(acceptEpReq) if err != nil { - return fmt.Errorf("Error accepting VPC Endpoint connection: %s", err.Error()) + return fmt.Errorf("Error accepting VPC Endpoint connection: %s", err) } return nil @@ -312,33 +318,43 @@ func vpcEndpointStateRefresh(conn *ec2.EC2, vpceId string) resource.StateRefresh }) if err != nil { if isAWSErr(err, "InvalidVpcEndpointId.NotFound", "") { - return false, "deleted", nil + return "", "deleted", nil } return nil, "", err } - vpce := resp.VpcEndpoints[0] - state := aws.StringValue(vpce.State) - // No use in retrying if the endpoint is in a failed state. - if state == "failed" { - return nil, state, errors.New("VPC Endpoint is in a failed state") + n := len(resp.VpcEndpoints) + switch n { + case 0: + return "", "deleted", nil + + case 1: + vpce := resp.VpcEndpoints[0] + state := aws.StringValue(vpce.State) + // No use in retrying if the endpoint is in a failed state. + if state == "failed" { + return nil, state, errors.New("VPC Endpoint is in a failed state") + } + return vpce, state, nil + + default: + return nil, "", fmt.Errorf("Found %d VPC Endpoints for %s, expected 1", n, vpceId) } - return vpce, state, nil } } -func vpcEndpointWaitUntilAvailable(d *schema.ResourceData, conn *ec2.EC2) error { +func vpcEndpointWaitUntilAvailable(conn *ec2.EC2, vpceId string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{"pending"}, Target: []string{"available", "pendingAcceptance"}, - Refresh: vpcEndpointStateRefresh(conn, d.Id()), - Timeout: 10 * time.Minute, + Refresh: vpcEndpointStateRefresh(conn, vpceId), + Timeout: timeout, Delay: 5 * time.Second, MinTimeout: 5 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for VPC Endpoint %s to become available: %s", d.Id(), err.Error()) + return fmt.Errorf("Error waiting for VPC Endpoint (%s) to become available: %s", vpceId, err) } return nil diff --git a/aws/resource_aws_vpc_endpoint_subnet_association.go b/aws/resource_aws_vpc_endpoint_subnet_association.go index ee5b753be82..ab3f4bb0d90 100644 --- a/aws/resource_aws_vpc_endpoint_subnet_association.go +++ b/aws/resource_aws_vpc_endpoint_subnet_association.go @@ -9,7 +9,6 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -34,6 +33,11 @@ func resourceAwsVpcEndpointSubnetAssociation() *schema.Resource { ForceNew: true, }, }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, } } @@ -48,30 +52,19 @@ func resourceAwsVpcEndpointSubnetAssociationCreate(d *schema.ResourceData, meta return err } - // See https://github.com/terraform-providers/terraform-provider-aws/issues/3382. - // Prevent concurrent subnet association requests and delay between requests. - mk := "vpc_endpoint_subnet_association_" + endpointId - awsMutexKV.Lock(mk) - defer awsMutexKV.Unlock(mk) - - c := &resource.StateChangeConf{ - Delay: 1 * time.Minute, - Timeout: 3 * time.Minute, - Target: []string{"ok"}, - Refresh: func() (interface{}, string, error) { - res, err := conn.ModifyVpcEndpoint(&ec2.ModifyVpcEndpointInput{ - VpcEndpointId: aws.String(endpointId), - AddSubnetIds: aws.StringSlice([]string{snId}), - }) - return res, "ok", err - }, - } - _, err = c.WaitForState() + _, err = conn.ModifyVpcEndpoint(&ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(endpointId), + AddSubnetIds: aws.StringSlice([]string{snId}), + }) if err != nil { - return fmt.Errorf("Error creating Vpc Endpoint/Subnet association: %s", err.Error()) + return fmt.Errorf("Error creating Vpc Endpoint/Subnet association: %s", err) } - d.SetId(vpcEndpointIdSubnetIdHash(endpointId, snId)) + d.SetId(vpcEndpointSubnetAssociationId(endpointId, snId)) + + if err := vpcEndpointWaitUntilAvailable(conn, endpointId, d.Timeout(schema.TimeoutCreate)); err != nil { + return err + } return resourceAwsVpcEndpointSubnetAssociationRead(d, meta) } @@ -122,24 +115,26 @@ func resourceAwsVpcEndpointSubnetAssociationDelete(d *schema.ResourceData, meta if err != nil { ec2err, ok := err.(awserr.Error) if !ok { - return fmt.Errorf("Error deleting Vpc Endpoint/Subnet association: %s", err.Error()) + return fmt.Errorf("Error deleting Vpc Endpoint/Subnet association: %s", err) } switch ec2err.Code() { case "InvalidVpcEndpointId.NotFound": fallthrough - case "InvalidRouteTableId.NotFound": - fallthrough case "InvalidParameter": log.Printf("[DEBUG] Vpc Endpoint/Subnet association is already gone") default: - return fmt.Errorf("Error deleting Vpc Endpoint/Subnet association: %s", err.Error()) + return fmt.Errorf("Error deleting Vpc Endpoint/Subnet association: %s", err) } } + if err := vpcEndpointWaitUntilAvailable(conn, endpointId, d.Timeout(schema.TimeoutDelete)); err != nil { + return err + } + return nil } -func vpcEndpointIdSubnetIdHash(endpointId, snId string) string { +func vpcEndpointSubnetAssociationId(endpointId, snId string) string { return fmt.Sprintf("a-%s%d", endpointId, hashcode.String(snId)) } diff --git a/website/docs/r/vpc_endpoint.html.markdown b/website/docs/r/vpc_endpoint.html.markdown index 7799f638e59..b6efd4d6bd9 100644 --- a/website/docs/r/vpc_endpoint.html.markdown +++ b/website/docs/r/vpc_endpoint.html.markdown @@ -93,6 +93,15 @@ Defaults to full access. * `private_dns_enabled` - (Optional) Whether or not to associate a private hosted zone with the specified VPC. Applicable for endpoints of type `Interface`. Defaults to `false`. +### Timeouts + +`aws_vpc_endpoint` provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - (Default `10 minutes`) Used for creating a VPC endpoint +- `update` - (Default `10 minutes`) Used for VPC endpoint modifications +- `delete` - (Default `10 minutes`) Used for destroying VPC endpoints + ## Attributes Reference In addition to all arguments above, the following attributes are exported: diff --git a/website/docs/r/vpc_endpoint_subnet_association.html.markdown b/website/docs/r/vpc_endpoint_subnet_association.html.markdown index 67fb602a338..5ffb5cc76f2 100644 --- a/website/docs/r/vpc_endpoint_subnet_association.html.markdown +++ b/website/docs/r/vpc_endpoint_subnet_association.html.markdown @@ -34,6 +34,14 @@ The following arguments are supported: * `vpc_endpoint_id` - (Required) The ID of the VPC endpoint with which the subnet will be associated. * `subnet_id` - (Required) The ID of the subnet to be associated with the VPC endpoint. +### Timeouts + +`aws_vpc_endpoint_subnet_association` provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - (Default `10 minutes`) Used for creating the association +- `delete` - (Default `10 minutes`) Used for destroying the association + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 57c8e49e209e29098d1f3acccf9e57158b8c3b26 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 13 Jul 2018 17:07:52 -0400 Subject: [PATCH 5/6] Enhance 'TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup'. --- ...ws_vpc_endpoint_subnet_association_test.go | 2 +- aws/resource_aws_vpc_endpoint_test.go | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_vpc_endpoint_subnet_association_test.go b/aws/resource_aws_vpc_endpoint_subnet_association_test.go index 174fc5304ce..6aa78c832a9 100644 --- a/aws/resource_aws_vpc_endpoint_subnet_association_test.go +++ b/aws/resource_aws_vpc_endpoint_subnet_association_test.go @@ -197,7 +197,7 @@ resource "aws_subnet" "sn" { availability_zone = "${data.aws_availability_zones.available.names[count.index]}" cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, count.index)}" tags { - Name = "${format("tf-acc-vpc-endpoint-subnet-association-%d", count.index + 1)}" + Name = "${format("tf-acc-vpc-endpoint-subnet-association-%d", count.index + 1)}" } } diff --git a/aws/resource_aws_vpc_endpoint_test.go b/aws/resource_aws_vpc_endpoint_test.go index 7b7669d0f1d..c5c462de541 100644 --- a/aws/resource_aws_vpc_endpoint_test.go +++ b/aws/resource_aws_vpc_endpoint_test.go @@ -135,7 +135,7 @@ func TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup(t *testing.T) { resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "cidr_blocks.#", "0"), resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "vpc_endpoint_type", "Interface"), resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "route_table_ids.#", "0"), - resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "subnet_ids.#", "2"), + resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "subnet_ids.#", "3"), resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "security_group_ids.#", "1"), resource.TestCheckResourceAttr("aws_vpc_endpoint.ec2", "private_dns_enabled", "true"), ), @@ -438,7 +438,7 @@ data "aws_availability_zones" "available" {} resource "aws_subnet" "sn1" { vpc_id = "${aws_vpc.foo.id}" - cidr_block = "10.0.0.0/17" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, 0)}" availability_zone = "${data.aws_availability_zones.available.names[0]}" tags { Name = "tf-acc-vpc-endpoint-iface-w-subnet-1" @@ -447,13 +447,22 @@ resource "aws_subnet" "sn1" { resource "aws_subnet" "sn2" { vpc_id = "${aws_vpc.foo.id}" - cidr_block = "10.0.128.0/17" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, 1)}" availability_zone = "${data.aws_availability_zones.available.names[1]}" tags { Name = "tf-acc-vpc-endpoint-iface-w-subnet-2" } } +resource "aws_subnet" "sn3" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, 2)}" + availability_zone = "${data.aws_availability_zones.available.names[2]}" + tags { + Name = "tf-acc-vpc-endpoint-iface-w-subnet-3" + } +} + resource "aws_security_group" "sg1" { vpc_id = "${aws_vpc.foo.id}" } @@ -488,7 +497,7 @@ data "aws_availability_zones" "available" {} resource "aws_subnet" "sn1" { vpc_id = "${aws_vpc.foo.id}" - cidr_block = "10.0.0.0/17" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, 0)}" availability_zone = "${data.aws_availability_zones.available.names[0]}" tags { Name = "tf-acc-vpc-endpoint-iface-w-subnet-1" @@ -497,13 +506,22 @@ resource "aws_subnet" "sn1" { resource "aws_subnet" "sn2" { vpc_id = "${aws_vpc.foo.id}" - cidr_block = "10.0.128.0/17" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, 1)}" availability_zone = "${data.aws_availability_zones.available.names[1]}" tags { Name = "tf-acc-vpc-endpoint-iface-w-subnet-2" } } +resource "aws_subnet" "sn3" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "${cidrsubnet(aws_vpc.foo.cidr_block, 2, 2)}" + availability_zone = "${data.aws_availability_zones.available.names[2]}" + tags { + Name = "tf-acc-vpc-endpoint-iface-w-subnet-3" + } +} + resource "aws_security_group" "sg1" { vpc_id = "${aws_vpc.foo.id}" } @@ -516,7 +534,7 @@ resource "aws_vpc_endpoint" "ec2" { vpc_id = "${aws_vpc.foo.id}" service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" vpc_endpoint_type = "Interface" - subnet_ids = ["${aws_subnet.sn1.id}", "${aws_subnet.sn2.id}"] + subnet_ids = ["${aws_subnet.sn1.id}", "${aws_subnet.sn2.id}", "${aws_subnet.sn3.id}"] security_group_ids = ["${aws_security_group.sg1.id}"] private_dns_enabled = true } From eca12c6871facfbbe846d5589551191e08ca6d20 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 13 Jul 2018 17:27:03 -0400 Subject: [PATCH 6/6] Add back mutex and delay for concurrent modifications. --- ...rce_aws_vpc_endpoint_subnet_association.go | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_vpc_endpoint_subnet_association.go b/aws/resource_aws_vpc_endpoint_subnet_association.go index ab3f4bb0d90..0527eb14374 100644 --- a/aws/resource_aws_vpc_endpoint_subnet_association.go +++ b/aws/resource_aws_vpc_endpoint_subnet_association.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -52,10 +53,25 @@ func resourceAwsVpcEndpointSubnetAssociationCreate(d *schema.ResourceData, meta return err } - _, err = conn.ModifyVpcEndpoint(&ec2.ModifyVpcEndpointInput{ - VpcEndpointId: aws.String(endpointId), - AddSubnetIds: aws.StringSlice([]string{snId}), - }) + // See https://github.com/terraform-providers/terraform-provider-aws/issues/3382. + // Prevent concurrent subnet association requests and delay between requests. + mk := "vpc_endpoint_subnet_association_" + endpointId + awsMutexKV.Lock(mk) + defer awsMutexKV.Unlock(mk) + + c := &resource.StateChangeConf{ + Delay: 1 * time.Minute, + Timeout: 3 * time.Minute, + Target: []string{"ok"}, + Refresh: func() (interface{}, string, error) { + res, err := conn.ModifyVpcEndpoint(&ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(endpointId), + AddSubnetIds: aws.StringSlice([]string{snId}), + }) + return res, "ok", err + }, + } + _, err = c.WaitForState() if err != nil { return fmt.Errorf("Error creating Vpc Endpoint/Subnet association: %s", err) }