Skip to content

Commit

Permalink
Merge pull request #39507 from tcarreira/f-aws_globalaccelerator_endp…
Browse files Browse the repository at this point in the history
…oint_group-add-attachment_arn

r/aws_globalaccelerator_endpoint_group: add `attachment_arn` to `endpoint_configuration`
  • Loading branch information
ewbankkit authored Oct 1, 2024
2 parents fc016af + 98aa21e commit c20eb9b
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .changelog/39507.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_globalaccelerator_endpoint_group: Add `endpoint_configuration.attachment_arn` argument
```
79 changes: 74 additions & 5 deletions internal/service/globalaccelerator/endpoint_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/globalaccelerator"
awstypes "github.com/aws/aws-sdk-go-v2/service/globalaccelerator/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -53,6 +54,11 @@ func resourceEndpointGroup() *schema.Resource {
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"attachment_arn": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: verify.ValidARN,
},
"client_ip_preservation_enabled": {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -230,8 +236,13 @@ func resourceEndpointGroupRead(ctx context.Context, d *schema.ResourceData, meta
return sdkdiag.AppendFromErr(diags, err)
}

crossAccountAttachments, err := findCrossAccountAttachments(ctx, conn, endpointGroup.EndpointDescriptions)
if err != nil {
return sdkdiag.AppendErrorf(diags, "reading Global Accelerator Endpoint Group (%s) cross-account attachments: %s", d.Id(), err)
}

d.Set(names.AttrARN, endpointGroup.EndpointGroupArn)
if err := d.Set("endpoint_configuration", flattenEndpointDescriptions(endpointGroup.EndpointDescriptions)); err != nil {
if err := d.Set("endpoint_configuration", flattenEndpointDescriptions(endpointGroup.EndpointDescriptions, crossAccountAttachments)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting endpoint_configuration: %s", err)
}
d.Set("endpoint_group_region", endpointGroup.EndpointGroupRegion)
Expand Down Expand Up @@ -372,6 +383,10 @@ func expandEndpointConfiguration(tfMap map[string]interface{}) *awstypes.Endpoin

apiObject := &awstypes.EndpointConfiguration{}

if v, ok := tfMap["attachment_arn"].(string); ok && v != "" {
apiObject.AttachmentArn = aws.String(v)
}

if v, ok := tfMap["client_ip_preservation_enabled"].(bool); ok {
apiObject.ClientIPPreservationEnabled = aws.Bool(v)
}
Expand Down Expand Up @@ -457,7 +472,7 @@ func expandPortOverrides(tfList []interface{}) []awstypes.PortOverride {
return apiObjects
}

func flattenEndpointDescription(apiObject *awstypes.EndpointDescription) map[string]interface{} {
func flattenEndpointDescription(apiObject *awstypes.EndpointDescription, crossAccountAttachments map[string]string) map[string]interface{} {
if apiObject == nil {
return nil
}
Expand All @@ -469,7 +484,12 @@ func flattenEndpointDescription(apiObject *awstypes.EndpointDescription) map[str
}

if v := apiObject.EndpointId; v != nil {
tfMap["endpoint_id"] = aws.ToString(v)
v := aws.ToString(v)
tfMap["endpoint_id"] = v

if v, ok := crossAccountAttachments[v]; ok {
tfMap["attachment_arn"] = v
}
}

if v := apiObject.Weight; v != nil {
Expand All @@ -479,15 +499,15 @@ func flattenEndpointDescription(apiObject *awstypes.EndpointDescription) map[str
return tfMap
}

func flattenEndpointDescriptions(apiObjects []awstypes.EndpointDescription) []interface{} {
func flattenEndpointDescriptions(apiObjects []awstypes.EndpointDescription, crossAccountAttachments map[string]string) []interface{} {
if len(apiObjects) == 0 {
return nil
}

var tfList []interface{}

for _, apiObject := range apiObjects {
tfList = append(tfList, flattenEndpointDescription(&apiObject))
tfList = append(tfList, flattenEndpointDescription(&apiObject, crossAccountAttachments))
}

return tfList
Expand Down Expand Up @@ -524,3 +544,52 @@ func flattenPortOverrides(apiObjects []awstypes.PortOverride) []interface{} {

return tfList
}

func findCrossAccountAttachments(ctx context.Context, conn *globalaccelerator.Client, endpointDescriptions []awstypes.EndpointDescription) (map[string]string, error) {
crossAccountAttachments := map[string]string{}

accounts := map[string]bool{}
for _, endpointDescription := range endpointDescriptions {
arn, err := arn.Parse(aws.ToString(endpointDescription.EndpointId))
if err != nil {
continue // Not an ARN => not a cross-account resource.
}

accountID := arn.AccountID
if accounts[accountID] {
continue
}
accounts[accountID] = true

input := &globalaccelerator.ListCrossAccountResourcesInput{
ResourceOwnerAwsAccountId: aws.String(accountID),
}
crossAccountResources, err := findCrossAccountResources(ctx, conn, input)
if err != nil {
return nil, err
}

for _, crossAccountResource := range crossAccountResources {
crossAccountAttachments[aws.ToString(crossAccountResource.EndpointId)] = aws.ToString(crossAccountResource.AttachmentArn)
}
}

return crossAccountAttachments, nil
}

func findCrossAccountResources(ctx context.Context, conn *globalaccelerator.Client, input *globalaccelerator.ListCrossAccountResourcesInput) ([]awstypes.CrossAccountResource, error) {
var output []awstypes.CrossAccountResource

pages := globalaccelerator.NewListCrossAccountResourcesPaginator(conn, input)
for pages.HasMorePages() {
page, err := pages.NextPage(ctx)

if err != nil {
return nil, err
}

output = append(output, page.CrossAccountResources...)
}

return output, nil
}
183 changes: 183 additions & 0 deletions internal/service/globalaccelerator/endpoint_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func TestAccGlobalAcceleratorEndpointGroup_ALBEndpoint_clientIP(t *testing.T) {
acctest.MatchResourceAttrGlobalARN(resourceName, names.AttrARN, "globalaccelerator", regexache.MustCompile(`accelerator/[^/]+/listener/[^/]+/endpoint-group/[^/]+`)),
resource.TestCheckResourceAttr(resourceName, "endpoint_configuration.#", acctest.Ct1),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "endpoint_configuration.*", map[string]string{
"attachment_arn": "",
"client_ip_preservation_enabled": acctest.CtFalse,
names.AttrWeight: "20",
}),
Expand Down Expand Up @@ -429,6 +430,47 @@ func TestAccGlobalAcceleratorEndpointGroup_update(t *testing.T) {
})
}

func TestAccGlobalAcceleratorEndpointGroup_crossAccountAttachment(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.EndpointGroup
resourceName := "aws_globalaccelerator_endpoint_group.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
testAccPreCheck(ctx, t)
acctest.PreCheckMultipleRegion(t, 2)
acctest.PreCheckAlternateAccount(t)
},
ErrorCheck: acctest.ErrorCheck(t, names.GlobalAcceleratorServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t),
CheckDestroy: testAccCheckEndpointGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccEndpointGroupConfig_crossAccountAttachement(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckEndpointGroupExists(ctx, resourceName, &v),
acctest.MatchResourceAttrGlobalARN(resourceName, names.AttrARN, "globalaccelerator", regexache.MustCompile(`accelerator/[^/]+/listener/[^/]+/endpoint-group/[^/]+`)),
resource.TestCheckResourceAttr(resourceName, "endpoint_configuration.#", acctest.Ct1),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "endpoint_configuration.*", map[string]string{
"client_ip_preservation_enabled": acctest.CtFalse,
names.AttrWeight: "20",
}),
resource.TestCheckTypeSetElemAttrPair(resourceName, "endpoint_configuration.*.attachment_arn", "aws_globalaccelerator_cross_account_attachment.alt_test", names.AttrARN),
resource.TestCheckTypeSetElemAttrPair(resourceName, "endpoint_configuration.*.endpoint_id", "aws_lb.alt_test", names.AttrARN),
resource.TestCheckResourceAttr(resourceName, "endpoint_group_region", acctest.AlternateRegion()),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckEndpointGroupExists(ctx context.Context, name string, v *awstypes.EndpointGroup) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
Expand Down Expand Up @@ -874,3 +916,144 @@ resource "aws_globalaccelerator_endpoint_group" "test" {
}
`, rName)
}

func testAccEndpointGroupConfig_crossAccountAttachement(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigAlternateAccountAlternateRegionProvider(),
fmt.Sprintf(`
###############################################################################
## Alternate account setup.
###############################################################################
data "aws_availability_zones" "alt_available" {
provider = "awsalternate"
exclude_zone_ids = ["usw2-az4", "usgw1-az2"]
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
resource "aws_vpc" "alt_test" {
provider = "awsalternate"
cidr_block = "10.0.0.0/16"
tags = {
Name = %[1]q
}
}
resource "aws_subnet" "alt_test" {
provider = "awsalternate"
count = 2
vpc_id = aws_vpc.alt_test.id
availability_zone = data.aws_availability_zones.alt_available.names[count.index]
cidr_block = cidrsubnet(aws_vpc.alt_test.cidr_block, 8, count.index)
tags = {
Name = %[1]q
}
}
resource "aws_lb" "alt_test" {
provider = "awsalternate"
name = %[1]q
internal = false
security_groups = [aws_security_group.alt_test.id]
subnets = aws_subnet.alt_test[*].id
idle_timeout = 30
enable_deletion_protection = false
tags = {
Name = %[1]q
}
}
resource "aws_security_group" "alt_test" {
provider = "awsalternate"
name = %[1]q
vpc_id = aws_vpc.alt_test.id
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = %[1]q
}
}
resource "aws_internet_gateway" "alt_test" {
provider = "awsalternate"
vpc_id = aws_vpc.alt_test.id
tags = {
Name = %[1]q
}
}
resource "aws_globalaccelerator_cross_account_attachment" "alt_test" {
provider = "awsalternate"
name = %[1]q
principals = [data.aws_caller_identity.current.account_id]
resource {
endpoint_id = aws_lb.alt_test.arn
}
}
###############################################################################
## Main account setup.
###############################################################################
data "aws_caller_identity" "current" {}
resource "aws_globalaccelerator_accelerator" "test" {
name = %[1]q
ip_address_type = "IPV4"
enabled = false
}
resource "aws_globalaccelerator_listener" "test" {
accelerator_arn = aws_globalaccelerator_accelerator.test.id
protocol = "TCP"
port_range {
from_port = 80
to_port = 80
}
}
resource "aws_globalaccelerator_endpoint_group" "test" {
listener_arn = aws_globalaccelerator_listener.test.id
endpoint_configuration {
endpoint_id = aws_lb.alt_test.arn
attachment_arn = aws_globalaccelerator_cross_account_attachment.alt_test.arn
weight = 20
client_ip_preservation_enabled = false
}
endpoint_group_region = %[2]q
}
`, rName, acctest.AlternateRegion()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Terraform will only perform drift detection of its value when present in a confi
**Note:** When client IP address preservation is enabled, the Global Accelerator service creates an EC2 Security Group in the VPC named `GlobalAccelerator` that must be deleted (potentially outside of Terraform) before the VPC will successfully delete. If this EC2 Security Group is not deleted, Terraform will retry the VPC deletion for a few minutes before reporting a `DependencyViolation` error. This cannot be resolved by re-running Terraform.
* `endpoint_id` - (Optional) An ID for the endpoint. If the endpoint is a Network Load Balancer or Application Load Balancer, this is the Amazon Resource Name (ARN) of the resource. If the endpoint is an Elastic IP address, this is the Elastic IP address allocation ID.
* `weight` - (Optional) The weight associated with the endpoint. When you add weights to endpoints, you configure AWS Global Accelerator to route traffic based on proportions that you specify.
* `attachment_arn` - (Optional) An ARN of an exposed cross-account attachment. See the [AWS documentation](https://docs.aws.amazon.com/global-accelerator/latest/dg/cross-account-resources.html) for more details.

`port_override` supports the following arguments:

Expand Down Expand Up @@ -104,4 +105,4 @@ Using `terraform import`, import Global Accelerator endpoint groups using the `i
% terraform import aws_globalaccelerator_endpoint_group.example arn:aws:globalaccelerator::111111111111:accelerator/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/listener/xxxxxxx/endpoint-group/xxxxxxxx
```

<!-- cache-key: cdktf-0.20.1 input-a99822bb8d802bdee38671c5875d85175be0aff6331a6b380641cb861eac7402 -->
<!-- cache-key: cdktf-0.20.1 input-a99822bb8d802bdee38671c5875d85175be0aff6331a6b380641cb861eac7402 -->
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Terraform will only perform drift detection of its value when present in a confi

`endpoint_configuration` supports the following arguments:

* `attachment_arn` - (Optional) An ARN of an exposed cross-account attachment. See the [AWS documentation](https://docs.aws.amazon.com/global-accelerator/latest/dg/cross-account-resources.html) for more details.
* `client_ip_preservation_enabled` - (Optional) Indicates whether client IP address preservation is enabled for an Application Load Balancer endpoint. See the [AWS documentation](https://docs.aws.amazon.com/global-accelerator/latest/dg/preserve-client-ip-address.html) for more details. The default value is `false`.
**Note:** When client IP address preservation is enabled, the Global Accelerator service creates an EC2 Security Group in the VPC named `GlobalAccelerator` that must be deleted (potentially outside of Terraform) before the VPC will successfully delete. If this EC2 Security Group is not deleted, Terraform will retry the VPC deletion for a few minutes before reporting a `DependencyViolation` error. This cannot be resolved by re-running Terraform.
* `endpoint_id` - (Optional) An ID for the endpoint. If the endpoint is a Network Load Balancer or Application Load Balancer, this is the Amazon Resource Name (ARN) of the resource. If the endpoint is an Elastic IP address, this is the Elastic IP address allocation ID.
Expand Down

0 comments on commit c20eb9b

Please sign in to comment.