From a23cd5a6e799064ef79d4ee2d9bd3329faa0d0ed Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 11 Jun 2020 12:26:23 -0400 Subject: [PATCH 1/9] Add new resource 'aws_vpc_endpoint_security_group_association'. Test no security groups specified for Interface VPC endpoint. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpoint_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws/ -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpoint_ -timeout 120m === RUN TestAccAWSVpcEndpoint_gatewayBasic === PAUSE TestAccAWSVpcEndpoint_gatewayBasic === RUN TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicyAndTags === PAUSE TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicyAndTags === RUN TestAccAWSVpcEndpoint_gatewayPolicy === PAUSE TestAccAWSVpcEndpoint_gatewayPolicy === RUN TestAccAWSVpcEndpoint_interfaceBasic === PAUSE TestAccAWSVpcEndpoint_interfaceBasic === RUN TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup === PAUSE TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup === RUN TestAccAWSVpcEndpoint_interfaceNonAWSService === PAUSE TestAccAWSVpcEndpoint_interfaceNonAWSService === RUN TestAccAWSVpcEndpoint_disappears === PAUSE TestAccAWSVpcEndpoint_disappears === CONT TestAccAWSVpcEndpoint_gatewayBasic === CONT TestAccAWSVpcEndpoint_disappears --- PASS: TestAccAWSVpcEndpoint_disappears (48.99s) === CONT TestAccAWSVpcEndpoint_interfaceNonAWSService --- PASS: TestAccAWSVpcEndpoint_gatewayBasic (51.48s) === CONT TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup --- PASS: TestAccAWSVpcEndpoint_interfaceNonAWSService (330.97s) === CONT TestAccAWSVpcEndpoint_interfaceBasic --- PASS: TestAccAWSVpcEndpoint_interfaceBasic (112.93s) === CONT TestAccAWSVpcEndpoint_gatewayPolicy --- PASS: TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup (451.61s) === CONT TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicyAndTags --- PASS: TestAccAWSVpcEndpoint_gatewayPolicy (84.62s) --- PASS: TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicyAndTags (93.34s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 596.473s First acceptance test. Output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_basic' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws/ -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_basic -timeout 120m === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_basic === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_basic === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_basic --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_basic (136.41s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 136.460s Add 'TestAccAWSVpcEndpointSecurityGroupAssociation_disappears'. Output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_disappears' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws/ -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_disappears -timeout 120m === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_disappears --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_disappears (189.39s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 189.437s Remove resource importer. r/aws_vpc_endpoint_security_group_association: Add 'replace_default_association' attribute. Fix acceptance tests after rebase. Upgrade to Plugin SDK v2 and use Terraform 0.12 syntax in documentation. r/aws_vpc_endpoint: Add 'finder.VpcEndpointByID'. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpoint_' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpoint_ -timeout 120m === RUN TestAccAWSVpcEndpoint_gatewayBasic === PAUSE TestAccAWSVpcEndpoint_gatewayBasic === RUN TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy === PAUSE TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy === RUN TestAccAWSVpcEndpoint_gatewayPolicy === PAUSE TestAccAWSVpcEndpoint_gatewayPolicy === RUN TestAccAWSVpcEndpoint_interfaceBasic === PAUSE TestAccAWSVpcEndpoint_interfaceBasic === RUN TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup === PAUSE TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup === RUN TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate === PAUSE TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate === RUN TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate === PAUSE TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate === RUN TestAccAWSVpcEndpoint_disappears === PAUSE TestAccAWSVpcEndpoint_disappears === RUN TestAccAWSVpcEndpoint_tags === PAUSE TestAccAWSVpcEndpoint_tags === CONT TestAccAWSVpcEndpoint_gatewayBasic === CONT TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate --- PASS: TestAccAWSVpcEndpoint_gatewayBasic (39.27s) === CONT TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup --- PASS: TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate (272.03s) === CONT TestAccAWSVpcEndpoint_tags --- PASS: TestAccAWSVpcEndpoint_tags (94.20s) === CONT TestAccAWSVpcEndpoint_disappears --- PASS: TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup (344.51s) === CONT TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate === CONT TestAccAWSVpcEndpoint_disappears resource_aws_vpc_endpoint_test.go:445: [INFO] Got non-empty plan, as expected --- PASS: TestAccAWSVpcEndpoint_disappears (35.43s) === CONT TestAccAWSVpcEndpoint_gatewayPolicy --- PASS: TestAccAWSVpcEndpoint_gatewayPolicy (64.31s) === CONT TestAccAWSVpcEndpoint_interfaceBasic --- PASS: TestAccAWSVpcEndpoint_interfaceBasic (79.91s) === CONT TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy --- PASS: TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy (73.57s) --- PASS: TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate (326.78s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 710.605s r/aws_vpc_endpoint_security_group_association: Use internal 'finder' package. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_ -timeout 120m === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_basic === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_basic === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_basic === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation (104.16s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_multiple --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_basic (111.52s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_disappears resource_aws_vpc_endpoint_security_group_association_test.go:41: [INFO] Got non-empty plan, as expected --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_multiple (61.28s) --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_disappears (63.64s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 175.194s Fix linting issues. 'hcl' -> 'terraform' in documentation. r/aws_vpc_endpoint_*: Use internal finder and waiter packages. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_\|TestAccAWSVpcEndpoint_\|TestAccAWSVpcEndpointSubnetAssociation_' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_\|TestAccAWSVpcEndpoint_\|TestAccAWSVpcEndpointSubnetAssociation_ -timeout 180m === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_basic === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_basic === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === RUN TestAccAWSVpcEndpointSubnetAssociation_basic === PAUSE TestAccAWSVpcEndpointSubnetAssociation_basic === RUN TestAccAWSVpcEndpointSubnetAssociation_disappears === PAUSE TestAccAWSVpcEndpointSubnetAssociation_disappears === RUN TestAccAWSVpcEndpointSubnetAssociation_multiple === PAUSE TestAccAWSVpcEndpointSubnetAssociation_multiple === RUN TestAccAWSVpcEndpoint_gatewayBasic === PAUSE TestAccAWSVpcEndpoint_gatewayBasic === RUN TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy === PAUSE TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy === RUN TestAccAWSVpcEndpoint_gatewayPolicy === PAUSE TestAccAWSVpcEndpoint_gatewayPolicy === RUN TestAccAWSVpcEndpoint_interfaceBasic === PAUSE TestAccAWSVpcEndpoint_interfaceBasic === RUN TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup === PAUSE TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup === RUN TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate === PAUSE TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate === RUN TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate === PAUSE TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate === RUN TestAccAWSVpcEndpoint_disappears === PAUSE TestAccAWSVpcEndpoint_disappears === RUN TestAccAWSVpcEndpoint_tags === PAUSE TestAccAWSVpcEndpoint_tags === RUN TestAccAWSVpcEndpoint_VpcEndpointType_GatewayLoadBalancer === PAUSE TestAccAWSVpcEndpoint_VpcEndpointType_GatewayLoadBalancer === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_basic === CONT TestAccAWSVpcEndpoint_gatewayPolicy --- PASS: TestAccAWSVpcEndpoint_gatewayPolicy (59.54s) === CONT TestAccAWSVpcEndpoint_VpcEndpointType_GatewayLoadBalancer --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_basic (66.30s) === CONT TestAccAWSVpcEndpoint_tags --- PASS: TestAccAWSVpcEndpoint_tags (83.44s) === CONT TestAccAWSVpcEndpoint_disappears --- PASS: TestAccAWSVpcEndpoint_disappears (31.54s) === CONT TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate --- PASS: TestAccAWSVpcEndpoint_VpcEndpointType_GatewayLoadBalancer (324.96s) === CONT TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate --- PASS: TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnUpdate (277.00s) === CONT TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup --- PASS: TestAccAWSVpcEndpoint_interfaceNonAWSServiceAcceptOnCreate (271.89s) === CONT TestAccAWSVpcEndpoint_interfaceBasic --- PASS: TestAccAWSVpcEndpoint_interfaceBasic (76.82s) === CONT TestAccAWSVpcEndpointSubnetAssociation_disappears --- PASS: TestAccAWSVpcEndpoint_interfaceWithSubnetAndSecurityGroup (323.02s) === CONT TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy === CONT TestAccAWSVpcEndpoint_gatewayBasic --- PASS: TestAccAWSVpcEndpoint_gatewayWithRouteTableAndPolicy (67.98s) --- PASS: TestAccAWSVpcEndpoint_gatewayBasic (34.33s) === CONT TestAccAWSVpcEndpointSubnetAssociation_multiple --- PASS: TestAccAWSVpcEndpointSubnetAssociation_disappears (300.21s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation (67.70s) === CONT TestAccAWSVpcEndpointSubnetAssociation_basic --- PASS: TestAccAWSVpcEndpointSubnetAssociation_basic (247.76s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === CONT TestAccAWSVpcEndpointSubnetAssociation_multiple === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_disappears --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_multiple (107.91s) --- PASS: TestAccAWSVpcEndpointSubnetAssociation_multiple (585.07s) --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_disappears (76.35s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 1533.242s Add 'ErrorCheck' for new acceptance tests. Fix golangci-lint error: aws/resource_aws_vpc_endpoint_subnet_association_test.go:111:63: `testAccCheckVpcEndpointSubnetAssociationExists` - `vpce` is unused (unparam) func testAccCheckVpcEndpointSubnetAssociationExists(n string, vpce *ec2.VpcEndpoint) resource.TestCheckFunc { ^ Fix golangci-lint error: S1039: unnecessary use of fmt.Sprintf (gosimple) fmt.Sprintf(` ^ r/aws_route_table_association: Tidy up after rebase including #18465. r/aws_vpc_endpoint_route_table_association: Use internal finder package. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointRouteTableAssociation_' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointRouteTableAssociation_ -timeout 180m === RUN TestAccAWSVpcEndpointRouteTableAssociation_basic === PAUSE TestAccAWSVpcEndpointRouteTableAssociation_basic === RUN TestAccAWSVpcEndpointRouteTableAssociation_disappears === PAUSE TestAccAWSVpcEndpointRouteTableAssociation_disappears === CONT TestAccAWSVpcEndpointRouteTableAssociation_basic === CONT TestAccAWSVpcEndpointRouteTableAssociation_disappears --- PASS: TestAccAWSVpcEndpointRouteTableAssociation_disappears (36.58s) --- PASS: TestAccAWSVpcEndpointRouteTableAssociation_basic (39.06s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 39.161s r/aws_vpc_endpoint_security_group_association: 'VpcEndpointSecurityGroupAssociation' -> 'VpcEndpointSecurityGroupAssociationExists'. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_ -timeout 180m === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_basic === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_basic === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_basic === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation (65.31s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_multiple --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_basic (67.35s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_disappears --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_disappears (65.30s) --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_multiple (81.87s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 147.314s r/aws_vpc_endpoint_subnet_association: Use internal finder package. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSubnetAssociation_' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSubnetAssociation_ -timeout 180m === RUN TestAccAWSVpcEndpointSubnetAssociation_basic === PAUSE TestAccAWSVpcEndpointSubnetAssociation_basic === RUN TestAccAWSVpcEndpointSubnetAssociation_disappears === PAUSE TestAccAWSVpcEndpointSubnetAssociation_disappears === RUN TestAccAWSVpcEndpointSubnetAssociation_multiple === PAUSE TestAccAWSVpcEndpointSubnetAssociation_multiple === CONT TestAccAWSVpcEndpointSubnetAssociation_basic === CONT TestAccAWSVpcEndpointSubnetAssociation_multiple --- PASS: TestAccAWSVpcEndpointSubnetAssociation_basic (244.58s) === CONT TestAccAWSVpcEndpointSubnetAssociation_disappears --- PASS: TestAccAWSVpcEndpointSubnetAssociation_disappears (301.32s) --- PASS: TestAccAWSVpcEndpointSubnetAssociation_multiple (592.71s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 592.827s Set Name tag in acceptance tests where possible. Add CHANGELOG entry. 'ExistsPropagation' -> 'ExistancePropagation'. Acceptance test output: $ make testacc TEST=./aws/ TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_basic\|TestAccAWSVpcEndpointSubnetAssociation_basic\|TestAccAWSVpcEndpointRouteTableAssociation_basic' ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 2 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_basic\|TestAccAWSVpcEndpointSubnetAssociation_basic\|TestAccAWSVpcEndpointRouteTableAssociation_basic -timeout 180m === RUN TestAccAWSVpcEndpointRouteTableAssociation_basic === PAUSE TestAccAWSVpcEndpointRouteTableAssociation_basic === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_basic === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_basic === RUN TestAccAWSVpcEndpointSubnetAssociation_basic === PAUSE TestAccAWSVpcEndpointSubnetAssociation_basic === CONT TestAccAWSVpcEndpointRouteTableAssociation_basic === CONT TestAccAWSVpcEndpointSubnetAssociation_basic --- PASS: TestAccAWSVpcEndpointRouteTableAssociation_basic (38.09s) === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_basic --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_basic (82.50s) --- PASS: TestAccAWSVpcEndpointSubnetAssociation_basic (255.78s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 255.882s Fix golangci-lint error: S1021: should merge variable declaration with assignment on next line (gosimple) var err error ^ Add 'tfresource.RetryUntilFound'. Exclude 'aws/internal/tfresource/retry.go' from helper-schema-resource-Retry-without-TimeoutError-check. r/aws_vpc_endpoint_security_group_association: Don't retry on read. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSVpcEndpointSecurityGroupAssociation_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSVpcEndpointSecurityGroupAssociation_ -timeout 180m === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_basic === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_basic === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_multiple === RUN TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === PAUSE TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_basic === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_disappears === CONT TestAccAWSVpcEndpointSecurityGroupAssociation_multiple --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation (104.66s) --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_basic (108.84s) --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_multiple (111.20s) --- PASS: TestAccAWSVpcEndpointSecurityGroupAssociation_disappears (124.08s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 127.135s r/aws_vpc_endpoint_subnet_association: Don't retry on read. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSVpcEndpointSubnetAssociation_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSVpcEndpointSubnetAssociation_ -timeout 180m === RUN TestAccAWSVpcEndpointSubnetAssociation_basic === PAUSE TestAccAWSVpcEndpointSubnetAssociation_basic === RUN TestAccAWSVpcEndpointSubnetAssociation_disappears === PAUSE TestAccAWSVpcEndpointSubnetAssociation_disappears === RUN TestAccAWSVpcEndpointSubnetAssociation_multiple === PAUSE TestAccAWSVpcEndpointSubnetAssociation_multiple === CONT TestAccAWSVpcEndpointSubnetAssociation_basic === CONT TestAccAWSVpcEndpointSubnetAssociation_multiple === CONT TestAccAWSVpcEndpointSubnetAssociation_disappears --- PASS: TestAccAWSVpcEndpointSubnetAssociation_disappears (258.95s) --- PASS: TestAccAWSVpcEndpointSubnetAssociation_basic (259.71s) --- PASS: TestAccAWSVpcEndpointSubnetAssociation_multiple (545.62s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 548.634s --- .changelog/13737.txt | 7 + aws/internal/service/ec2/finder/finder.go | 47 ++++ aws/internal/service/ec2/id.go | 4 + aws/provider.go | 1 + aws/resource_aws_vpc_endpoint.go | 6 - ...vpc_endpoint_security_group_association.go | 200 ++++++++++++++ ...ndpoint_security_group_association_test.go | 246 ++++++++++++++++++ aws/resource_aws_vpc_endpoint_test.go | 9 - website/docs/r/vpc_endpoint.html.markdown | 10 +- ...t_security_group_association.html.markdown | 43 +++ 10 files changed, 554 insertions(+), 19 deletions(-) create mode 100644 .changelog/13737.txt create mode 100644 aws/resource_aws_vpc_endpoint_security_group_association.go create mode 100644 aws/resource_aws_vpc_endpoint_security_group_association_test.go create mode 100644 website/docs/r/vpc_endpoint_security_group_association.html.markdown diff --git a/.changelog/13737.txt b/.changelog/13737.txt new file mode 100644 index 00000000000..8e70f9228b3 --- /dev/null +++ b/.changelog/13737.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_vpc_endpoint_security_group_association +``` + +```release-note:enhancement +resource/aws_vpc_endpoint: The `security_group_ids` attribute can now be empty when the resource is created. In this case the VPC's default security is associated with the VPC endpoint +``` diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index 81d99aff35b..7b5edb1680b 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -407,6 +407,34 @@ func RouteByPrefixListIDDestination(conn *ec2.EC2, routeTableID, prefixListID st return nil, &resource.NotFoundError{} } +// DefaultSecurityGroup returns the default security group for the specified VPC. +// Returns NotFoundError if no default security group is found. +func DefaultSecurityGroup(conn *ec2.EC2, vpcID string) (*ec2.SecurityGroup, error) { + filters := map[string]string{ + "group-name": "default", + "vpc-id": vpcID, + } + + input := &ec2.DescribeSecurityGroupsInput{ + Filters: tfec2.BuildAttributeFilterList(filters), + } + + output, err := conn.DescribeSecurityGroups(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.SecurityGroups) == 0 || output.SecurityGroups[0] == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.SecurityGroups[0], nil +} + // SecurityGroupByID looks up a security group by ID. When not found, returns nil and potentially an API error. func SecurityGroupByID(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { req := &ec2.DescribeSecurityGroupsInput{ @@ -692,6 +720,25 @@ func VpcEndpointRouteTableAssociationExists(conn *ec2.EC2, vpcEndpointID string, } } +// VpcEndpointRouteTableAssociationExists returns NotFoundError if no association for the specified VPC endpoint and security group IDs is found. +func VpcEndpointSecurityGroupAssociationExists(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + vpcEndpoint, err := VpcEndpointByID(conn, vpcEndpointID) + + if err != nil { + return err + } + + for _, group := range vpcEndpoint.Groups { + if aws.StringValue(group.GroupId) == securityGroupID { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint Security Group Association (%s/%s) not found", vpcEndpointID, securityGroupID), + } +} + // VpcEndpointSubnetAssociationExists returns NotFoundError if no association for the specified VPC endpoint and subnet IDs is found. func VpcEndpointSubnetAssociationExists(conn *ec2.EC2, vpcEndpointID string, subnetID string) error { vpcEndpoint, err := VpcEndpointByID(conn, vpcEndpointID) diff --git a/aws/internal/service/ec2/id.go b/aws/internal/service/ec2/id.go index cf5cb8a0cb3..49b9dc1bfa2 100644 --- a/aws/internal/service/ec2/id.go +++ b/aws/internal/service/ec2/id.go @@ -99,6 +99,10 @@ func VpcEndpointRouteTableAssociationCreateID(vpcEndpointID, routeTableID string return fmt.Sprintf("a-%s%d", vpcEndpointID, hashcode.String(routeTableID)) } +func VpcEndpointSecurityGroupAssociationCreateID(vpcEndpointID, securityGroupID string) string { + return fmt.Sprintf("a-%s%d", vpcEndpointID, hashcode.String(securityGroupID)) +} + func VpcEndpointSubnetAssociationCreateID(vpcEndpointID, subnetID string) string { return fmt.Sprintf("a-%s%d", vpcEndpointID, hashcode.String(subnetID)) } diff --git a/aws/provider.go b/aws/provider.go index 5e91aefcac2..7351c520908 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -1119,6 +1119,7 @@ func Provider() *schema.Provider { "aws_vpc_endpoint": resourceAwsVpcEndpoint(), "aws_vpc_endpoint_connection_notification": resourceAwsVpcEndpointConnectionNotification(), "aws_vpc_endpoint_route_table_association": resourceAwsVpcEndpointRouteTableAssociation(), + "aws_vpc_endpoint_security_group_association": resourceAwsVpcEndpointSecurityGroupAssociation(), "aws_vpc_endpoint_subnet_association": resourceAwsVpcEndpointSubnetAssociation(), "aws_vpc_endpoint_service": resourceAwsVpcEndpointService(), "aws_vpc_endpoint_service_allowed_principal": resourceAwsVpcEndpointServiceAllowedPrincipal(), diff --git a/aws/resource_aws_vpc_endpoint.go b/aws/resource_aws_vpc_endpoint.go index 0f372c3acf0..2044ef67993 100644 --- a/aws/resource_aws_vpc_endpoint.go +++ b/aws/resource_aws_vpc_endpoint.go @@ -1,7 +1,6 @@ package aws import ( - "errors" "fmt" "log" "time" @@ -156,11 +155,6 @@ func resourceAwsVpcEndpoint() *schema.Resource { } func resourceAwsVpcEndpointCreate(d *schema.ResourceData, meta interface{}) error { - if d.Get("vpc_endpoint_type").(string) == ec2.VpcEndpointTypeInterface && - d.Get("security_group_ids").(*schema.Set).Len() == 0 { - return errors.New("An Interface VPC Endpoint must always have at least one Security Group") - } - conn := meta.(*AWSClient).ec2conn defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) diff --git a/aws/resource_aws_vpc_endpoint_security_group_association.go b/aws/resource_aws_vpc_endpoint_security_group_association.go new file mode 100644 index 00000000000..f449d202529 --- /dev/null +++ b/aws/resource_aws_vpc_endpoint_security_group_association.go @@ -0,0 +1,200 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsVpcEndpointSecurityGroupAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsVpcEndpointSecurityGroupAssociationCreate, + Read: resourceAwsVpcEndpointSecurityGroupAssociationRead, + Delete: resourceAwsVpcEndpointSecurityGroupAssociationDelete, + + Schema: map[string]*schema.Schema{ + "replace_default_association": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + "security_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "vpc_endpoint_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsVpcEndpointSecurityGroupAssociationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + vpcEndpointID := d.Get("vpc_endpoint_id").(string) + securityGroupID := d.Get("security_group_id").(string) + replaceDefaultAssociation := d.Get("replace_default_association").(bool) + + defaultSecurityGroupID := "" + if replaceDefaultAssociation { + vpcEndpoint, err := finder.VpcEndpointByID(conn, vpcEndpointID) + + if err != nil { + return fmt.Errorf("error reading VPC endpoint (%s): %w", vpcEndpointID, err) + } + + vpcID := aws.StringValue(vpcEndpoint.VpcId) + + defaultSecurityGroup, err := finder.DefaultSecurityGroup(conn, vpcID) + + if err != nil { + return fmt.Errorf("error reading default security group for VPC (%s): %w", vpcID, err) + } + + defaultSecurityGroupID = aws.StringValue(defaultSecurityGroup.GroupId) + + if defaultSecurityGroupID == securityGroupID { + return fmt.Errorf("%s is the default security group for VPC (%s)", securityGroupID, vpcID) + } + + foundDefaultAssociation := false + + for _, group := range vpcEndpoint.Groups { + if aws.StringValue(group.GroupId) == defaultSecurityGroupID { + foundDefaultAssociation = true + break + } + } + + if !foundDefaultAssociation { + return fmt.Errorf("no association of default security group (%s) with VPC endpoint (%s)", defaultSecurityGroupID, vpcEndpointID) + } + } + + err := createVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, securityGroupID) + + if err != nil { + return err + } + + d.SetId(tfec2.VpcEndpointSecurityGroupAssociationCreateID(vpcEndpointID, securityGroupID)) + + if replaceDefaultAssociation { + // Delete the existing VPC endpoint/default security group association. + err := deleteVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, defaultSecurityGroupID) + + if err != nil { + return err + } + } + + return resourceAwsVpcEndpointSecurityGroupAssociationRead(d, meta) +} + +func resourceAwsVpcEndpointSecurityGroupAssociationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + vpcEndpointID := d.Get("vpc_endpoint_id").(string) + securityGroupID := d.Get("security_group_id").(string) + // Human friendly ID for error messages since d.Id() is non-descriptive + id := fmt.Sprintf("%s/%s", vpcEndpointID, securityGroupID) + + err := finder.VpcEndpointSecurityGroupAssociationExists(conn, vpcEndpointID, securityGroupID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] VPC Endpoint Security Group Association (%s) not found, removing from state", id) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading VPC Security Group Association (%s): %w", id, err) + } + + return nil +} + +func resourceAwsVpcEndpointSecurityGroupAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + vpcEndpointID := d.Get("vpc_endpoint_id").(string) + securityGroupID := d.Get("security_group_id").(string) + replaceDefaultAssociation := d.Get("replace_default_association").(bool) + + if replaceDefaultAssociation { + vpcEndpoint, err := finder.VpcEndpointByID(conn, vpcEndpointID) + + if err != nil { + return fmt.Errorf("error reading VPC endpoint (%s): %w", vpcEndpointID, err) + } + + vpcID := aws.StringValue(vpcEndpoint.VpcId) + + defaultSecurityGroup, err := finder.DefaultSecurityGroup(conn, vpcID) + + if err != nil { + return fmt.Errorf("error reading default security group for VPC (%s): %w", vpcID, err) + } + + // Add back the VPC endpoint/default security group association. + err = createVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, aws.StringValue(defaultSecurityGroup.GroupId)) + + if err != nil { + return err + } + } + + return deleteVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, securityGroupID) +} + +// createVpcEndpointSecurityGroupAssociation creates the specified VPC endpoint/security group association. +func createVpcEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + input := &ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(vpcEndpointID), + AddSecurityGroupIds: aws.StringSlice([]string{securityGroupID}), + } + + log.Printf("[DEBUG] Creating VPC Endpoint Security Group Association: %s", input) + + _, err := conn.ModifyVpcEndpoint(input) + + if err != nil { + return fmt.Errorf("error creating VPC Endpoint Security Group Association (%s/%s): %w", vpcEndpointID, securityGroupID, err) + } + + return nil +} + +// deleteVpcEndpointSecurityGroupAssociation deletes the specified VPC endpoint/security group association. +func deleteVpcEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + input := &ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(vpcEndpointID), + RemoveSecurityGroupIds: aws.StringSlice([]string{securityGroupID}), + } + + log.Printf("[DEBUG] Deleting VPC Endpoint Security Group Association: %s", input) + + _, err := conn.ModifyVpcEndpoint(input) + + if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcEndpointIdNotFound) || tfawserr.ErrCodeEquals(err, tfec2.InvalidGroupNotFound) || tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidParameter) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting VPC Endpoint Security Group Association (%s/%s): %w", vpcEndpointID, securityGroupID, err) + } + + return nil +} diff --git a/aws/resource_aws_vpc_endpoint_security_group_association_test.go b/aws/resource_aws_vpc_endpoint_security_group_association_test.go new file mode 100644 index 00000000000..5baeb37d2ff --- /dev/null +++ b/aws/resource_aws_vpc_endpoint_security_group_association_test.go @@ -0,0 +1,246 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func TestAccAWSVpcEndpointSecurityGroupAssociation_basic(t *testing.T) { + var endpoint ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint_security_group_association.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &endpoint), + testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&endpoint, 2), + ), + }, + }, + }) +} + +func TestAccAWSVpcEndpointSecurityGroupAssociation_disappears(t *testing.T) { + var endpoint ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint_security_group_association.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &endpoint), + testAccCheckResourceDisappears(testAccProvider, resourceAwsVpcEndpointSecurityGroupAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSVpcEndpointSecurityGroupAssociation_multiple(t *testing.T) { + var endpoint ec2.VpcEndpoint + resourceName0 := "aws_vpc_endpoint_security_group_association.test.0" + resourceName1 := "aws_vpc_endpoint_security_group_association.test.1" + resourceName2 := "aws_vpc_endpoint_security_group_association.test.2" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigMultiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName0, &endpoint), + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName1, &endpoint), + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName2, &endpoint), + testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&endpoint, 4), + ), + }, + }, + }) +} + +func TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation(t *testing.T) { + var endpoint ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint_security_group_association.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigReplaceDefaultAssociation(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &endpoint), + testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&endpoint, 1), + ), + }, + }, + }) +} + +func testAccCheckVpcEndpointSecurityGroupAssociationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpc_endpoint_security_group_association" { + continue + } + + out, err := finder.VpcEndpointByID(conn, rs.Primary.Attributes["vpc_endpoint_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + // VPC Endpoint will always have 1 SG. + if len(out.Groups) > 1 { + return fmt.Errorf("VPC endpoint %s has security groups", aws.StringValue(out.VpcEndpointId)) + } + } + + return nil +} + +func testAccCheckVpcEndpointSecurityGroupAssociationExists(n string, vpce *ec2.VpcEndpoint) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + out, err := finder.VpcEndpointByID(conn, rs.Primary.Attributes["vpc_endpoint_id"]) + + if err != nil { + return err + } + + err = finder.VpcEndpointSecurityGroupAssociationExists(conn, rs.Primary.Attributes["vpc_endpoint_id"], rs.Primary.Attributes["security_group_id"]) + + if err != nil { + return err + } + + *vpce = *out + + return nil + } +} + +func testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(vpce *ec2.VpcEndpoint, n int) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len := len(vpce.Groups); len != n { + return fmt.Errorf("got %d associations; wanted %d", len, n) + } + + return nil + } +} + +func testAccVpcEndpointSecurityGroupAssociationConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +data "aws_region" "current" {} + +resource "aws_security_group" "test" { + count = 3 + + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" + vpc_endpoint_type = "Interface" + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName string) string { + return composeConfig( + testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), + ` +resource "aws_vpc_endpoint_security_group_association" "test" { + vpc_endpoint_id = aws_vpc_endpoint.test.id + security_group_id = aws_security_group.test[0].id +} +`) +} + +func testAccVpcEndpointSecurityGroupAssociationConfigMultiple(rName string) string { + return composeConfig( + testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), + ` +resource "aws_vpc_endpoint_security_group_association" "test" { + count = length(aws_security_group.test) + + vpc_endpoint_id = aws_vpc_endpoint.test.id + security_group_id = aws_security_group.test[count.index].id +} +`) +} + +func testAccVpcEndpointSecurityGroupAssociationConfigReplaceDefaultAssociation(rName string) string { + return composeConfig( + testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), + ` +resource "aws_vpc_endpoint_security_group_association" "test" { + vpc_endpoint_id = aws_vpc_endpoint.test.id + security_group_id = aws_security_group.test[0].id + + replace_default_association = true +} +`) +} diff --git a/aws/resource_aws_vpc_endpoint_test.go b/aws/resource_aws_vpc_endpoint_test.go index 40c22ec21d8..55883b2fc4e 100644 --- a/aws/resource_aws_vpc_endpoint_test.go +++ b/aws/resource_aws_vpc_endpoint_test.go @@ -782,21 +782,12 @@ resource "aws_vpc" "test" { } } -data "aws_security_group" "test" { - vpc_id = aws_vpc.test.id - name = "default" -} - data "aws_region" "current" {} resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" vpc_endpoint_type = "Interface" - - security_group_ids = [ - data.aws_security_group.test.id, - ] } `, rName) } diff --git a/website/docs/r/vpc_endpoint.html.markdown b/website/docs/r/vpc_endpoint.html.markdown index 368dd73fa2d..fab79c89e34 100644 --- a/website/docs/r/vpc_endpoint.html.markdown +++ b/website/docs/r/vpc_endpoint.html.markdown @@ -11,9 +11,10 @@ description: |- Provides a VPC Endpoint resource. ~> **NOTE on VPC Endpoints and VPC Endpoint Associations:** Terraform provides both standalone VPC Endpoint Associations for -[Route Tables](vpc_endpoint_route_table_association.html) - (an association between a VPC endpoint and a single `route_table_id`) and -[Subnets](vpc_endpoint_subnet_association.html) - (an association between a VPC endpoint and a single `subnet_id`) and -a VPC Endpoint resource with `route_table_ids` and `subnet_ids` attributes. +[Route Tables](vpc_endpoint_route_table_association.html) - (an association between a VPC endpoint and a single `route_table_id`), +[Security Groups](vpc_endpoint_security_group_association.html) - (an association between a VPC endpoint and a single `security_group_id`), +and [Subnets](vpc_endpoint_subnet_association.html) - (an association between a VPC endpoint and a single `subnet_id`) and +a VPC Endpoint resource with `route_table_ids`, `security_group_ids` and `subnet_ids` attributes. Do not use the same resource ID in both a VPC Endpoint resource and a VPC Endpoint Association resource. Doing so will cause a conflict of associations and will overwrite the association. @@ -121,7 +122,8 @@ The following arguments are supported: Defaults to `false`. * `route_table_ids` - (Optional) One or more route table IDs. Applicable for endpoints of type `Gateway`. * `subnet_ids` - (Optional) The ID of one or more subnets in which to create a network interface for the endpoint. Applicable for endpoints of type `GatewayLoadBalancer` and `Interface`. -* `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Required for endpoints of type `Interface`. +* `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Applicable for endpoints of type `Interface`. +If no security groups are specified, the VPC's [default security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html#DefaultSecurityGroup) is associated with the endpoint. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `vpc_endpoint_type` - (Optional) The VPC endpoint type, `Gateway`, `GatewayLoadBalancer`, or `Interface`. Defaults to `Gateway`. diff --git a/website/docs/r/vpc_endpoint_security_group_association.html.markdown b/website/docs/r/vpc_endpoint_security_group_association.html.markdown new file mode 100644 index 00000000000..44cb62e3272 --- /dev/null +++ b/website/docs/r/vpc_endpoint_security_group_association.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "VPC" +layout: "aws" +page_title: "AWS: aws_vpc_endpoint_security_group_association" +description: |- + Provides a resource to create an association between a VPC endpoint and a security group. +--- + +# Resource: aws_vpc_endpoint_security_group_association + +Provides a resource to create an association between a VPC endpoint and a security group. + +~> **NOTE on VPC Endpoints and VPC Endpoint Security Group Associations:** Terraform provides +both a standalone VPC Endpoint Security Group Association (an association between a VPC endpoint +and a single `security_group_id`) and a [VPC Endpoint](vpc_endpoint.html) resource with a `security_group_ids` +attribute. Do not use the same security group ID in both a VPC Endpoint resource and a VPC Endpoint Security +Group Association resource. Doing so will cause a conflict of associations and will overwrite the association. + +## Example Usage + +Basic usage: + +```terraform +resource "aws_vpc_endpoint_security_group_association" "sg_ec2" { + vpc_endpoint_id = aws_vpc_endpoint.ec2.id + security_group_id = aws_security_group.sg.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `security_group_id` - (Required) The ID of the security group to be associated with the VPC endpoint. +* `vpc_endpoint_id` - (Required) The ID of the VPC endpoint with which the security group will be associated. +* `replace_default_association` - (Optional) Whether this association should replace the association with the VPC's default security group that is created when no security groups are specified during VPC endpoint creation. +At most 1 association per-VPC endpoint should be configured with `replace_default_association = true`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the association. From a1e4de62036bb33bc0a4b429033e68f9d3fee598 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 09:22:12 -0500 Subject: [PATCH 2/9] Revert "Add new resource 'aws_vpc_endpoint_security_group_association'." This reverts commit a23cd5a6e799064ef79d4ee2d9bd3329faa0d0ed. --- .changelog/13737.txt | 7 - aws/internal/service/ec2/finder/finder.go | 47 ---- aws/internal/service/ec2/id.go | 4 - aws/provider.go | 1 - aws/resource_aws_vpc_endpoint.go | 6 + ...vpc_endpoint_security_group_association.go | 200 -------------- ...ndpoint_security_group_association_test.go | 246 ------------------ aws/resource_aws_vpc_endpoint_test.go | 9 + website/docs/r/vpc_endpoint.html.markdown | 10 +- ...t_security_group_association.html.markdown | 43 --- 10 files changed, 19 insertions(+), 554 deletions(-) delete mode 100644 .changelog/13737.txt delete mode 100644 aws/resource_aws_vpc_endpoint_security_group_association.go delete mode 100644 aws/resource_aws_vpc_endpoint_security_group_association_test.go delete mode 100644 website/docs/r/vpc_endpoint_security_group_association.html.markdown diff --git a/.changelog/13737.txt b/.changelog/13737.txt deleted file mode 100644 index 8e70f9228b3..00000000000 --- a/.changelog/13737.txt +++ /dev/null @@ -1,7 +0,0 @@ -```release-note:new-resource -aws_vpc_endpoint_security_group_association -``` - -```release-note:enhancement -resource/aws_vpc_endpoint: The `security_group_ids` attribute can now be empty when the resource is created. In this case the VPC's default security is associated with the VPC endpoint -``` diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index 7b5edb1680b..81d99aff35b 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -407,34 +407,6 @@ func RouteByPrefixListIDDestination(conn *ec2.EC2, routeTableID, prefixListID st return nil, &resource.NotFoundError{} } -// DefaultSecurityGroup returns the default security group for the specified VPC. -// Returns NotFoundError if no default security group is found. -func DefaultSecurityGroup(conn *ec2.EC2, vpcID string) (*ec2.SecurityGroup, error) { - filters := map[string]string{ - "group-name": "default", - "vpc-id": vpcID, - } - - input := &ec2.DescribeSecurityGroupsInput{ - Filters: tfec2.BuildAttributeFilterList(filters), - } - - output, err := conn.DescribeSecurityGroups(input) - - if err != nil { - return nil, err - } - - if output == nil || len(output.SecurityGroups) == 0 || output.SecurityGroups[0] == nil { - return nil, &resource.NotFoundError{ - Message: "Empty result", - LastRequest: input, - } - } - - return output.SecurityGroups[0], nil -} - // SecurityGroupByID looks up a security group by ID. When not found, returns nil and potentially an API error. func SecurityGroupByID(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { req := &ec2.DescribeSecurityGroupsInput{ @@ -720,25 +692,6 @@ func VpcEndpointRouteTableAssociationExists(conn *ec2.EC2, vpcEndpointID string, } } -// VpcEndpointRouteTableAssociationExists returns NotFoundError if no association for the specified VPC endpoint and security group IDs is found. -func VpcEndpointSecurityGroupAssociationExists(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { - vpcEndpoint, err := VpcEndpointByID(conn, vpcEndpointID) - - if err != nil { - return err - } - - for _, group := range vpcEndpoint.Groups { - if aws.StringValue(group.GroupId) == securityGroupID { - return nil - } - } - - return &resource.NotFoundError{ - LastError: fmt.Errorf("VPC Endpoint Security Group Association (%s/%s) not found", vpcEndpointID, securityGroupID), - } -} - // VpcEndpointSubnetAssociationExists returns NotFoundError if no association for the specified VPC endpoint and subnet IDs is found. func VpcEndpointSubnetAssociationExists(conn *ec2.EC2, vpcEndpointID string, subnetID string) error { vpcEndpoint, err := VpcEndpointByID(conn, vpcEndpointID) diff --git a/aws/internal/service/ec2/id.go b/aws/internal/service/ec2/id.go index 49b9dc1bfa2..cf5cb8a0cb3 100644 --- a/aws/internal/service/ec2/id.go +++ b/aws/internal/service/ec2/id.go @@ -99,10 +99,6 @@ func VpcEndpointRouteTableAssociationCreateID(vpcEndpointID, routeTableID string return fmt.Sprintf("a-%s%d", vpcEndpointID, hashcode.String(routeTableID)) } -func VpcEndpointSecurityGroupAssociationCreateID(vpcEndpointID, securityGroupID string) string { - return fmt.Sprintf("a-%s%d", vpcEndpointID, hashcode.String(securityGroupID)) -} - func VpcEndpointSubnetAssociationCreateID(vpcEndpointID, subnetID string) string { return fmt.Sprintf("a-%s%d", vpcEndpointID, hashcode.String(subnetID)) } diff --git a/aws/provider.go b/aws/provider.go index 7351c520908..5e91aefcac2 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -1119,7 +1119,6 @@ func Provider() *schema.Provider { "aws_vpc_endpoint": resourceAwsVpcEndpoint(), "aws_vpc_endpoint_connection_notification": resourceAwsVpcEndpointConnectionNotification(), "aws_vpc_endpoint_route_table_association": resourceAwsVpcEndpointRouteTableAssociation(), - "aws_vpc_endpoint_security_group_association": resourceAwsVpcEndpointSecurityGroupAssociation(), "aws_vpc_endpoint_subnet_association": resourceAwsVpcEndpointSubnetAssociation(), "aws_vpc_endpoint_service": resourceAwsVpcEndpointService(), "aws_vpc_endpoint_service_allowed_principal": resourceAwsVpcEndpointServiceAllowedPrincipal(), diff --git a/aws/resource_aws_vpc_endpoint.go b/aws/resource_aws_vpc_endpoint.go index 2044ef67993..0f372c3acf0 100644 --- a/aws/resource_aws_vpc_endpoint.go +++ b/aws/resource_aws_vpc_endpoint.go @@ -1,6 +1,7 @@ package aws import ( + "errors" "fmt" "log" "time" @@ -155,6 +156,11 @@ func resourceAwsVpcEndpoint() *schema.Resource { } func resourceAwsVpcEndpointCreate(d *schema.ResourceData, meta interface{}) error { + if d.Get("vpc_endpoint_type").(string) == ec2.VpcEndpointTypeInterface && + d.Get("security_group_ids").(*schema.Set).Len() == 0 { + return errors.New("An Interface VPC Endpoint must always have at least one Security Group") + } + conn := meta.(*AWSClient).ec2conn defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) diff --git a/aws/resource_aws_vpc_endpoint_security_group_association.go b/aws/resource_aws_vpc_endpoint_security_group_association.go deleted file mode 100644 index f449d202529..00000000000 --- a/aws/resource_aws_vpc_endpoint_security_group_association.go +++ /dev/null @@ -1,200 +0,0 @@ -package aws - -import ( - "fmt" - "log" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" -) - -func resourceAwsVpcEndpointSecurityGroupAssociation() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsVpcEndpointSecurityGroupAssociationCreate, - Read: resourceAwsVpcEndpointSecurityGroupAssociationRead, - Delete: resourceAwsVpcEndpointSecurityGroupAssociationDelete, - - Schema: map[string]*schema.Schema{ - "replace_default_association": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, - }, - "security_group_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "vpc_endpoint_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - }, - } -} - -func resourceAwsVpcEndpointSecurityGroupAssociationCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - vpcEndpointID := d.Get("vpc_endpoint_id").(string) - securityGroupID := d.Get("security_group_id").(string) - replaceDefaultAssociation := d.Get("replace_default_association").(bool) - - defaultSecurityGroupID := "" - if replaceDefaultAssociation { - vpcEndpoint, err := finder.VpcEndpointByID(conn, vpcEndpointID) - - if err != nil { - return fmt.Errorf("error reading VPC endpoint (%s): %w", vpcEndpointID, err) - } - - vpcID := aws.StringValue(vpcEndpoint.VpcId) - - defaultSecurityGroup, err := finder.DefaultSecurityGroup(conn, vpcID) - - if err != nil { - return fmt.Errorf("error reading default security group for VPC (%s): %w", vpcID, err) - } - - defaultSecurityGroupID = aws.StringValue(defaultSecurityGroup.GroupId) - - if defaultSecurityGroupID == securityGroupID { - return fmt.Errorf("%s is the default security group for VPC (%s)", securityGroupID, vpcID) - } - - foundDefaultAssociation := false - - for _, group := range vpcEndpoint.Groups { - if aws.StringValue(group.GroupId) == defaultSecurityGroupID { - foundDefaultAssociation = true - break - } - } - - if !foundDefaultAssociation { - return fmt.Errorf("no association of default security group (%s) with VPC endpoint (%s)", defaultSecurityGroupID, vpcEndpointID) - } - } - - err := createVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, securityGroupID) - - if err != nil { - return err - } - - d.SetId(tfec2.VpcEndpointSecurityGroupAssociationCreateID(vpcEndpointID, securityGroupID)) - - if replaceDefaultAssociation { - // Delete the existing VPC endpoint/default security group association. - err := deleteVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, defaultSecurityGroupID) - - if err != nil { - return err - } - } - - return resourceAwsVpcEndpointSecurityGroupAssociationRead(d, meta) -} - -func resourceAwsVpcEndpointSecurityGroupAssociationRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - vpcEndpointID := d.Get("vpc_endpoint_id").(string) - securityGroupID := d.Get("security_group_id").(string) - // Human friendly ID for error messages since d.Id() is non-descriptive - id := fmt.Sprintf("%s/%s", vpcEndpointID, securityGroupID) - - err := finder.VpcEndpointSecurityGroupAssociationExists(conn, vpcEndpointID, securityGroupID) - - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] VPC Endpoint Security Group Association (%s) not found, removing from state", id) - d.SetId("") - return nil - } - - if err != nil { - return fmt.Errorf("error reading VPC Security Group Association (%s): %w", id, err) - } - - return nil -} - -func resourceAwsVpcEndpointSecurityGroupAssociationDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn - - vpcEndpointID := d.Get("vpc_endpoint_id").(string) - securityGroupID := d.Get("security_group_id").(string) - replaceDefaultAssociation := d.Get("replace_default_association").(bool) - - if replaceDefaultAssociation { - vpcEndpoint, err := finder.VpcEndpointByID(conn, vpcEndpointID) - - if err != nil { - return fmt.Errorf("error reading VPC endpoint (%s): %w", vpcEndpointID, err) - } - - vpcID := aws.StringValue(vpcEndpoint.VpcId) - - defaultSecurityGroup, err := finder.DefaultSecurityGroup(conn, vpcID) - - if err != nil { - return fmt.Errorf("error reading default security group for VPC (%s): %w", vpcID, err) - } - - // Add back the VPC endpoint/default security group association. - err = createVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, aws.StringValue(defaultSecurityGroup.GroupId)) - - if err != nil { - return err - } - } - - return deleteVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, securityGroupID) -} - -// createVpcEndpointSecurityGroupAssociation creates the specified VPC endpoint/security group association. -func createVpcEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { - input := &ec2.ModifyVpcEndpointInput{ - VpcEndpointId: aws.String(vpcEndpointID), - AddSecurityGroupIds: aws.StringSlice([]string{securityGroupID}), - } - - log.Printf("[DEBUG] Creating VPC Endpoint Security Group Association: %s", input) - - _, err := conn.ModifyVpcEndpoint(input) - - if err != nil { - return fmt.Errorf("error creating VPC Endpoint Security Group Association (%s/%s): %w", vpcEndpointID, securityGroupID, err) - } - - return nil -} - -// deleteVpcEndpointSecurityGroupAssociation deletes the specified VPC endpoint/security group association. -func deleteVpcEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { - input := &ec2.ModifyVpcEndpointInput{ - VpcEndpointId: aws.String(vpcEndpointID), - RemoveSecurityGroupIds: aws.StringSlice([]string{securityGroupID}), - } - - log.Printf("[DEBUG] Deleting VPC Endpoint Security Group Association: %s", input) - - _, err := conn.ModifyVpcEndpoint(input) - - if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcEndpointIdNotFound) || tfawserr.ErrCodeEquals(err, tfec2.InvalidGroupNotFound) || tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidParameter) { - return nil - } - - if err != nil { - return fmt.Errorf("error deleting VPC Endpoint Security Group Association (%s/%s): %w", vpcEndpointID, securityGroupID, err) - } - - return nil -} diff --git a/aws/resource_aws_vpc_endpoint_security_group_association_test.go b/aws/resource_aws_vpc_endpoint_security_group_association_test.go deleted file mode 100644 index 5baeb37d2ff..00000000000 --- a/aws/resource_aws_vpc_endpoint_security_group_association_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" -) - -func TestAccAWSVpcEndpointSecurityGroupAssociation_basic(t *testing.T) { - var endpoint ec2.VpcEndpoint - resourceName := "aws_vpc_endpoint_security_group_association.test" - rName := acctest.RandomWithPrefix("tf-acc-test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &endpoint), - testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&endpoint, 2), - ), - }, - }, - }) -} - -func TestAccAWSVpcEndpointSecurityGroupAssociation_disappears(t *testing.T) { - var endpoint ec2.VpcEndpoint - resourceName := "aws_vpc_endpoint_security_group_association.test" - rName := acctest.RandomWithPrefix("tf-acc-test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &endpoint), - testAccCheckResourceDisappears(testAccProvider, resourceAwsVpcEndpointSecurityGroupAssociation(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func TestAccAWSVpcEndpointSecurityGroupAssociation_multiple(t *testing.T) { - var endpoint ec2.VpcEndpoint - resourceName0 := "aws_vpc_endpoint_security_group_association.test.0" - resourceName1 := "aws_vpc_endpoint_security_group_association.test.1" - resourceName2 := "aws_vpc_endpoint_security_group_association.test.2" - rName := acctest.RandomWithPrefix("tf-acc-test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcEndpointSecurityGroupAssociationConfigMultiple(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName0, &endpoint), - testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName1, &endpoint), - testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName2, &endpoint), - testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&endpoint, 4), - ), - }, - }, - }) -} - -func TestAccAWSVpcEndpointSecurityGroupAssociation_ReplaceDefaultAssociation(t *testing.T) { - var endpoint ec2.VpcEndpoint - resourceName := "aws_vpc_endpoint_security_group_association.test" - rName := acctest.RandomWithPrefix("tf-acc-test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccVpcEndpointSecurityGroupAssociationConfigReplaceDefaultAssociation(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &endpoint), - testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&endpoint, 1), - ), - }, - }, - }) -} - -func testAccCheckVpcEndpointSecurityGroupAssociationDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_vpc_endpoint_security_group_association" { - continue - } - - out, err := finder.VpcEndpointByID(conn, rs.Primary.Attributes["vpc_endpoint_id"]) - - if tfresource.NotFound(err) { - continue - } - - if err != nil { - return err - } - - // VPC Endpoint will always have 1 SG. - if len(out.Groups) > 1 { - return fmt.Errorf("VPC endpoint %s has security groups", aws.StringValue(out.VpcEndpointId)) - } - } - - return nil -} - -func testAccCheckVpcEndpointSecurityGroupAssociationExists(n string, vpce *ec2.VpcEndpoint) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - out, err := finder.VpcEndpointByID(conn, rs.Primary.Attributes["vpc_endpoint_id"]) - - if err != nil { - return err - } - - err = finder.VpcEndpointSecurityGroupAssociationExists(conn, rs.Primary.Attributes["vpc_endpoint_id"], rs.Primary.Attributes["security_group_id"]) - - if err != nil { - return err - } - - *vpce = *out - - return nil - } -} - -func testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(vpce *ec2.VpcEndpoint, n int) resource.TestCheckFunc { - return func(s *terraform.State) error { - if len := len(vpce.Groups); len != n { - return fmt.Errorf("got %d associations; wanted %d", len, n) - } - - return nil - } -} - -func testAccVpcEndpointSecurityGroupAssociationConfigBase(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - -data "aws_region" "current" {} - -resource "aws_security_group" "test" { - count = 3 - - vpc_id = aws_vpc.test.id - - tags = { - Name = %[1]q - } -} - -resource "aws_vpc_endpoint" "test" { - vpc_id = aws_vpc.test.id - service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" - vpc_endpoint_type = "Interface" - - tags = { - Name = %[1]q - } -} -`, rName) -} - -func testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName string) string { - return composeConfig( - testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), - ` -resource "aws_vpc_endpoint_security_group_association" "test" { - vpc_endpoint_id = aws_vpc_endpoint.test.id - security_group_id = aws_security_group.test[0].id -} -`) -} - -func testAccVpcEndpointSecurityGroupAssociationConfigMultiple(rName string) string { - return composeConfig( - testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), - ` -resource "aws_vpc_endpoint_security_group_association" "test" { - count = length(aws_security_group.test) - - vpc_endpoint_id = aws_vpc_endpoint.test.id - security_group_id = aws_security_group.test[count.index].id -} -`) -} - -func testAccVpcEndpointSecurityGroupAssociationConfigReplaceDefaultAssociation(rName string) string { - return composeConfig( - testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), - ` -resource "aws_vpc_endpoint_security_group_association" "test" { - vpc_endpoint_id = aws_vpc_endpoint.test.id - security_group_id = aws_security_group.test[0].id - - replace_default_association = true -} -`) -} diff --git a/aws/resource_aws_vpc_endpoint_test.go b/aws/resource_aws_vpc_endpoint_test.go index 55883b2fc4e..40c22ec21d8 100644 --- a/aws/resource_aws_vpc_endpoint_test.go +++ b/aws/resource_aws_vpc_endpoint_test.go @@ -782,12 +782,21 @@ resource "aws_vpc" "test" { } } +data "aws_security_group" "test" { + vpc_id = aws_vpc.test.id + name = "default" +} + data "aws_region" "current" {} resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" vpc_endpoint_type = "Interface" + + security_group_ids = [ + data.aws_security_group.test.id, + ] } `, rName) } diff --git a/website/docs/r/vpc_endpoint.html.markdown b/website/docs/r/vpc_endpoint.html.markdown index fab79c89e34..368dd73fa2d 100644 --- a/website/docs/r/vpc_endpoint.html.markdown +++ b/website/docs/r/vpc_endpoint.html.markdown @@ -11,10 +11,9 @@ description: |- Provides a VPC Endpoint resource. ~> **NOTE on VPC Endpoints and VPC Endpoint Associations:** Terraform provides both standalone VPC Endpoint Associations for -[Route Tables](vpc_endpoint_route_table_association.html) - (an association between a VPC endpoint and a single `route_table_id`), -[Security Groups](vpc_endpoint_security_group_association.html) - (an association between a VPC endpoint and a single `security_group_id`), -and [Subnets](vpc_endpoint_subnet_association.html) - (an association between a VPC endpoint and a single `subnet_id`) and -a VPC Endpoint resource with `route_table_ids`, `security_group_ids` and `subnet_ids` attributes. +[Route Tables](vpc_endpoint_route_table_association.html) - (an association between a VPC endpoint and a single `route_table_id`) and +[Subnets](vpc_endpoint_subnet_association.html) - (an association between a VPC endpoint and a single `subnet_id`) and +a VPC Endpoint resource with `route_table_ids` and `subnet_ids` attributes. Do not use the same resource ID in both a VPC Endpoint resource and a VPC Endpoint Association resource. Doing so will cause a conflict of associations and will overwrite the association. @@ -122,8 +121,7 @@ The following arguments are supported: Defaults to `false`. * `route_table_ids` - (Optional) One or more route table IDs. Applicable for endpoints of type `Gateway`. * `subnet_ids` - (Optional) The ID of one or more subnets in which to create a network interface for the endpoint. Applicable for endpoints of type `GatewayLoadBalancer` and `Interface`. -* `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Applicable for endpoints of type `Interface`. -If no security groups are specified, the VPC's [default security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html#DefaultSecurityGroup) is associated with the endpoint. +* `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Required for endpoints of type `Interface`. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `vpc_endpoint_type` - (Optional) The VPC endpoint type, `Gateway`, `GatewayLoadBalancer`, or `Interface`. Defaults to `Gateway`. diff --git a/website/docs/r/vpc_endpoint_security_group_association.html.markdown b/website/docs/r/vpc_endpoint_security_group_association.html.markdown deleted file mode 100644 index 44cb62e3272..00000000000 --- a/website/docs/r/vpc_endpoint_security_group_association.html.markdown +++ /dev/null @@ -1,43 +0,0 @@ ---- -subcategory: "VPC" -layout: "aws" -page_title: "AWS: aws_vpc_endpoint_security_group_association" -description: |- - Provides a resource to create an association between a VPC endpoint and a security group. ---- - -# Resource: aws_vpc_endpoint_security_group_association - -Provides a resource to create an association between a VPC endpoint and a security group. - -~> **NOTE on VPC Endpoints and VPC Endpoint Security Group Associations:** Terraform provides -both a standalone VPC Endpoint Security Group Association (an association between a VPC endpoint -and a single `security_group_id`) and a [VPC Endpoint](vpc_endpoint.html) resource with a `security_group_ids` -attribute. Do not use the same security group ID in both a VPC Endpoint resource and a VPC Endpoint Security -Group Association resource. Doing so will cause a conflict of associations and will overwrite the association. - -## Example Usage - -Basic usage: - -```terraform -resource "aws_vpc_endpoint_security_group_association" "sg_ec2" { - vpc_endpoint_id = aws_vpc_endpoint.ec2.id - security_group_id = aws_security_group.sg.id -} -``` - -## Argument Reference - -The following arguments are supported: - -* `security_group_id` - (Required) The ID of the security group to be associated with the VPC endpoint. -* `vpc_endpoint_id` - (Required) The ID of the VPC endpoint with which the security group will be associated. -* `replace_default_association` - (Optional) Whether this association should replace the association with the VPC's default security group that is created when no security groups are specified during VPC endpoint creation. -At most 1 association per-VPC endpoint should be configured with `replace_default_association = true`. - -## Attributes Reference - -In addition to all arguments above, the following attributes are exported: - -* `id` - The ID of the association. From 14dbfb0073460c4e4df9cfdeb9261d1f48bc2f6b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 09:24:00 -0500 Subject: [PATCH 3/9] Add CHANGELOG entry. --- .changelog/13737.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/13737.txt diff --git a/.changelog/13737.txt b/.changelog/13737.txt new file mode 100644 index 00000000000..91d67531c6d --- /dev/null +++ b/.changelog/13737.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_vpc_endpoint_security_group_association +``` + +```release-note:enhancement +resource/aws_vpc_endpoint: The `security_group_ids` attribute can now be empty when the resource is created. In this case the VPC's default security is associated with the VPC endpoint +``` \ No newline at end of file From 808f272d9543fa84ab9f5ff87e0517d74ba04057 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 09:31:19 -0500 Subject: [PATCH 4/9] Use paginator when finding VPC endpoints. --- internal/service/ec2/find.go | 72 ++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index dc0a74b79d4..5bf60f5950f 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1813,8 +1813,55 @@ func FindVPCMainRouteTable(conn *ec2.EC2, id string) (*ec2.RouteTable, error) { return FindRouteTable(conn, input) } -// FindVPCEndpointByID returns the VPC endpoint corresponding to the specified identifier. -// Returns NotFoundError if no VPC endpoint is found. +func FindVPCEndpoint(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) (*ec2.VpcEndpoint, error) { + output, err := FindVPCEndpoints(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVPCEndpoints(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) ([]*ec2.VpcEndpoint, error) { + var output []*ec2.VpcEndpoint + + err := conn.DescribeVpcEndpointsPages(input, func(page *ec2.DescribeVpcEndpointsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.VpcEndpoints { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpcEndpointIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + func FindVPCEndpointByID(conn *ec2.EC2, vpcEndpointID string) (*ec2.VpcEndpoint, error) { input := &ec2.DescribeVpcEndpointsInput{ VpcEndpointIds: aws.StringSlice([]string{vpcEndpointID}), @@ -1843,27 +1890,6 @@ func FindVPCEndpointByID(conn *ec2.EC2, vpcEndpointID string) (*ec2.VpcEndpoint, return output, nil } -func FindVPCEndpoint(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) (*ec2.VpcEndpoint, error) { - output, err := conn.DescribeVpcEndpoints(input) - - if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpcEndpointIdNotFound) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || len(output.VpcEndpoints) == 0 || output.VpcEndpoints[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.VpcEndpoints[0], nil -} - // FindVPCEndpointRouteTableAssociationExists returns NotFoundError if no association for the specified VPC endpoint and route table IDs is found. func FindVPCEndpointRouteTableAssociationExists(conn *ec2.EC2, vpcEndpointID string, routeTableID string) error { vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) From 4327532bcdf2101d779e0e1b601da60c59659ca9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 09:33:44 -0500 Subject: [PATCH 5/9] Add 'FindVPCEndpointSecurityGroupAssociationExists'. --- internal/service/ec2/find.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 5bf60f5950f..7d079ba1a9d 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1905,7 +1905,26 @@ func FindVPCEndpointRouteTableAssociationExists(conn *ec2.EC2, vpcEndpointID str } return &resource.NotFoundError{ - LastError: fmt.Errorf("VPC Endpoint Route Table Association (%s/%s) not found", vpcEndpointID, routeTableID), + LastError: fmt.Errorf("VPC Endpoint (%s) Route Table (%s) Association not found", vpcEndpointID, routeTableID), + } +} + +// FindVPCEndpointSecurityGroupAssociationExists returns NotFoundError if no association for the specified VPC endpoint and security group IDs is found. +func FindVPCEndpointSecurityGroupAssociationExists(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) + + if err != nil { + return err + } + + for _, group := range vpcEndpoint.Groups { + if aws.StringValue(group.GroupId) == securityGroupID { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint (%s) Security Group (%s) Association not found", vpcEndpointID, securityGroupID), } } From b493c9ed279580de0642ad207a7f88f9f6bbc34a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 09:44:18 -0500 Subject: [PATCH 6/9] r/aws_vpc_endpoint: Remove requirement for 'security_group_ids' to be set for Interface endpoints. --- internal/service/ec2/vpc_endpoint.go | 6 ------ internal/service/ec2/vpc_endpoint_test.go | 9 --------- website/docs/r/vpc_endpoint.html.markdown | 8 +++++--- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/internal/service/ec2/vpc_endpoint.go b/internal/service/ec2/vpc_endpoint.go index 1d3a4ddfb61..e63ff49e444 100644 --- a/internal/service/ec2/vpc_endpoint.go +++ b/internal/service/ec2/vpc_endpoint.go @@ -1,7 +1,6 @@ package ec2 import ( - "errors" "fmt" "log" "time" @@ -157,11 +156,6 @@ func ResourceVPCEndpoint() *schema.Resource { } func resourceVPCEndpointCreate(d *schema.ResourceData, meta interface{}) error { - if d.Get("vpc_endpoint_type").(string) == ec2.VpcEndpointTypeInterface && - d.Get("security_group_ids").(*schema.Set).Len() == 0 { - return errors.New("An Interface VPC Endpoint must always have at least one Security Group") - } - conn := meta.(*conns.AWSClient).EC2Conn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) diff --git a/internal/service/ec2/vpc_endpoint_test.go b/internal/service/ec2/vpc_endpoint_test.go index 71ebc60b5dc..212c474d943 100644 --- a/internal/service/ec2/vpc_endpoint_test.go +++ b/internal/service/ec2/vpc_endpoint_test.go @@ -736,21 +736,12 @@ resource "aws_vpc" "test" { } } -data "aws_security_group" "test" { - vpc_id = aws_vpc.test.id - name = "default" -} - data "aws_region" "current" {} resource "aws_vpc_endpoint" "test" { vpc_id = aws_vpc.test.id service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" vpc_endpoint_type = "Interface" - - security_group_ids = [ - data.aws_security_group.test.id, - ] } `, rName) } diff --git a/website/docs/r/vpc_endpoint.html.markdown b/website/docs/r/vpc_endpoint.html.markdown index c2381199775..b5525d06c8f 100644 --- a/website/docs/r/vpc_endpoint.html.markdown +++ b/website/docs/r/vpc_endpoint.html.markdown @@ -11,8 +11,9 @@ description: |- Provides a VPC Endpoint resource. ~> **NOTE on VPC Endpoints and VPC Endpoint Associations:** Terraform provides both standalone VPC Endpoint Associations for -[Route Tables](vpc_endpoint_route_table_association.html) - (an association between a VPC endpoint and a single `route_table_id`) and -[Subnets](vpc_endpoint_subnet_association.html) - (an association between a VPC endpoint and a single `subnet_id`) and +[Route Tables](vpc_endpoint_route_table_association.html) - (an association between a VPC endpoint and a single `route_table_id`), +[Security Groups](vpc_endpoint_security_group_association.html) - (an association between a VPC endpoint and a single `security_group_id`), +and [Subnets](vpc_endpoint_subnet_association.html) - (an association between a VPC endpoint and a single `subnet_id`) and a VPC Endpoint resource with `route_table_ids` and `subnet_ids` attributes. Do not use the same resource ID in both a VPC Endpoint resource and a VPC Endpoint Association resource. Doing so will cause a conflict of associations and will overwrite the association. @@ -121,7 +122,8 @@ The following arguments are supported: Defaults to `false`. * `route_table_ids` - (Optional) One or more route table IDs. Applicable for endpoints of type `Gateway`. * `subnet_ids` - (Optional) The ID of one or more subnets in which to create a network interface for the endpoint. Applicable for endpoints of type `GatewayLoadBalancer` and `Interface`. -* `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Required for endpoints of type `Interface`. +* `security_group_ids` - (Optional) The ID of one or more security groups to associate with the network interface. Applicable for endpoints of type `Interface`. +If no security groups are specified, the VPC's [default security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html#DefaultSecurityGroup) is associated with the endpoint. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `vpc_endpoint_type` - (Optional) The VPC endpoint type, `Gateway`, `GatewayLoadBalancer`, or `Interface`. Defaults to `Gateway`. From 302ac913e7ef83157385ccb54cbe941e04fa5f89 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 11:09:54 -0500 Subject: [PATCH 7/9] r/aws_vpc_endpoint_security_group_association: New resource. --- internal/provider/provider.go | 1 + internal/service/ec2/id.go | 4 + ...vpc_endpoint_security_group_association.go | 195 ++++++++++++++++++ ...t_security_group_association.html.markdown | 43 ++++ 4 files changed, 243 insertions(+) create mode 100644 internal/service/ec2/vpc_endpoint_security_group_association.go create mode 100644 website/docs/r/vpc_endpoint_security_group_association.html.markdown diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d68601f1e17..d453f985712 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1203,6 +1203,7 @@ func Provider() *schema.Provider { "aws_vpc_endpoint_connection_accepter": ec2.ResourceVPCEndpointConnectionAccepter(), "aws_vpc_endpoint_connection_notification": ec2.ResourceVPCEndpointConnectionNotification(), "aws_vpc_endpoint_route_table_association": ec2.ResourceVPCEndpointRouteTableAssociation(), + "aws_vpc_endpoint_security_group_association": ec2.ResourceVPCEndpointSecurityGroupAssociation(), "aws_vpc_endpoint_service": ec2.ResourceVPCEndpointService(), "aws_vpc_endpoint_service_allowed_principal": ec2.ResourceVPCEndpointServiceAllowedPrincipal(), "aws_vpc_endpoint_subnet_association": ec2.ResourceVPCEndpointSubnetAssociation(), diff --git a/internal/service/ec2/id.go b/internal/service/ec2/id.go index 7a5559610e9..e4f81a0044a 100644 --- a/internal/service/ec2/id.go +++ b/internal/service/ec2/id.go @@ -53,6 +53,10 @@ func VPCEndpointRouteTableAssociationCreateID(vpcEndpointID, routeTableID string return fmt.Sprintf("a-%s%d", vpcEndpointID, create.StringHashcode(routeTableID)) } +func VPCEndpointSecurityGroupAssociationCreateID(vpcEndpointID, securityGroupID string) string { + return fmt.Sprintf("a-%s%d", vpcEndpointID, create.StringHashcode(securityGroupID)) +} + func VPCEndpointSubnetAssociationCreateID(vpcEndpointID, subnetID string) string { return fmt.Sprintf("a-%s%d", vpcEndpointID, create.StringHashcode(subnetID)) } diff --git a/internal/service/ec2/vpc_endpoint_security_group_association.go b/internal/service/ec2/vpc_endpoint_security_group_association.go new file mode 100644 index 00000000000..986fc3ae99e --- /dev/null +++ b/internal/service/ec2/vpc_endpoint_security_group_association.go @@ -0,0 +1,195 @@ +package ec2 + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func ResourceVPCEndpointSecurityGroupAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceVPCEndpointSecurityGroupAssociationCreate, + Read: resourceVPCEndpointSecurityGroupAssociationRead, + Delete: resourceVPCEndpointSecurityGroupAssociationDelete, + + Schema: map[string]*schema.Schema{ + "replace_default_association": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + "security_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "vpc_endpoint_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceVPCEndpointSecurityGroupAssociationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + + vpcEndpointID := d.Get("vpc_endpoint_id").(string) + securityGroupID := d.Get("security_group_id").(string) + replaceDefaultAssociation := d.Get("replace_default_association").(bool) + + defaultSecurityGroupID := "" + if replaceDefaultAssociation { + vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) + + if err != nil { + return fmt.Errorf("error reading VPC Endpoint (%s): %w", vpcEndpointID, err) + } + + vpcID := aws.StringValue(vpcEndpoint.VpcId) + + defaultSecurityGroup, err := FindVPCDefaultSecurityGroup(conn, vpcID) + + if err != nil { + return fmt.Errorf("error reading EC2 VPC (%s) default Security Group: %w", vpcID, err) + } + + defaultSecurityGroupID = aws.StringValue(defaultSecurityGroup.GroupId) + + if defaultSecurityGroupID == securityGroupID { + return fmt.Errorf("%s is the default Security Group for EC2 VPC (%s)", securityGroupID, vpcID) + } + + foundDefaultAssociation := false + + for _, group := range vpcEndpoint.Groups { + if aws.StringValue(group.GroupId) == defaultSecurityGroupID { + foundDefaultAssociation = true + break + } + } + + if !foundDefaultAssociation { + return fmt.Errorf("no association of default Security Group (%s) with VPC Endpoint (%s)", defaultSecurityGroupID, vpcEndpointID) + } + } + + err := createVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, securityGroupID) + + if err != nil { + return err + } + + d.SetId(VPCEndpointSecurityGroupAssociationCreateID(vpcEndpointID, securityGroupID)) + + if replaceDefaultAssociation { + // Delete the existing VPC endpoint/default security group association. + if err := deleteVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, defaultSecurityGroupID); err != nil { + return err + } + } + + return resourceVPCEndpointSecurityGroupAssociationRead(d, meta) +} + +func resourceVPCEndpointSecurityGroupAssociationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + + vpcEndpointID := d.Get("vpc_endpoint_id").(string) + securityGroupID := d.Get("security_group_id").(string) + // Human friendly ID for error messages since d.Id() is non-descriptive + id := fmt.Sprintf("%s/%s", vpcEndpointID, securityGroupID) + + err := FindVPCEndpointSecurityGroupAssociationExists(conn, vpcEndpointID, securityGroupID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] VPC Endpoint Security Group Association (%s) not found, removing from state", id) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading VPC Security Group Association (%s): %w", id, err) + } + + return nil +} + +func resourceVPCEndpointSecurityGroupAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + + vpcEndpointID := d.Get("vpc_endpoint_id").(string) + securityGroupID := d.Get("security_group_id").(string) + replaceDefaultAssociation := d.Get("replace_default_association").(bool) + + if replaceDefaultAssociation { + vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) + + if err != nil { + return fmt.Errorf("error reading VPC Endpoint (%s): %w", vpcEndpointID, err) + } + + vpcID := aws.StringValue(vpcEndpoint.VpcId) + + defaultSecurityGroup, err := FindVPCDefaultSecurityGroup(conn, vpcID) + + if err != nil { + return fmt.Errorf("error reading EC2 VPC (%s) default Security Group: %w", vpcID, err) + } + + // Add back the VPC endpoint/default security group association. + err = createVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, aws.StringValue(defaultSecurityGroup.GroupId)) + + if err != nil { + return err + } + } + + return deleteVpcEndpointSecurityGroupAssociation(conn, vpcEndpointID, securityGroupID) +} + +// createVpcEndpointSecurityGroupAssociation creates the specified VPC endpoint/security group association. +func createVpcEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + input := &ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(vpcEndpointID), + AddSecurityGroupIds: aws.StringSlice([]string{securityGroupID}), + } + + log.Printf("[DEBUG] Creating VPC Endpoint Security Group Association: %s", input) + _, err := conn.ModifyVpcEndpoint(input) + + if err != nil { + return fmt.Errorf("error creating VPC Endpoint (%s) Security Group (%s) Association: %w", vpcEndpointID, securityGroupID, err) + } + + return nil +} + +// deleteVpcEndpointSecurityGroupAssociation deletes the specified VPC endpoint/security group association. +func deleteVpcEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + input := &ec2.ModifyVpcEndpointInput{ + VpcEndpointId: aws.String(vpcEndpointID), + RemoveSecurityGroupIds: aws.StringSlice([]string{securityGroupID}), + } + + log.Printf("[DEBUG] Deleting VPC Endpoint Security Group Association: %s", input) + _, err := conn.ModifyVpcEndpoint(input) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidVpcEndpointIdNotFound, ErrCodeInvalidGroupNotFound, ErrCodeInvalidParameter) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting VPC Endpoint (%s) Security Group (%s) Association: %w", vpcEndpointID, securityGroupID, err) + } + + return nil +} diff --git a/website/docs/r/vpc_endpoint_security_group_association.html.markdown b/website/docs/r/vpc_endpoint_security_group_association.html.markdown new file mode 100644 index 00000000000..06825f434b1 --- /dev/null +++ b/website/docs/r/vpc_endpoint_security_group_association.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "VPC" +layout: "aws" +page_title: "AWS: aws_vpc_endpoint_security_group_association" +description: |- + Provides a resource to create an association between a VPC endpoint and a security group. +--- + +# Resource: aws_vpc_endpoint_security_group_association + +Provides a resource to create an association between a VPC endpoint and a security group. + +~> **NOTE on VPC Endpoints and VPC Endpoint Security Group Associations:** Terraform provides +both a standalone VPC Endpoint Security Group Association (an association between a VPC endpoint +and a single `security_group_id`) and a [VPC Endpoint](vpc_endpoint.html) resource with a `security_group_ids` +attribute. Do not use the same security group ID in both a VPC Endpoint resource and a VPC Endpoint Security +Group Association resource. Doing so will cause a conflict of associations and will overwrite the association. + +## Example Usage + +Basic usage: + +```terraform +resource "aws_vpc_endpoint_security_group_association" "sg_ec2" { + vpc_endpoint_id = aws_vpc_endpoint.ec2.id + security_group_id = aws_security_group.sg.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `security_group_id` - (Required) The ID of the security group to be associated with the VPC endpoint. +* `vpc_endpoint_id` - (Required) The ID of the VPC endpoint with which the security group will be associated. +* `replace_default_association` - (Optional) Whether this association should replace the association with the VPC's default security group that is created when no security groups are specified during VPC endpoint creation. +At most 1 association per-VPC endpoint should be configured with `replace_default_association = true`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the association. \ No newline at end of file From 29d477d905b148d59148c71f7e6773d7f3ef2810 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 9 Feb 2022 11:18:50 -0500 Subject: [PATCH 8/9] r/aws_vpc_endpoint_security_group_association: Add acceptance tests. --- ...ndpoint_security_group_association_test.go | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 internal/service/ec2/vpc_endpoint_security_group_association_test.go diff --git a/internal/service/ec2/vpc_endpoint_security_group_association_test.go b/internal/service/ec2/vpc_endpoint_security_group_association_test.go new file mode 100644 index 00000000000..1fac4614a49 --- /dev/null +++ b/internal/service/ec2/vpc_endpoint_security_group_association_test.go @@ -0,0 +1,244 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/ec2" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccEC2VPCEndpointSecurityGroupAssociation_basic(t *testing.T) { + var v ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint_security_group_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &v), + testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&v, 2), + ), + }, + }, + }) +} + +func TestAccEC2VPCEndpointSecurityGroupAssociation_disappears(t *testing.T) { + var v ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint_security_group_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPCEndpointSecurityGroupAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccEC2VPCEndpointSecurityGroupAssociation_multiple(t *testing.T) { + var v ec2.VpcEndpoint + resourceName0 := "aws_vpc_endpoint_security_group_association.test.0" + resourceName1 := "aws_vpc_endpoint_security_group_association.test.1" + resourceName2 := "aws_vpc_endpoint_security_group_association.test.2" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigMultiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName0, &v), + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName1, &v), + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName2, &v), + testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&v, 4), + ), + }, + }, + }) +} + +func TestAccEC2VPCEndpointSecurityGroupAssociation_replaceDefaultAssociation(t *testing.T) { + var v ec2.VpcEndpoint + resourceName := "aws_vpc_endpoint_security_group_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckVpcEndpointSecurityGroupAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVpcEndpointSecurityGroupAssociationConfigReplaceDefaultAssociation(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcEndpointSecurityGroupAssociationExists(resourceName, &v), + testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(&v, 1), + ), + }, + }, + }) +} + +func testAccCheckVpcEndpointSecurityGroupAssociationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpc_endpoint_security_group_association" { + continue + } + + err := tfec2.FindVPCEndpointSecurityGroupAssociationExists(conn, rs.Primary.Attributes["vpc_endpoint_id"], rs.Primary.Attributes["security_group_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("VPC Endpoint Security Group Association %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckVpcEndpointSecurityGroupAssociationExists(n string, v *ec2.VpcEndpoint) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No VPC Endpoint Security Group Association ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + output, err := tfec2.FindVPCEndpointByID(conn, rs.Primary.Attributes["vpc_endpoint_id"]) + + if err != nil { + return err + } + + err = tfec2.FindVPCEndpointSecurityGroupAssociationExists(conn, rs.Primary.Attributes["vpc_endpoint_id"], rs.Primary.Attributes["security_group_id"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckVpcEndpointSecurityGroupAssociationNumAssociations(v *ec2.VpcEndpoint, n int) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len := len(v.Groups); len != n { + return fmt.Errorf("got %d associations; wanted %d", len, n) + } + + return nil + } +} + +func testAccVpcEndpointSecurityGroupAssociationConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +data "aws_region" "current" {} + +resource "aws_security_group" "test" { + count = 3 + + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_vpc_endpoint" "test" { + vpc_id = aws_vpc.test.id + service_name = "com.amazonaws.${data.aws_region.current.name}.ec2" + vpc_endpoint_type = "Interface" + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccVpcEndpointSecurityGroupAssociationConfigBasic(rName string) string { + return acctest.ConfigCompose( + testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), + ` +resource "aws_vpc_endpoint_security_group_association" "test" { + vpc_endpoint_id = aws_vpc_endpoint.test.id + security_group_id = aws_security_group.test[0].id +} +`) +} + +func testAccVpcEndpointSecurityGroupAssociationConfigMultiple(rName string) string { + return acctest.ConfigCompose( + testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), + ` +resource "aws_vpc_endpoint_security_group_association" "test" { + count = length(aws_security_group.test) + + vpc_endpoint_id = aws_vpc_endpoint.test.id + security_group_id = aws_security_group.test[count.index].id +} +`) +} + +func testAccVpcEndpointSecurityGroupAssociationConfigReplaceDefaultAssociation(rName string) string { + return acctest.ConfigCompose( + testAccVpcEndpointSecurityGroupAssociationConfigBase(rName), + ` +resource "aws_vpc_endpoint_security_group_association" "test" { + vpc_endpoint_id = aws_vpc_endpoint.test.id + security_group_id = aws_security_group.test[0].id + + replace_default_association = true +} +`) +} From 8bf44a881e75f9dd69aed59e42062e3027a7f29c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 15 Mar 2022 11:52:32 -0400 Subject: [PATCH 9/9] Update website/docs/r/vpc_endpoint_security_group_association.html.markdown Co-authored-by: Dirk Avery <31492422+YakDriver@users.noreply.github.com> --- .../r/vpc_endpoint_security_group_association.html.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/docs/r/vpc_endpoint_security_group_association.html.markdown b/website/docs/r/vpc_endpoint_security_group_association.html.markdown index 06825f434b1..530779d1305 100644 --- a/website/docs/r/vpc_endpoint_security_group_association.html.markdown +++ b/website/docs/r/vpc_endpoint_security_group_association.html.markdown @@ -33,8 +33,7 @@ The following arguments are supported: * `security_group_id` - (Required) The ID of the security group to be associated with the VPC endpoint. * `vpc_endpoint_id` - (Required) The ID of the VPC endpoint with which the security group will be associated. -* `replace_default_association` - (Optional) Whether this association should replace the association with the VPC's default security group that is created when no security groups are specified during VPC endpoint creation. -At most 1 association per-VPC endpoint should be configured with `replace_default_association = true`. +* `replace_default_association` - (Optional) Whether this association should replace the association with the VPC's default security group that is created when no security groups are specified during VPC endpoint creation. At most 1 association per-VPC endpoint should be configured with `replace_default_association = true`. ## Attributes Reference