diff --git a/.changelog/28922.txt b/.changelog/28922.txt new file mode 100644 index 000000000000..6693278a3c20 --- /dev/null +++ b/.changelog/28922.txt @@ -0,0 +1,23 @@ +```release-note:new-resource +aws_globalaccelerator_custom_routing_accelerator +``` + +```release-note:new-data-source +aws_globalaccelerator_custom_routing_accelerator +``` + +```release-note:new-resource +aws_globalaccelerator_custom_routing_listener +``` + +```release-note:new-resource +aws_globalaccelerator_custom_routing_endpoint_group +``` + +```release-note:enhancement +resource/aws_globalaccelerator_accelerator: Add `dual_stack_dns_name` attribute +``` + +```release-note:enhancement +data-source/aws_globalaccelerator_accelerator: Add `dual_stack_dns_name` attribute +``` \ No newline at end of file diff --git a/internal/conns/awsclient.go b/internal/conns/awsclient.go index 97f9c1c2b075..cc6cc27ddc1a 100644 --- a/internal/conns/awsclient.go +++ b/internal/conns/awsclient.go @@ -39,7 +39,7 @@ func (client *AWSClient) HTTPClient() *http.Client { return client.httpClient } -// CloudFrontDistributionHostedZoneIDForPartition returns for the Route 53 hosted zone ID +// CloudFrontDistributionHostedZoneID returns the Route 53 hosted zone ID // for Amazon CloudFront distributions in the configured AWS partition. func (client *AWSClient) CloudFrontDistributionHostedZoneID() string { if client.Partition == endpoints.AwsCnPartitionID { @@ -68,3 +68,9 @@ func (client *AWSClient) DefaultKMSKeyPolicy() string { } `, client.Partition, client.AccountID) } + +// GlobalAcceleratorHostedZoneID returns the Route 53 hosted zone ID +// for AWS Global Accelerator accelerators in the configured AWS partition. +func (client *AWSClient) GlobalAcceleratorHostedZoneID() string { + return "Z2BJ6XQ5FK7U4H" // See https://docs.aws.amazon.com/general/latest/gr/global_accelerator.html#global_accelerator_region +} diff --git a/internal/service/globalaccelerator/accelerator.go b/internal/service/globalaccelerator/accelerator.go index 44bb94632e62..5b8f587ec97c 100644 --- a/internal/service/globalaccelerator/accelerator.go +++ b/internal/service/globalaccelerator/accelerator.go @@ -20,11 +20,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// Global Route53 Zone ID for Global Accelerators, exported as a -// convenience attribute for Route53 aliases (see -// https://docs.aws.amazon.com/Route53/latest/APIReference/API_AliasTarget.html). -const route53ZoneID = "Z2BJ6XQ5FK7U4H" - // @SDKResource("aws_globalaccelerator_accelerator") func ResourceAccelerator() *schema.Resource { return &schema.Resource{ @@ -72,6 +67,10 @@ func ResourceAccelerator() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "dual_stack_dns_name": { + Type: schema.TypeString, + Computed: true, + }, "enabled": { Type: schema.TypeBool, Optional: true, @@ -197,8 +196,9 @@ func resourceAcceleratorRead(ctx context.Context, d *schema.ResourceData, meta i } d.Set("dns_name", accelerator.DnsName) + d.Set("dual_stack_dns_name", accelerator.DualStackDnsName) d.Set("enabled", accelerator.Enabled) - d.Set("hosted_zone_id", route53ZoneID) + d.Set("hosted_zone_id", meta.(*conns.AWSClient).GlobalAcceleratorHostedZoneID()) d.Set("ip_address_type", accelerator.IpAddressType) if err := d.Set("ip_sets", flattenIPSets(accelerator.IpSets)); err != nil { return diag.Errorf("setting ip_sets: %s", err) @@ -346,6 +346,97 @@ func resourceAcceleratorDelete(ctx context.Context, d *schema.ResourceData, meta return nil } +func FindAcceleratorByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.Accelerator, error) { + input := &globalaccelerator.DescribeAcceleratorInput{ + AcceleratorArn: aws.String(arn), + } + + return findAccelerator(ctx, conn, input) +} + +func findAccelerator(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeAcceleratorInput) (*globalaccelerator.Accelerator, error) { + output, err := conn.DescribeAcceleratorWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Accelerator == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Accelerator, nil +} + +func FindAcceleratorAttributesByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.AcceleratorAttributes, error) { + input := &globalaccelerator.DescribeAcceleratorAttributesInput{ + AcceleratorArn: aws.String(arn), + } + + return findAcceleratorAttributes(ctx, conn, input) +} + +func findAcceleratorAttributes(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeAcceleratorAttributesInput) (*globalaccelerator.AcceleratorAttributes, error) { + output, err := conn.DescribeAcceleratorAttributesWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.AcceleratorAttributes == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AcceleratorAttributes, nil +} + +func statusAccelerator(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + accelerator, err := FindAcceleratorByARN(ctx, conn, arn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return accelerator, aws.StringValue(accelerator.Status), nil + } +} + +func waitAcceleratorDeployed(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string, timeout time.Duration) (*globalaccelerator.Accelerator, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{globalaccelerator.AcceleratorStatusInProgress}, + Target: []string{globalaccelerator.AcceleratorStatusDeployed}, + Refresh: statusAccelerator(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*globalaccelerator.Accelerator); ok { + return output, err + } + + return nil, err +} + func expandUpdateAcceleratorAttributesInput(tfMap map[string]interface{}) *globalaccelerator.UpdateAcceleratorAttributesInput { if tfMap == nil { return nil diff --git a/internal/service/globalaccelerator/accelerator_data_source.go b/internal/service/globalaccelerator/accelerator_data_source.go index f480a79be3b5..39815b73f492 100644 --- a/internal/service/globalaccelerator/accelerator_data_source.go +++ b/internal/service/globalaccelerator/accelerator_data_source.go @@ -56,6 +56,9 @@ func (d *dataSourceAccelerator) Schema(ctx context.Context, req datasource.Schem "dns_name": schema.StringAttribute{ Computed: true, }, + "dual_stack_dns_name": schema.StringAttribute{ + Computed: true, + }, "enabled": schema.BoolAttribute{ Computed: true, }, @@ -150,8 +153,9 @@ func (d *dataSourceAccelerator) Read(ctx context.Context, request datasource.Rea data.ARN = fwtypes.ARNValue(v) } data.DnsName = flex.StringToFrameworkLegacy(ctx, accelerator.DnsName) + data.DualStackDNSName = flex.StringToFrameworkLegacy(ctx, accelerator.DualStackDnsName) data.Enabled = flex.BoolToFrameworkLegacy(ctx, accelerator.Enabled) - data.HostedZoneID = types.StringValue(route53ZoneID) + data.HostedZoneID = types.StringValue(d.Meta().GlobalAcceleratorHostedZoneID()) data.ID = types.StringValue(acceleratorARN) data.IpAddressType = flex.StringToFrameworkLegacy(ctx, accelerator.IpAddressType) data.IpSets = d.flattenIPSetsFramework(ctx, accelerator.IpSets) @@ -240,14 +244,15 @@ func (d *dataSourceAccelerator) flattenAcceleratorAttributesFramework(ctx contex } type dataSourceAcceleratorData struct { - ARN fwtypes.ARN `tfsdk:"arn"` - Attributes types.List `tfsdk:"attributes"` - DnsName types.String `tfsdk:"dns_name"` - Enabled types.Bool `tfsdk:"enabled"` - HostedZoneID types.String `tfsdk:"hosted_zone_id"` - ID types.String `tfsdk:"id"` - IpAddressType types.String `tfsdk:"ip_address_type"` - IpSets types.List `tfsdk:"ip_sets"` - Name types.String `tfsdk:"name"` - Tags types.Map `tfsdk:"tags"` + ARN fwtypes.ARN `tfsdk:"arn"` + Attributes types.List `tfsdk:"attributes"` + DnsName types.String `tfsdk:"dns_name"` + DualStackDNSName types.String `tfsdk:"dual_stack_dns_name"` + Enabled types.Bool `tfsdk:"enabled"` + HostedZoneID types.String `tfsdk:"hosted_zone_id"` + ID types.String `tfsdk:"id"` + IpAddressType types.String `tfsdk:"ip_address_type"` + IpSets types.List `tfsdk:"ip_sets"` + Name types.String `tfsdk:"name"` + Tags types.Map `tfsdk:"tags"` } diff --git a/internal/service/globalaccelerator/accelerator_data_source_test.go b/internal/service/globalaccelerator/accelerator_data_source_test.go index 79dcc2ead448..3829658d1f02 100644 --- a/internal/service/globalaccelerator/accelerator_data_source_test.go +++ b/internal/service/globalaccelerator/accelerator_data_source_test.go @@ -30,6 +30,7 @@ func TestAccGlobalAcceleratorAcceleratorDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSource1Name, "attributes.0.flow_logs_s3_bucket", resourceName, "attributes.0.flow_logs_s3_bucket"), resource.TestCheckResourceAttrPair(dataSource1Name, "attributes.0.flow_logs_s3_prefix", resourceName, "attributes.0.flow_logs_s3_prefix"), resource.TestCheckResourceAttrPair(dataSource1Name, "dns_name", resourceName, "dns_name"), + resource.TestCheckResourceAttrPair(dataSource1Name, "dual_stack_dns_name", resourceName, "dual_stack_dns_name"), resource.TestCheckResourceAttrPair(dataSource1Name, "enabled", resourceName, "enabled"), resource.TestCheckResourceAttrPair(dataSource1Name, "hosted_zone_id", resourceName, "hosted_zone_id"), resource.TestCheckResourceAttrPair(dataSource1Name, "ip_address_type", resourceName, "ip_address_type"), @@ -46,6 +47,7 @@ func TestAccGlobalAcceleratorAcceleratorDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSource2Name, "attributes.0.flow_logs_s3_bucket", resourceName, "attributes.0.flow_logs_s3_bucket"), resource.TestCheckResourceAttrPair(dataSource2Name, "attributes.0.flow_logs_s3_prefix", resourceName, "attributes.0.flow_logs_s3_prefix"), resource.TestCheckResourceAttrPair(dataSource2Name, "dns_name", resourceName, "dns_name"), + resource.TestCheckResourceAttrPair(dataSource2Name, "dual_stack_dns_name", resourceName, "dual_stack_dns_name"), resource.TestCheckResourceAttrPair(dataSource2Name, "enabled", resourceName, "enabled"), resource.TestCheckResourceAttrPair(dataSource2Name, "hosted_zone_id", resourceName, "hosted_zone_id"), resource.TestCheckResourceAttrPair(dataSource2Name, "ip_address_type", resourceName, "ip_address_type"), diff --git a/internal/service/globalaccelerator/accelerator_test.go b/internal/service/globalaccelerator/accelerator_test.go index 063d3d58fc38..9d04005ae71c 100644 --- a/internal/service/globalaccelerator/accelerator_test.go +++ b/internal/service/globalaccelerator/accelerator_test.go @@ -33,13 +33,14 @@ func TestAccGlobalAcceleratorAccelerator_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccAcceleratorConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAcceleratorExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "attributes.#", "1"), resource.TestCheckResourceAttr(resourceName, "attributes.0.flow_logs_enabled", "false"), resource.TestCheckResourceAttr(resourceName, "attributes.0.flow_logs_s3_bucket", ""), resource.TestCheckResourceAttr(resourceName, "attributes.0.flow_logs_s3_prefix", ""), resource.TestMatchResourceAttr(resourceName, "dns_name", dnsNameRegex), + resource.TestCheckResourceAttr(resourceName, "dual_stack_dns_name", ""), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), resource.TestCheckResourceAttr(resourceName, "hosted_zone_id", "Z2BJ6XQ5FK7U4H"), resource.TestCheckResourceAttr(resourceName, "ip_address_type", "IPV4"), @@ -65,6 +66,7 @@ func TestAccGlobalAcceleratorAccelerator_ipAddressType_dualStack(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_globalaccelerator_accelerator.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dualStackDNSNameRegex := regexp.MustCompile(`^a[a-f0-9]{16}\.dualstack\.awsglobalaccelerator\.com$`) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, @@ -76,6 +78,7 @@ func TestAccGlobalAcceleratorAccelerator_ipAddressType_dualStack(t *testing.T) { Config: testAccAcceleratorConfig_ipAddressTypeDualStack(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAcceleratorExists(ctx, resourceName), + resource.TestMatchResourceAttr(resourceName, "dual_stack_dns_name", dualStackDNSNameRegex), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), resource.TestCheckResourceAttr(resourceName, "ip_address_type", "DUAL_STACK"), resource.TestCheckResourceAttr(resourceName, "ip_sets.#", "2"), @@ -473,11 +476,6 @@ resource "aws_s3_bucket" "test" { force_destroy = true } -resource "aws_s3_bucket_acl" "test" { - bucket = aws_s3_bucket.test.id - acl = "private" -} - resource "aws_globalaccelerator_accelerator" "test" { name = %[1]q ip_address_type = "IPV4" diff --git a/internal/service/globalaccelerator/custom_routing_accelerator.go b/internal/service/globalaccelerator/custom_routing_accelerator.go new file mode 100644 index 000000000000..2cd02b51ce21 --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_accelerator.go @@ -0,0 +1,444 @@ +package globalaccelerator + +import ( + "context" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/globalaccelerator" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +// @SDKResource("aws_globalaccelerator_custom_routing_accelerator") +func ResourceCustomRoutingAccelerator() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomRoutingAcceleratorCreate, + ReadWithoutTimeout: resourceCustomRoutingAcceleratorRead, + UpdateWithoutTimeout: resourceCustomRoutingAcceleratorUpdate, + DeleteWithoutTimeout: resourceCustomRoutingAcceleratorDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "attributes": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flow_logs_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "flow_logs_s3_bucket": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 255), + }, + "flow_logs_s3_prefix": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 255), + }, + }, + }, + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "hosted_zone_id": { + Type: schema.TypeString, + Computed: true, + }, + "ip_address_type": { + Type: schema.TypeString, + Optional: true, + Default: globalaccelerator.IpAddressTypeIpv4, + ValidateFunc: validation.StringInSlice(globalaccelerator.IpAddressType_Values(), false), + }, + "ip_addresses": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ip_sets": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_addresses": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ip_family": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 255), + validation.StringMatch(regexp.MustCompile(`^[0-9A-Za-z-]+$`), "only alphanumeric characters and hyphens are allowed"), + validation.StringDoesNotMatch(regexp.MustCompile(`^-`), "cannot start with a hyphen"), + validation.StringDoesNotMatch(regexp.MustCompile(`-$`), "cannot end with a hyphen"), + ), + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceCustomRoutingAcceleratorCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) + + name := d.Get("name").(string) + input := &globalaccelerator.CreateCustomRoutingAcceleratorInput{ + Name: aws.String(name), + IdempotencyToken: aws.String(resource.UniqueId()), + Enabled: aws.Bool(d.Get("enabled").(bool)), + Tags: Tags(tags.IgnoreAWS()), + } + + if v, ok := d.GetOk("ip_address_type"); ok { + input.IpAddressType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("ip_addresses"); ok && len(v.([]interface{})) > 0 { + input.IpAddresses = flex.ExpandStringList(v.([]interface{})) + } + + output, err := conn.CreateCustomRoutingAcceleratorWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating Global Accelerator Custom Routing Accelerator (%s): %s", name, err) + } + + d.SetId(aws.StringValue(output.Accelerator.AcceleratorArn)) + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", d.Id(), err) + } + + if v, ok := d.GetOk("attributes"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input := expandUpdateAcceleratorAttributesInput(v.([]interface{})[0].(map[string]interface{})) + input.AcceleratorArn = aws.String(d.Id()) + + if _, err := conn.UpdateAcceleratorAttributesWithContext(ctx, input); err != nil { + return sdkdiag.AppendErrorf(diags, "updating Global Accelerator Custom Routing Accelerator (%s) attributes: %s", d.Id(), err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", d.Id(), err) + } + } + + return append(diags, resourceCustomRoutingAcceleratorRead(ctx, d, meta)...) +} + +func resourceCustomRoutingAcceleratorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + accelerator, err := FindCustomRoutingAcceleratorByARN(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Global Accelerator Custom Routing Accelerator (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Global Accelerator Custom Routing Accelerator (%s): %s", d.Id(), err) + } + + d.Set("dns_name", accelerator.DnsName) + d.Set("enabled", accelerator.Enabled) + d.Set("hosted_zone_id", meta.(*conns.AWSClient).GlobalAcceleratorHostedZoneID()) + d.Set("ip_address_type", accelerator.IpAddressType) + if err := d.Set("ip_sets", flattenIPSets(accelerator.IpSets)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting ip_sets: %s", err) + } + d.Set("name", accelerator.Name) + + acceleratorAttributes, err := FindCustomRoutingAcceleratorAttributesByARN(ctx, conn, d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Global Accelerator Custom Routing Accelerator (%s) attributes: %s", d.Id(), err) + } + + if err := d.Set("attributes", []interface{}{flattenCustomRoutingAcceleratorAttributes(acceleratorAttributes)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting attributes: %s", err) + } + + tags, err := ListTags(ctx, conn, d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "listing tags for Global Accelerator Custom Routing Accelerator (%s): %s", d.Id(), err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tags_all: %s", err) + } + + return diags +} + +func resourceCustomRoutingAcceleratorUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + if d.HasChanges("name", "ip_address_type", "enabled") { + input := &globalaccelerator.UpdateCustomRoutingAcceleratorInput{ + AcceleratorArn: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + Enabled: aws.Bool(d.Get("enabled").(bool)), + } + + if v, ok := d.GetOk("ip_address_type"); ok { + input.IpAddressType = aws.String(v.(string)) + } + + _, err := conn.UpdateCustomRoutingAcceleratorWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Global Accelerator Custom Routing Accelerator (%s): %s", d.Id(), err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", d.Id(), err) + } + } + + if d.HasChange("attributes") { + o, n := d.GetChange("attributes") + if len(o.([]interface{})) > 0 && o.([]interface{})[0] != nil { + if len(n.([]interface{})) > 0 && n.([]interface{})[0] != nil { + oInput := expandUpdateCustomRoutingAcceleratorAttributesInput(o.([]interface{})[0].(map[string]interface{})) + oInput.AcceleratorArn = aws.String(d.Id()) + nInput := expandUpdateCustomRoutingAcceleratorAttributesInput(n.([]interface{})[0].(map[string]interface{})) + nInput.AcceleratorArn = aws.String(d.Id()) + + // To change flow logs bucket and prefix attributes while flows are enabled, first disable flow logs. + if aws.BoolValue(oInput.FlowLogsEnabled) && aws.BoolValue(nInput.FlowLogsEnabled) { + oInput.FlowLogsEnabled = aws.Bool(false) + + _, err := conn.UpdateCustomRoutingAcceleratorAttributesWithContext(ctx, oInput) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Global Accelerator Custom Routing Accelerator (%s) attributes: %s", d.Id(), err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", d.Id(), err) + } + } + + _, err := conn.UpdateCustomRoutingAcceleratorAttributesWithContext(ctx, nInput) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Global Accelerator Custom Routing Accelerator (%s) attributes: %s", d.Id(), err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", d.Id(), err) + } + } + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { + return sdkdiag.AppendErrorf(diags, "updating Global Accelerator Custom Routing Accelerator (%s) tags: %s", d.Id(), err) + } + } + + return append(diags, resourceCustomRoutingAcceleratorRead(ctx, d, meta)...) +} + +func resourceCustomRoutingAcceleratorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + input := &globalaccelerator.UpdateCustomRoutingAcceleratorInput{ + AcceleratorArn: aws.String(d.Id()), + Enabled: aws.Bool(false), + } + + _, err := conn.UpdateCustomRoutingAcceleratorWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { + return nil + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "disabling Global Accelerator Custom Routing Accelerator (%s): %s", d.Id(), err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", d.Id(), err) + } + + log.Printf("[DEBUG] Deleting Global Accelerator Custom Routing Accelerator (%s)", d.Id()) + _, err = conn.DeleteCustomRoutingAcceleratorWithContext(ctx, &globalaccelerator.DeleteCustomRoutingAcceleratorInput{ + AcceleratorArn: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { + return nil + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Global Accelerator Custom Routing Accelerator (%s): %s", d.Id(), err) + } + + return diags +} + +func FindCustomRoutingAcceleratorByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.CustomRoutingAccelerator, error) { + input := &globalaccelerator.DescribeCustomRoutingAcceleratorInput{ + AcceleratorArn: aws.String(arn), + } + + return findCustomRoutingAccelerator(ctx, conn, input) +} + +func findCustomRoutingAccelerator(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeCustomRoutingAcceleratorInput) (*globalaccelerator.CustomRoutingAccelerator, error) { + output, err := conn.DescribeCustomRoutingAcceleratorWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Accelerator == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Accelerator, nil +} + +func FindCustomRoutingAcceleratorAttributesByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.CustomRoutingAcceleratorAttributes, error) { + input := &globalaccelerator.DescribeCustomRoutingAcceleratorAttributesInput{ + AcceleratorArn: aws.String(arn), + } + + return findCustomRoutingAcceleratorAttributes(ctx, conn, input) +} + +func findCustomRoutingAcceleratorAttributes(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeCustomRoutingAcceleratorAttributesInput) (*globalaccelerator.CustomRoutingAcceleratorAttributes, error) { + output, err := conn.DescribeCustomRoutingAcceleratorAttributesWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.AcceleratorAttributes == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AcceleratorAttributes, nil +} + +func statusCustomRoutingAccelerator(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + accelerator, err := FindCustomRoutingAcceleratorByARN(ctx, conn, arn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return accelerator, aws.StringValue(accelerator.Status), nil + } +} + +func waitCustomRoutingAcceleratorDeployed(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string, timeout time.Duration) (*globalaccelerator.CustomRoutingAccelerator, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{globalaccelerator.AcceleratorStatusInProgress}, + Target: []string{globalaccelerator.AcceleratorStatusDeployed}, + Refresh: statusCustomRoutingAccelerator(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*globalaccelerator.CustomRoutingAccelerator); ok { + return output, err + } + + return nil, err +} + +func expandUpdateCustomRoutingAcceleratorAttributesInput(tfMap map[string]interface{}) *globalaccelerator.UpdateCustomRoutingAcceleratorAttributesInput { + return (*globalaccelerator.UpdateCustomRoutingAcceleratorAttributesInput)(expandUpdateAcceleratorAttributesInput(tfMap)) +} + +func flattenCustomRoutingAcceleratorAttributes(apiObject *globalaccelerator.CustomRoutingAcceleratorAttributes) map[string]interface{} { + return flattenAcceleratorAttributes((*globalaccelerator.AcceleratorAttributes)(apiObject)) +} diff --git a/internal/service/globalaccelerator/custom_routing_accelerator_data_source.go b/internal/service/globalaccelerator/custom_routing_accelerator_data_source.go new file mode 100644 index 000000000000..89daabf4b105 --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_accelerator_data_source.go @@ -0,0 +1,161 @@ +package globalaccelerator + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/globalaccelerator" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +// @SDKDataSource("aws_globalaccelerator_custom_routing_accelerator") +func DataSourceCustomRoutingAccelerator() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceCustomRoutingAcceleratorRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "attributes": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flow_logs_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "flow_logs_s3_bucket": { + Type: schema.TypeString, + Computed: true, + }, + "flow_logs_s3_prefix": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + }, + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "hosted_zone_id": { + Type: schema.TypeString, + Computed: true, + }, + "ip_address_type": { + Type: schema.TypeString, + Computed: true, + }, + "ip_sets": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_addresses": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ip_family": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceCustomRoutingAcceleratorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + var results []*globalaccelerator.CustomRoutingAccelerator + + err := conn.ListCustomRoutingAcceleratorsPagesWithContext(ctx, &globalaccelerator.ListCustomRoutingAcceleratorsInput{}, func(page *globalaccelerator.ListCustomRoutingAcceleratorsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, l := range page.Accelerators { + if l == nil { + continue + } + + if v, ok := d.GetOk("arn"); ok && v.(string) != aws.StringValue(l.AcceleratorArn) { + continue + } + + if v, ok := d.GetOk("name"); ok && v.(string) != aws.StringValue(l.Name) { + continue + } + + results = append(results, l) + } + + return !lastPage + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "listing Global Accelerator Custom Routing Accelerators: %s", err) + } + + if count := len(results); count != 1 { + return sdkdiag.AppendErrorf(diags, "search returned %d results, please revise so only one is returned", count) + } + + accelerator := results[0] + d.SetId(aws.StringValue(accelerator.AcceleratorArn)) + d.Set("arn", accelerator.AcceleratorArn) + d.Set("dns_name", accelerator.DnsName) + d.Set("enabled", accelerator.Enabled) + d.Set("hosted_zone_id", meta.(*conns.AWSClient).GlobalAcceleratorHostedZoneID()) + d.Set("ip_address_type", accelerator.IpAddressType) + if err := d.Set("ip_sets", flattenIPSets(accelerator.IpSets)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting ip_sets: %s", err) + } + d.Set("name", accelerator.Name) + + acceleratorAttributes, err := FindCustomRoutingAcceleratorAttributesByARN(ctx, conn, d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Global Accelerator Custom Routing Accelerator (%s) attributes: %s", d.Id(), err) + } + + if err := d.Set("attributes", []interface{}{flattenCustomRoutingAcceleratorAttributes(acceleratorAttributes)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting attributes: %s", err) + } + + tags, err := ListTags(ctx, conn, d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "listing tags for Global Accelerator Custom Routing Accelerator (%s): %s", d.Id(), err) + } + + if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) + } + + return diags +} diff --git a/internal/service/globalaccelerator/custom_routing_accelerator_data_source_test.go b/internal/service/globalaccelerator/custom_routing_accelerator_data_source_test.go new file mode 100644 index 000000000000..14496af150ed --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_accelerator_data_source_test.go @@ -0,0 +1,67 @@ +package globalaccelerator_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/globalaccelerator" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccGlobalAcceleratorCustomRoutingAcceleratorDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_globalaccelerator_custom_routing_accelerator.test" + dataSource1Name := "data.aws_globalaccelerator_custom_routing_accelerator.test_by_arn" + dataSourceName2 := "data.aws_globalaccelerator_custom_routing_accelerator.test_by_name" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingAcceleratorDataSourceConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSource1Name, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSource1Name, "enabled", resourceName, "enabled"), + resource.TestCheckResourceAttrPair(dataSource1Name, "dns_name", resourceName, "dns_name"), + resource.TestCheckResourceAttrPair(dataSource1Name, "hosted_zone_id", resourceName, "hosted_zone_id"), + resource.TestCheckResourceAttrPair(dataSource1Name, "ip_sets.#", resourceName, "ip_sets.#"), + resource.TestCheckResourceAttrPair(dataSource1Name, "ip_sets.0.ip_addresses.#", resourceName, "ip_sets.0.ip_addresses.#"), + resource.TestCheckResourceAttrPair(dataSource1Name, "ip_sets.0.ip_addresses.0", resourceName, "ip_sets.0.ip_addresses.0"), + resource.TestCheckResourceAttrPair(dataSource1Name, "ip_sets.0.ip_addresses.1", resourceName, "ip_sets.0.ip_addresses.1"), + resource.TestCheckResourceAttrPair(dataSource1Name, "ip_sets.0.ip_family", resourceName, "ip_sets.0.ip_family"), + + resource.TestCheckResourceAttrPair(dataSourceName2, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName2, "enabled", resourceName, "enabled"), + resource.TestCheckResourceAttrPair(dataSourceName2, "dns_name", resourceName, "dns_name"), + resource.TestCheckResourceAttrPair(dataSourceName2, "hosted_zone_id", resourceName, "hosted_zone_id"), + resource.TestCheckResourceAttrPair(dataSourceName2, "ip_sets.#", resourceName, "ip_sets.#"), + resource.TestCheckResourceAttrPair(dataSourceName2, "ip_sets.0.ip_addresses.#", resourceName, "ip_sets.0.ip_addresses.#"), + resource.TestCheckResourceAttrPair(dataSourceName2, "ip_sets.0.ip_addresses.0", resourceName, "ip_sets.0.ip_addresses.0"), + resource.TestCheckResourceAttrPair(dataSourceName2, "ip_sets.0.ip_addresses.1", resourceName, "ip_sets.0.ip_addresses.1"), + resource.TestCheckResourceAttrPair(dataSourceName2, "ip_sets.0.ip_family", resourceName, "ip_sets.0.ip_family"), + ), + }, + }, + }) +} + +func testAccCustomRoutingAcceleratorDataSourceConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q +} + +data "aws_globalaccelerator_custom_routing_accelerator" "test_by_arn" { + arn = aws_globalaccelerator_custom_routing_accelerator.test.id +} + +data "aws_globalaccelerator_custom_routing_accelerator" "test_by_name" { + name = aws_globalaccelerator_custom_routing_accelerator.test.name +} +`, rName) +} diff --git a/internal/service/globalaccelerator/custom_routing_accelerator_test.go b/internal/service/globalaccelerator/custom_routing_accelerator_test.go new file mode 100644 index 000000000000..ce7b494a7279 --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_accelerator_test.go @@ -0,0 +1,241 @@ +package globalaccelerator_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/globalaccelerator" + 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" + tfglobalaccelerator "github.com/hashicorp/terraform-provider-aws/internal/service/globalaccelerator" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccGlobalAcceleratorCustomRoutingAccelerator_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_globalaccelerator_custom_routing_accelerator.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + ipRegex := regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`) + dnsNameRegex := regexp.MustCompile(`^a[a-f0-9]{16}\.awsglobalaccelerator\.com$`) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingAcceleratorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingAcceleratorConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "attributes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "attributes.0.flow_logs_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "attributes.0.flow_logs_s3_bucket", ""), + resource.TestCheckResourceAttr(resourceName, "attributes.0.flow_logs_s3_prefix", ""), + resource.TestMatchResourceAttr(resourceName, "dns_name", dnsNameRegex), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "hosted_zone_id", "Z2BJ6XQ5FK7U4H"), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "IPV4"), + resource.TestCheckResourceAttr(resourceName, "ip_addresses.#", "0"), + resource.TestCheckResourceAttr(resourceName, "ip_sets.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_sets.0.ip_addresses.#", "2"), + resource.TestMatchResourceAttr(resourceName, "ip_sets.0.ip_addresses.0", ipRegex), + resource.TestMatchResourceAttr(resourceName, "ip_sets.0.ip_addresses.1", ipRegex), + resource.TestCheckResourceAttr(resourceName, "ip_sets.0.ip_family", "IPv4"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGlobalAcceleratorCustomRoutingAccelerator_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_globalaccelerator_custom_routing_accelerator.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingAcceleratorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingAcceleratorConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfglobalaccelerator.ResourceCustomRoutingAccelerator(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccGlobalAcceleratorCustomRoutingAccelerator_tags(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_globalaccelerator_custom_routing_accelerator.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingAcceleratorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingAcceleratorConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCustomRoutingAcceleratorConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccCustomRoutingAcceleratorConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccGlobalAcceleratorCustomRoutingAccelerator_update(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_globalaccelerator_custom_routing_accelerator.test" + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingAcceleratorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingAcceleratorConfig_basic(rName1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCustomRoutingAcceleratorConfig_basic(rName2), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomRoutingAcceleratorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), + }, + }, + }) +} + +func testAccCheckCustomRoutingAcceleratorExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GlobalAcceleratorConn() + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Global Accelerator Custom Routing Accelerator ID is set") + } + + _, err := tfglobalaccelerator.FindCustomRoutingAcceleratorByARN(ctx, conn, rs.Primary.ID) + + return err + } +} + +func testAccCheckCustomRoutingAcceleratorDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GlobalAcceleratorConn() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_globalaccelerator_custom_routing_accelerator" { + continue + } + + _, err := tfglobalaccelerator.FindCustomRoutingAcceleratorByARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Global Accelerator Custom Routing Accelerator %s still exists", rs.Primary.ID) + } + return nil + } +} + +func testAccCustomRoutingAcceleratorConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q +} +`, rName) +} + +func testAccCustomRoutingAcceleratorConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccCustomRoutingAcceleratorConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/globalaccelerator/custom_routing_endpoint_group.go b/internal/service/globalaccelerator/custom_routing_endpoint_group.go new file mode 100644 index 000000000000..867e1a39355c --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_endpoint_group.go @@ -0,0 +1,403 @@ +package globalaccelerator + +import ( + "context" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/globalaccelerator" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +// @SDKResource("aws_globalaccelerator_custom_routing_endpoint_group") +func ResourceCustomRoutingEndpointGroup() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomRoutingEndpointGroupCreate, + ReadWithoutTimeout: resourceCustomRoutingEndpointGroupRead, + DeleteWithoutTimeout: resourceCustomRoutingEndpointGroupDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "destination_configuration": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IsPortNumber, + }, + "protocols": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(globalaccelerator.CustomRoutingProtocol_Values(), false), + }, + }, + "to_port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IsPortNumber, + }, + }, + }, + }, + "endpoint_configuration": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + "endpoint_group_region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "listener_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + }, + } +} + +func resourceCustomRoutingEndpointGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + region := meta.(*conns.AWSClient).Region + + input := &globalaccelerator.CreateCustomRoutingEndpointGroupInput{ + DestinationConfigurations: expandCustomRoutingDestinationConfigurations(d.Get("destination_configuration").(*schema.Set).List()), + EndpointGroupRegion: aws.String(region), + IdempotencyToken: aws.String(resource.UniqueId()), + ListenerArn: aws.String(d.Get("listener_arn").(string)), + } + + output, err := conn.CreateCustomRoutingEndpointGroupWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating Global Accelerator Custom Routing Endpoint Group: %s", err) + } + + d.SetId(aws.StringValue(output.EndpointGroup.EndpointGroupArn)) + + acceleratorARN, err := ListenerOrEndpointGroupARNToAcceleratorARN(d.Id()) + + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, acceleratorARN, d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", acceleratorARN, err) + } + + if v, ok := d.GetOk("endpoint_configuration"); ok { + input := &globalaccelerator.AddCustomRoutingEndpointsInput{ + EndpointGroupArn: aws.String(d.Id()), + EndpointConfigurations: expandCustomRoutingEndpointConfigurations(v.(*schema.Set).List()), + } + + _, err := conn.AddCustomRoutingEndpointsWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "adding Global Accelerator Custom Routing Endpoint Group (%s) endpoints: %s", d.Id(), err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, acceleratorARN, d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", acceleratorARN, err) + } + } + + return append(diags, resourceCustomRoutingEndpointGroupRead(ctx, d, meta)...) +} + +func resourceCustomRoutingEndpointGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + endpointGroup, err := FindCustomRoutingEndpointGroupByARN(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Global Accelerator Custom Routing Endpoint Group (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Global Accelerator Custom Routing Endpoint Group (%s): %s", d.Id(), err) + } + + listenerARN, err := EndpointGroupARNToListenerARN(d.Id()) + + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + d.Set("arn", endpointGroup.EndpointGroupArn) + if err := d.Set("destination_configuration", flattenCustomRoutingDestinationDescriptions(endpointGroup.DestinationDescriptions)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting destination_configuration: %s", err) + } + d.Set("endpoint_group_region", endpointGroup.EndpointGroupRegion) + if err := d.Set("endpoint_configuration", flattenCustomRoutingEndpointDescriptions(endpointGroup.EndpointDescriptions)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting endpoint_configuration: %s", err) + } + d.Set("listener_arn", listenerARN) + + return diags +} + +func resourceCustomRoutingEndpointGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + log.Printf("[DEBUG] Deleting Global Accelerator Custom Routing Endpoint Group (%s)", d.Id()) + _, err := conn.DeleteCustomRoutingEndpointGroupWithContext(ctx, &globalaccelerator.DeleteCustomRoutingEndpointGroupInput{ + EndpointGroupArn: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeEndpointGroupNotFoundException) { + return nil + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Global Accelerator Custom Routing Endpoint Group (%s): %s", d.Id(), err) + } + + acceleratorARN, err := ListenerOrEndpointGroupARNToAcceleratorARN(d.Id()) + + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, acceleratorARN, d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", acceleratorARN, err) + } + + return diags +} + +func FindCustomRoutingEndpointGroupByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.CustomRoutingEndpointGroup, error) { + input := &globalaccelerator.DescribeCustomRoutingEndpointGroupInput{ + EndpointGroupArn: aws.String(arn), + } + + return findCustomRoutingEndpointGroup(ctx, conn, input) +} + +func findCustomRoutingEndpointGroup(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeCustomRoutingEndpointGroupInput) (*globalaccelerator.CustomRoutingEndpointGroup, error) { + output, err := conn.DescribeCustomRoutingEndpointGroupWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeEndpointGroupNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.EndpointGroup == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.EndpointGroup, nil +} + +func expandCustomRoutingEndpointDestinationConfiguration(tfMap map[string]interface{}) *globalaccelerator.CustomRoutingDestinationConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &globalaccelerator.CustomRoutingDestinationConfiguration{} + + if v, ok := tfMap["from_port"].(int); ok && v != 0 { + apiObject.FromPort = aws.Int64(int64(v)) + } + + if v, ok := tfMap["protocols"].(*schema.Set); ok { + apiObject.Protocols = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["to_port"].(int); ok && v != 0 { + apiObject.ToPort = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandCustomRoutingDestinationConfigurations(tfList []interface{}) []*globalaccelerator.CustomRoutingDestinationConfiguration { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*globalaccelerator.CustomRoutingDestinationConfiguration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCustomRoutingEndpointDestinationConfiguration(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandCustomRoutingEndpointConfiguration(tfMap map[string]interface{}) *globalaccelerator.CustomRoutingEndpointConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &globalaccelerator.CustomRoutingEndpointConfiguration{} + + if v, ok := tfMap["endpoint_id"].(string); ok && v != "" { + apiObject.EndpointId = aws.String(v) + } + + return apiObject +} + +func expandCustomRoutingEndpointConfigurations(tfList []interface{}) []*globalaccelerator.CustomRoutingEndpointConfiguration { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*globalaccelerator.CustomRoutingEndpointConfiguration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCustomRoutingEndpointConfiguration(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenCustomRoutingDestinationDescription(apiObject *globalaccelerator.CustomRoutingDestinationDescription) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.FromPort; v != nil { + tfMap["from_port"] = aws.Int64Value(v) + } + + if v := apiObject.Protocols; v != nil { + tfMap["protocols"] = aws.StringValueSlice(v) + } + + if v := apiObject.ToPort; v != nil { + tfMap["to_port"] = aws.Int64Value(v) + } + + return tfMap +} + +func flattenCustomRoutingDestinationDescriptions(apiObjects []*globalaccelerator.CustomRoutingDestinationDescription) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenCustomRoutingDestinationDescription(apiObject)) + } + + return tfList +} + +func flattenCustomRoutingEndpointDescription(apiObject *globalaccelerator.CustomRoutingEndpointDescription) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.EndpointId; v != nil { + tfMap["endpoint_id"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenCustomRoutingEndpointDescriptions(apiObjects []*globalaccelerator.CustomRoutingEndpointDescription) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenCustomRoutingEndpointDescription(apiObject)) + } + + return tfList +} diff --git a/internal/service/globalaccelerator/custom_routing_endpoint_group_test.go b/internal/service/globalaccelerator/custom_routing_endpoint_group_test.go new file mode 100644 index 000000000000..ace72e0a3e63 --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_endpoint_group_test.go @@ -0,0 +1,247 @@ +package globalaccelerator_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/globalaccelerator" + 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" + tfglobalaccelerator "github.com/hashicorp/terraform-provider-aws/internal/service/globalaccelerator" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccGlobalAcceleratorCustomRoutingEndpointGroup_basic(t *testing.T) { + ctx := acctest.Context(t) + var v globalaccelerator.CustomRoutingEndpointGroup + resourceName := "aws_globalaccelerator_custom_routing_endpoint_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingEndpointGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingEndpointGroupConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomRoutingEndpointGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.0.from_port", "443"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.0.protocols.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "destination_configuration.0.protocols.*", "TCP"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.0.to_port", "8443"), + resource.TestCheckResourceAttr(resourceName, "endpoint_configuration.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "endpoint_group_region"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGlobalAcceleratorCustomRoutingEndpointGroup_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v globalaccelerator.CustomRoutingEndpointGroup + resourceName := "aws_globalaccelerator_custom_routing_endpoint_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingEndpointGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingEndpointGroupConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomRoutingEndpointGroupExists(ctx, resourceName, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfglobalaccelerator.ResourceCustomRoutingEndpointGroup(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccGlobalAcceleratorCustomRoutingEndpointGroup_endpointConfiguration(t *testing.T) { + ctx := acctest.Context(t) + var v globalaccelerator.CustomRoutingEndpointGroup + resourceName := "aws_globalaccelerator_custom_routing_endpoint_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingEndpointGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingEndpointGroupConfig_endpointConfiguration(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomRoutingEndpointGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.0.from_port", "8080"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.0.protocols.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "destination_configuration.0.protocols.*", "TCP"), + resource.TestCheckResourceAttr(resourceName, "destination_configuration.0.to_port", "8081"), + resource.TestCheckResourceAttr(resourceName, "endpoint_configuration.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "endpoint_configuration.0.endpoint_id"), + resource.TestCheckResourceAttrSet(resourceName, "endpoint_group_region"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCustomRoutingEndpointGroupExists(ctx context.Context, n string, v *globalaccelerator.CustomRoutingEndpointGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GlobalAcceleratorConn() + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Global Accelerator Custom Routing Endpoint Group ID is set") + } + + output, err := tfglobalaccelerator.FindCustomRoutingEndpointGroupByARN(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckCustomRoutingEndpointGroupDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GlobalAcceleratorConn() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_globalaccelerator_custom_routing_endpoint_group" { + continue + } + + _, err := tfglobalaccelerator.FindCustomRoutingEndpointGroupByARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Global Accelerator Custom Routing Endpoint Group %s still exists", rs.Primary.ID) + } + return nil + } +} + +func testAccCustomRoutingEndpointGroupConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q +} + +resource "aws_globalaccelerator_custom_routing_listener" "test" { + accelerator_arn = aws_globalaccelerator_custom_routing_accelerator.test.id + + port_range { + from_port = 443 + to_port = 443 + } +} + +resource "aws_globalaccelerator_custom_routing_endpoint_group" "test" { + listener_arn = aws_globalaccelerator_custom_routing_listener.test.id + + destination_configuration { + from_port = 443 + to_port = 8443 + protocols = ["TCP"] + } +} +`, rName) +} + +func testAccCustomRoutingEndpointGroupConfig_endpointConfiguration(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q +} + +resource "aws_globalaccelerator_custom_routing_listener" "test" { + accelerator_arn = aws_globalaccelerator_custom_routing_accelerator.test.id + + port_range { + from_port = 1 + to_port = 65534 + } +} + +resource "aws_globalaccelerator_custom_routing_endpoint_group" "test" { + listener_arn = aws_globalaccelerator_custom_routing_listener.test.id + + destination_configuration { + from_port = 8080 + to_port = 8081 + protocols = ["TCP"] + } + + endpoint_configuration { + endpoint_id = aws_subnet.test.id + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/28" + + tags = { + Name = %[1]q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} +`, rName)) +} diff --git a/internal/service/globalaccelerator/custom_routing_listener.go b/internal/service/globalaccelerator/custom_routing_listener.go new file mode 100644 index 000000000000..d5cf3f5a511d --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_listener.go @@ -0,0 +1,203 @@ +package globalaccelerator + +import ( + "context" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/globalaccelerator" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +// @SDKResource("aws_globalaccelerator_custom_routing_listener") +func ResourceCustomRoutingListener() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomRoutingListenerCreate, + ReadWithoutTimeout: resourceCustomRoutingListenerRead, + UpdateWithoutTimeout: resourceCustomRoutingListenerUpdate, + DeleteWithoutTimeout: resourceCustomRoutingListenerDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "accelerator_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "port_range": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + MaxItems: 10, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, + "to_port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, + }, + }, + }, + }, + } +} + +func resourceCustomRoutingListenerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + acceleratorARN := d.Get("accelerator_arn").(string) + input := &globalaccelerator.CreateCustomRoutingListenerInput{ + AcceleratorArn: aws.String(acceleratorARN), + IdempotencyToken: aws.String(resource.UniqueId()), + PortRanges: expandPortRanges(d.Get("port_range").(*schema.Set).List()), + } + + output, err := conn.CreateCustomRoutingListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating Global Accelerator Custom Routing Listener: %s", err) + } + + d.SetId(aws.StringValue(output.Listener.ListenerArn)) + + // Creating a listener triggers the accelerator to change status to InPending. + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, acceleratorARN, d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", acceleratorARN, err) + } + + return append(diags, resourceCustomRoutingListenerRead(ctx, d, meta)...) +} + +func resourceCustomRoutingListenerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + listener, err := FindCustomRoutingListenerByARN(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Global Accelerator Custom Routing Listener (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Global Accelerator Custom Routing Listener (%s): %s", d.Id(), err) + } + + acceleratorARN, err := ListenerOrEndpointGroupARNToAcceleratorARN(d.Id()) + + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + d.Set("accelerator_arn", acceleratorARN) + if err := d.Set("port_range", flattenPortRanges(listener.PortRanges)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting port_range: %s", err) + } + + return diags +} + +func resourceCustomRoutingListenerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + acceleratorARN := d.Get("accelerator_arn").(string) + + input := &globalaccelerator.UpdateCustomRoutingListenerInput{ + ListenerArn: aws.String(d.Id()), + PortRanges: expandPortRanges(d.Get("port_range").(*schema.Set).List()), + } + + _, err := conn.UpdateCustomRoutingListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Global Accelerator Custom Routing Listener (%s): %s", d.Id(), err) + } + + // Updating a listener triggers the accelerator to change status to InPending. + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, acceleratorARN, d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", acceleratorARN, err) + } + + return append(diags, resourceCustomRoutingListenerRead(ctx, d, meta)...) +} + +func resourceCustomRoutingListenerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GlobalAcceleratorConn() + + acceleratorARN := d.Get("accelerator_arn").(string) + + log.Printf("[DEBUG] Deleting Global Accelerator Custom Routing Listener (%s)", d.Id()) + _, err := conn.DeleteCustomRoutingListenerWithContext(ctx, &globalaccelerator.DeleteCustomRoutingListenerInput{ + ListenerArn: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeListenerNotFoundException) { + return nil + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Global Accelerator Custom Routing Listener (%s): %s", d.Id(), err) + } + + // Deleting a listener triggers the accelerator to change status to InPending. + if _, err := waitCustomRoutingAcceleratorDeployed(ctx, conn, acceleratorARN, d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Global Accelerator Custom Routing Accelerator (%s) deployment: %s", acceleratorARN, err) + } + + return diags +} + +func FindCustomRoutingListenerByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.CustomRoutingListener, error) { + input := &globalaccelerator.DescribeCustomRoutingListenerInput{ + ListenerArn: aws.String(arn), + } + + return findCustomRoutingListener(ctx, conn, input) +} + +func findCustomRoutingListener(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeCustomRoutingListenerInput) (*globalaccelerator.CustomRoutingListener, error) { + output, err := conn.DescribeCustomRoutingListenerWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeListenerNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Listener == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Listener, nil +} diff --git a/internal/service/globalaccelerator/custom_routing_listener_test.go b/internal/service/globalaccelerator/custom_routing_listener_test.go new file mode 100644 index 000000000000..6efd6e2f1f0b --- /dev/null +++ b/internal/service/globalaccelerator/custom_routing_listener_test.go @@ -0,0 +1,148 @@ +package globalaccelerator_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/globalaccelerator" + 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" + tfglobalaccelerator "github.com/hashicorp/terraform-provider-aws/internal/service/globalaccelerator" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccGlobalAcceleratorCustomRoutingListener_basic(t *testing.T) { + ctx := acctest.Context(t) + var v globalaccelerator.CustomRoutingListener + resourceName := "aws_globalaccelerator_custom_routing_listener.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingListenerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingListenerConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCustomRoutingListenerExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "port_range.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "port_range.*", map[string]string{ + "from_port": "443", + "to_port": "443", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "port_range.*", map[string]string{ + "from_port": "10000", + "to_port": "30000", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGlobalAcceleratorCustomRoutingListener_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v globalaccelerator.CustomRoutingListener + resourceName := "aws_globalaccelerator_custom_routing_listener.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, globalaccelerator.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomRoutingListenerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCustomRoutingListenerConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomRoutingListenerExists(ctx, resourceName, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfglobalaccelerator.ResourceCustomRoutingListener(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCustomRoutingListenerExists(ctx context.Context, n string, v *globalaccelerator.CustomRoutingListener) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GlobalAcceleratorConn() + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Global Accelerator Custom Routing Listener ID is set") + } + + output, err := tfglobalaccelerator.FindCustomRoutingListenerByARN(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckCustomRoutingListenerDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GlobalAcceleratorConn() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_globalaccelerator_custom_routing_listener" { + continue + } + + _, err := tfglobalaccelerator.FindCustomRoutingListenerByARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Global Accelerator Custom Routing Listener %s still exists", rs.Primary.ID) + } + return nil + } +} + +func testAccCustomRoutingListenerConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_globalaccelerator_custom_routing_accelerator" "test" { + name = %[1]q +} + +resource "aws_globalaccelerator_custom_routing_listener" "test" { + accelerator_arn = aws_globalaccelerator_custom_routing_accelerator.test.id + + port_range { + from_port = 443 + to_port = 443 + } + + port_range { + from_port = 10000 + to_port = 30000 + } +} +`, rName) +} diff --git a/internal/service/globalaccelerator/endpoint_group.go b/internal/service/globalaccelerator/endpoint_group.go index 1531c73f97b0..12af5365673d 100644 --- a/internal/service/globalaccelerator/endpoint_group.go +++ b/internal/service/globalaccelerator/endpoint_group.go @@ -332,6 +332,35 @@ func resourceEndpointGroupDelete(ctx context.Context, d *schema.ResourceData, me return nil } +func FindEndpointGroupByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.EndpointGroup, error) { + input := &globalaccelerator.DescribeEndpointGroupInput{ + EndpointGroupArn: aws.String(arn), + } + + return findEndpointGroup(ctx, conn, input) +} + +func findEndpointGroup(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeEndpointGroupInput) (*globalaccelerator.EndpointGroup, error) { + output, err := conn.DescribeEndpointGroupWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeEndpointGroupNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.EndpointGroup == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.EndpointGroup, nil +} + func expandEndpointConfiguration(tfMap map[string]interface{}) *globalaccelerator.EndpointConfiguration { if tfMap == nil { return nil diff --git a/internal/service/globalaccelerator/find.go b/internal/service/globalaccelerator/find.go deleted file mode 100644 index f45b17263bd0..000000000000 --- a/internal/service/globalaccelerator/find.go +++ /dev/null @@ -1,127 +0,0 @@ -package globalaccelerator - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/globalaccelerator" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func FindAcceleratorByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.Accelerator, error) { - input := &globalaccelerator.DescribeAcceleratorInput{ - AcceleratorArn: aws.String(arn), - } - - return FindAccelerator(ctx, conn, input) -} - -func FindAccelerator(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeAcceleratorInput) (*globalaccelerator.Accelerator, error) { - output, err := conn.DescribeAcceleratorWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.Accelerator == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.Accelerator, nil -} - -func FindAcceleratorAttributesByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.AcceleratorAttributes, error) { - input := &globalaccelerator.DescribeAcceleratorAttributesInput{ - AcceleratorArn: aws.String(arn), - } - - return FindAcceleratorAttributes(ctx, conn, input) -} - -func FindAcceleratorAttributes(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeAcceleratorAttributesInput) (*globalaccelerator.AcceleratorAttributes, error) { - output, err := conn.DescribeAcceleratorAttributesWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeAcceleratorNotFoundException) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.AcceleratorAttributes == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.AcceleratorAttributes, nil -} - -func FindEndpointGroupByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.EndpointGroup, error) { - input := &globalaccelerator.DescribeEndpointGroupInput{ - EndpointGroupArn: aws.String(arn), - } - - return FindEndpointGroup(ctx, conn, input) -} - -func FindEndpointGroup(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeEndpointGroupInput) (*globalaccelerator.EndpointGroup, error) { - output, err := conn.DescribeEndpointGroupWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeEndpointGroupNotFoundException) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.EndpointGroup == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.EndpointGroup, nil -} - -func FindListenerByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.Listener, error) { - input := &globalaccelerator.DescribeListenerInput{ - ListenerArn: aws.String(arn), - } - - return FindListener(ctx, conn, input) -} - -func FindListener(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeListenerInput) (*globalaccelerator.Listener, error) { - output, err := conn.DescribeListenerWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeListenerNotFoundException) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.Listener == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.Listener, nil -} diff --git a/internal/service/globalaccelerator/listener.go b/internal/service/globalaccelerator/listener.go index c24626ea826f..9e873d0e7565 100644 --- a/internal/service/globalaccelerator/listener.go +++ b/internal/service/globalaccelerator/listener.go @@ -184,6 +184,35 @@ func resourceListenerDelete(ctx context.Context, d *schema.ResourceData, meta in return nil } +func FindListenerByARN(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) (*globalaccelerator.Listener, error) { + input := &globalaccelerator.DescribeListenerInput{ + ListenerArn: aws.String(arn), + } + + return findListener(ctx, conn, input) +} + +func findListener(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, input *globalaccelerator.DescribeListenerInput) (*globalaccelerator.Listener, error) { + output, err := conn.DescribeListenerWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, globalaccelerator.ErrCodeListenerNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Listener == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Listener, nil +} + func expandPortRange(tfMap map[string]interface{}) *globalaccelerator.PortRange { if tfMap == nil { return nil diff --git a/internal/service/globalaccelerator/service_package_gen.go b/internal/service/globalaccelerator/service_package_gen.go index b9335ba3bffb..f0e65fbb853d 100644 --- a/internal/service/globalaccelerator/service_package_gen.go +++ b/internal/service/globalaccelerator/service_package_gen.go @@ -24,7 +24,12 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { - return []*types.ServicePackageSDKDataSource{} + return []*types.ServicePackageSDKDataSource{ + { + Factory: DataSourceCustomRoutingAccelerator, + TypeName: "aws_globalaccelerator_custom_routing_accelerator", + }, + } } func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { @@ -33,6 +38,18 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceAccelerator, TypeName: "aws_globalaccelerator_accelerator", }, + { + Factory: ResourceCustomRoutingAccelerator, + TypeName: "aws_globalaccelerator_custom_routing_accelerator", + }, + { + Factory: ResourceCustomRoutingEndpointGroup, + TypeName: "aws_globalaccelerator_custom_routing_endpoint_group", + }, + { + Factory: ResourceCustomRoutingListener, + TypeName: "aws_globalaccelerator_custom_routing_listener", + }, { Factory: ResourceEndpointGroup, TypeName: "aws_globalaccelerator_endpoint_group", diff --git a/internal/service/globalaccelerator/status.go b/internal/service/globalaccelerator/status.go deleted file mode 100644 index cf5e8448f796..000000000000 --- a/internal/service/globalaccelerator/status.go +++ /dev/null @@ -1,26 +0,0 @@ -package globalaccelerator - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/globalaccelerator" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func statusAccelerator(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - accelerator, err := FindAcceleratorByARN(ctx, conn, arn) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return accelerator, aws.StringValue(accelerator.Status), nil - } -} diff --git a/internal/service/globalaccelerator/wait.go b/internal/service/globalaccelerator/wait.go deleted file mode 100644 index b05f01f7430e..000000000000 --- a/internal/service/globalaccelerator/wait.go +++ /dev/null @@ -1,26 +0,0 @@ -package globalaccelerator - -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go/service/globalaccelerator" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func waitAcceleratorDeployed(ctx context.Context, conn *globalaccelerator.GlobalAccelerator, arn string, timeout time.Duration) (*globalaccelerator.Accelerator, error) { //nolint:unparam - stateConf := &resource.StateChangeConf{ - Pending: []string{globalaccelerator.AcceleratorStatusInProgress}, - Target: []string{globalaccelerator.AcceleratorStatusDeployed}, - Refresh: statusAccelerator(ctx, conn, arn), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*globalaccelerator.Accelerator); ok { - return output, err - } - - return nil, err -} diff --git a/website/docs/d/globalaccelerator_custom_routing_accelerator.html.markdown b/website/docs/d/globalaccelerator_custom_routing_accelerator.html.markdown new file mode 100644 index 000000000000..ca118d94f812 --- /dev/null +++ b/website/docs/d/globalaccelerator_custom_routing_accelerator.html.markdown @@ -0,0 +1,44 @@ +--- +subcategory: "Global Accelerator" +layout: "aws" +page_title: "AWS: aws_globalaccelerator_custom_routing_accelerator" +description: |- + Provides a Global Accelerator custom routing accelerator data source. +--- + +# Data Source: aws_globalaccelerator_custom_routing_accelerator + +Provides information about a Global Accelerator custom routing accelerator. + +## Example Usage + +```terraform +variable "accelerator_arn" { + type = string + default = "" +} + +variable "accelerator_name" { + type = string + default = "" +} + +data "aws_globalaccelerator_custom_routing_accelerator" "example" { + arn = var.accelerator_arn + name = var.accelerator_name +} +``` + +## Argument Reference + +The following arguments are supported: + +* `arn` - (Optional) Full ARN of the custom routing accelerator. +* `name` - (Optional) Unique name of the custom routing accelerator. + +~> **NOTE:** When both `arn` and `name` are specified, `arn` takes precedence. + +## Attributes Reference + +See the [`aws_globalaccelerator_custom_routing_accelerator` resource](/docs/providers/aws/r/globalaccelerator_custom_routing_accelerator.html) for details on the +returned attributes - they are identical. diff --git a/website/docs/r/globalaccelerator_accelerator.html.markdown b/website/docs/r/globalaccelerator_accelerator.html.markdown index ece9cc30c0c1..ec68eb0cd8e2 100644 --- a/website/docs/r/globalaccelerator_accelerator.html.markdown +++ b/website/docs/r/globalaccelerator_accelerator.html.markdown @@ -50,6 +50,7 @@ In addition to all arguments above, the following attributes are exported: * `id` - The Amazon Resource Name (ARN) of the accelerator. * `dns_name` - The DNS name of the accelerator. For example, `a5d53ff5ee6bca4ce.awsglobalaccelerator.com`. +* `dual_stack_dns_name` - The Domain Name System (DNS) name that Global Accelerator creates that points to a dual-stack accelerator's four static IP addresses: two IPv4 addresses and two IPv6 addresses. For example, `a1234567890abcdef.dualstack.awsglobalaccelerator.com`. * `hosted_zone_id` -- The Global Accelerator Route 53 zone ID that can be used to route an [Alias Resource Record Set][1] to the Global Accelerator. This attribute is simply an alias for the zone ID `Z2BJ6XQ5FK7U4H`. diff --git a/website/docs/r/globalaccelerator_custom_routing_accelerator.html.markdown b/website/docs/r/globalaccelerator_custom_routing_accelerator.html.markdown new file mode 100644 index 000000000000..b58b5e75213d --- /dev/null +++ b/website/docs/r/globalaccelerator_custom_routing_accelerator.html.markdown @@ -0,0 +1,79 @@ +--- +subcategory: "Global Accelerator" +layout: "aws" +page_title: "AWS: aws_globalaccelerator_custom_routing_accelerator" +description: |- + Provides a Global Accelerator custom routing accelerator. +--- + +# Resource: aws_globalaccelerator_custom_routing_accelerator + +Creates a Global Accelerator custom routing accelerator. + +## Example Usage + +```terraform +resource "aws_globalaccelerator_custom_routing_accelerator" "example" { + name = "Example" + ip_address_type = "IPV4" + ip_addresses = ["1.2.3.4"] + enabled = true + + attributes { + flow_logs_enabled = true + flow_logs_s3_bucket = "example-bucket" + flow_logs_s3_prefix = "flow-logs/" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of a custom routing accelerator. +* `ip_address_type` - (Optional) The IP address type that an accelerator supports. For a custom routing accelerator, the value must be `"IPV4"`. +* `ip_addresses` - (Optional) The IP addresses to use for BYOIP accelerators. If not specified, the service assigns IP addresses. Valid values: 1 or 2 IPv4 addresses. +* `enabled` - (Optional) Indicates whether the accelerator is enabled. Defaults to `true`. Valid values: `true`, `false`. +* `attributes` - (Optional) The attributes of the accelerator. Fields documented below. +* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +**attributes** supports the following attributes: + +* `flow_logs_enabled` - (Optional) Indicates whether flow logs are enabled. Defaults to `false`. Valid values: `true`, `false`. +* `flow_logs_s3_bucket` - (Optional) The name of the Amazon S3 bucket for the flow logs. Required if `flow_logs_enabled` is `true`. +* `flow_logs_s3_prefix` - (Optional) The prefix for the location in the Amazon S3 bucket for the flow logs. Required if `flow_logs_enabled` is `true`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The Amazon Resource Name (ARN) of the custom accelerator. +* `dns_name` - The DNS name of the accelerator. For example, `a5d53ff5ee6bca4ce.awsglobalaccelerator.com`. +* `hosted_zone_id` -- The Global Accelerator Route 53 zone ID that can be used to + route an [Alias Resource Record Set][1] to the Global Accelerator. This attribute + is simply an alias for the zone ID `Z2BJ6XQ5FK7U4H`. +* `ip_sets` - IP address set associated with the accelerator. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +**ip_sets** exports the following attributes: + +* `ip_addresses` - A list of IP addresses in the IP address set. +* `ip_family` - The type of IP addresses included in this IP set. + +[1]: https://docs.aws.amazon.com/Route53/latest/APIReference/API_AliasTarget.html + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) + +## Import + +Global Accelerator custom routing accelerators can be imported using the `arn`, e.g., + +``` +$ terraform import aws_globalaccelerator_custom_routing_accelerator.example arn:aws:globalaccelerator::111111111111:accelerator/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +``` diff --git a/website/docs/r/globalaccelerator_custom_routing_endpoint_group.html.markdown b/website/docs/r/globalaccelerator_custom_routing_endpoint_group.html.markdown new file mode 100644 index 000000000000..583578df5acc --- /dev/null +++ b/website/docs/r/globalaccelerator_custom_routing_endpoint_group.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "Global Accelerator" +layout: "aws" +page_title: "AWS: aws_globalaccelerator_custom_routing_endpoint_group" +description: |- + Provides a Global Accelerator custom routing endpoint group. +--- + +# Resource: aws_globalaccelerator_custom_routing_endpoint_group + +Provides a Global Accelerator custom routing endpoint group. + +## Example Usage + +```terraform +resource "aws_globalaccelerator_custom_routing_endpoint_group" "example" { + listener_arn = aws_globalaccelerator_custom_routing_listener.example.id + + destination_configuration { + from_port = 80 + to_port = 8080 + protocols = ["TCP"] + } + + endpoint_configuration { + endpoint_id = aws_subnet.example.id + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `listener_arn` - (Required) The Amazon Resource Name (ARN) of the custom routing listener. +* `destination_configuration` - (Required) The port ranges and protocols for all endpoints in a custom routing endpoint group to accept client traffic on. Fields documented below. +* `endpoint_configuration` - (Optional) The list of endpoint objects. Fields documented below. +* `endpoint_group_region` (Optional) - The name of the AWS Region where the custom routing endpoint group is located. + +**destination_configuration** supports the following attributes: + +* `from_port` - (Required) The first port, inclusive, in the range of ports for the endpoint group that is associated with a custom routing accelerator. +* `protocols` - (Required) The protocol for the endpoint group that is associated with a custom routing accelerator. The protocol can be either `"TCP"` or `"UDP"`. +* `to_port` - (Required) The last port, inclusive, in the range of ports for the endpoint group that is associated with a custom routing accelerator. + +**endpoint_configuration** supports the following attributes: + +* `endpoint_id` - (Optional) An ID for the endpoint. For custom routing accelerators, this is the virtual private cloud (VPC) subnet ID. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The Amazon Resource Name (ARN) of the custom routing endpoint group. +* `arn` - The Amazon Resource Name (ARN) of the custom routing endpoint group. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +Global Accelerator custom routing endpoint groups can be imported using the `id`, e.g., + +``` +$ terraform import aws_globalaccelerator_custom_routing_endpoint_group.example arn:aws:globalaccelerator::111111111111:accelerator/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/listener/xxxxxxx/endpoint-group/xxxxxxxx +``` diff --git a/website/docs/r/globalaccelerator_custom_routing_listener.html.markdown b/website/docs/r/globalaccelerator_custom_routing_listener.html.markdown new file mode 100644 index 000000000000..9da500402a00 --- /dev/null +++ b/website/docs/r/globalaccelerator_custom_routing_listener.html.markdown @@ -0,0 +1,70 @@ +--- +subcategory: "Global Accelerator" +layout: "aws" +page_title: "AWS: aws_globalaccelerator_custom_routing_listener" +description: |- + Provides a Global Accelerator custom routing listener. +--- + +# Resource: aws_globalaccelerator_custom_routing_listener + +Provides a Global Accelerator custom routing listener. + +## Example Usage + +```terraform +resource "aws_globalaccelerator_custom_routing_accelerator" "example" { + name = "Example" + ip_address_type = "IPV4" + enabled = true + + attributes { + flow_logs_enabled = true + flow_logs_s3_bucket = "example-bucket" + flow_logs_s3_prefix = "flow-logs/" + } +} + +resource "aws_globalaccelerator_custom_routing_listener" "example" { + accelerator_arn = aws_globalaccelerator_custom_routing_accelerator.example.id + + port_range { + from_port = 80 + to_port = 80 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `accelerator_arn` - (Required) The Amazon Resource Name (ARN) of a custom routing accelerator. +* `port_range` - (Optional) The list of port ranges for the connections from clients to the accelerator. Fields documented below. + +**port_range** supports the following attributes: + +* `from_port` - (Optional) The first port in the range of ports, inclusive. +* `to_port` - (Optional) The last port in the range of ports, inclusive. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The Amazon Resource Name (ARN) of the custom routing listener. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +Global Accelerator custom routing listeners can be imported using the `id`, e.g., + +``` +$ terraform import aws_globalaccelerator_custom_routing_listener.example arn:aws:globalaccelerator::111111111111:accelerator/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/listener/xxxxxxxx +```