diff --git a/.changelog/40364.txt b/.changelog/40364.txt new file mode 100644 index 000000000000..98d547e5c8dd --- /dev/null +++ b/.changelog/40364.txt @@ -0,0 +1,11 @@ +```release-note:enhancement +resource/aws_api_gateway_domain_name: Add `policy` argument and `domain_name_id` attribute +``` + +```release-note:enhancement +resource/aws_api_gateway_domain_name: Support `PRIVATE` as a valid value for `endpoint_configuration.types` argument, enabling custom domain name support for private REST API endpoints +``` + +```release-note:enhancement +data-source/aws_api_gateway_domain_name: Add `policy` and `domain_name_id` attributes +``` \ No newline at end of file diff --git a/internal/service/apigateway/domain_name.go b/internal/service/apigateway/domain_name.go index de5abb04b9f3..2ba789aa05df 100644 --- a/internal/service/apigateway/domain_name.go +++ b/internal/service/apigateway/domain_name.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "log" + "strings" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -16,11 +17,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" "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" @@ -97,6 +100,10 @@ func resourceDomainName() *schema.Resource { Required: true, ForceNew: true, }, + "domain_name_id": { + Type: schema.TypeString, + Computed: true, + }, "endpoint_configuration": { Type: schema.TypeList, Optional: true, @@ -112,8 +119,8 @@ func resourceDomainName() *schema.Resource { // BadRequestException: Cannot create an api with multiple Endpoint Types MaxItems: 1, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(enum.Slice(types.EndpointTypeEdge, types.EndpointTypeRegional), false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[types.EndpointType](), }, }, }, @@ -142,6 +149,17 @@ func resourceDomainName() *schema.Resource { Computed: true, ValidateFunc: verify.ValidARN, }, + names.AttrPolicy: { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + DiffSuppressOnRefresh: true, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + }, "regional_certificate_arn": { Type: schema.TypeString, Optional: true, @@ -213,6 +231,10 @@ func resourceDomainNameCreate(ctx context.Context, d *schema.ResourceData, meta input.OwnershipVerificationCertificateArn = aws.String(v.(string)) } + if v, ok := d.GetOk(names.AttrPolicy); ok { + input.Policy = aws.String(v.(string)) + } + if v, ok := d.GetOk("regional_certificate_arn"); ok { input.RegionalCertificateArn = aws.String(v.(string)) } @@ -231,7 +253,7 @@ func resourceDomainNameCreate(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "creating API Gateway Domain Name (%s): %s", domainName, err) } - d.SetId(aws.ToString(output.DomainName)) + d.SetId(domainNameCreateResourceID(aws.ToString(output.DomainName), aws.ToString(output.DomainNameId))) return append(diags, resourceDomainNameRead(ctx, d, meta)...) } @@ -240,7 +262,12 @@ func resourceDomainNameRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayClient(ctx) - domainName, err := findDomainByName(ctx, conn, d.Id()) + domainName, domainNameID, err := domainNameParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + output, err := findDomainNameByTwoPartKey(ctx, conn, domainName, domainNameID) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] API Gateway Domain Name (%s) not found, removing from state", d.Id()) @@ -253,30 +280,32 @@ func resourceDomainNameRead(ctx context.Context, d *schema.ResourceData, meta in } d.Set(names.AttrARN, domainNameARN(ctx, meta.(*conns.AWSClient), d.Id())) - d.Set(names.AttrCertificateARN, domainName.CertificateArn) - d.Set("certificate_name", domainName.CertificateName) - if domainName.CertificateUploadDate != nil { - d.Set("certificate_upload_date", domainName.CertificateUploadDate.Format(time.RFC3339)) + d.Set(names.AttrCertificateARN, output.CertificateArn) + d.Set("certificate_name", output.CertificateName) + if output.CertificateUploadDate != nil { + d.Set("certificate_upload_date", output.CertificateUploadDate.Format(time.RFC3339)) } else { d.Set("certificate_upload_date", nil) } - d.Set("cloudfront_domain_name", domainName.DistributionDomainName) + d.Set("cloudfront_domain_name", output.DistributionDomainName) d.Set("cloudfront_zone_id", meta.(*conns.AWSClient).CloudFrontDistributionHostedZoneID(ctx)) - d.Set(names.AttrDomainName, domainName.DomainName) - if err := d.Set("endpoint_configuration", flattenEndpointConfiguration(domainName.EndpointConfiguration)); err != nil { + d.Set(names.AttrDomainName, output.DomainName) + d.Set("domain_name_id", output.DomainNameId) + if err := d.Set("endpoint_configuration", flattenEndpointConfiguration(output.EndpointConfiguration)); err != nil { return sdkdiag.AppendErrorf(diags, "setting endpoint_configuration: %s", err) } - if err = d.Set("mutual_tls_authentication", flattenMutualTLSAuthentication(domainName.MutualTlsAuthentication)); err != nil { + if err = d.Set("mutual_tls_authentication", flattenMutualTLSAuthentication(output.MutualTlsAuthentication)); err != nil { return sdkdiag.AppendErrorf(diags, "setting mutual_tls_authentication: %s", err) } - d.Set("ownership_verification_certificate_arn", domainName.OwnershipVerificationCertificateArn) - d.Set("regional_certificate_arn", domainName.RegionalCertificateArn) - d.Set("regional_certificate_name", domainName.RegionalCertificateName) - d.Set("regional_domain_name", domainName.RegionalDomainName) - d.Set("regional_zone_id", domainName.RegionalHostedZoneId) - d.Set("security_policy", domainName.SecurityPolicy) + d.Set("ownership_verification_certificate_arn", output.OwnershipVerificationCertificateArn) + d.Set(names.AttrPolicy, output.Policy) + d.Set("regional_certificate_arn", output.RegionalCertificateArn) + d.Set("regional_certificate_name", output.RegionalCertificateName) + d.Set("regional_domain_name", output.RegionalDomainName) + d.Set("regional_zone_id", output.RegionalHostedZoneId) + d.Set("security_policy", output.SecurityPolicy) - setTagsOut(ctx, domainName.Tags) + setTagsOut(ctx, output.Tags) return diags } @@ -285,6 +314,11 @@ func resourceDomainNameUpdate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayClient(ctx) + domainName, domainNameID, err := domainNameParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) { var operations []types.PatchOperation @@ -308,12 +342,12 @@ func resourceDomainNameUpdate(ctx context.Context, d *schema.ResourceData, meta // The domain name must have an endpoint type. // If attempting to remove the configuration, do nothing. if v, ok := d.GetOk("endpoint_configuration"); ok && len(v.([]interface{})) > 0 { - m := v.([]interface{})[0].(map[string]interface{}) + tfMap := v.([]interface{})[0].(map[string]interface{}) operations = append(operations, types.PatchOperation{ Op: types.OpReplace, Path: aws.String("/endpointConfiguration/types/0"), - Value: aws.String(m["types"].([]interface{})[0].(string)), + Value: aws.String(tfMap["types"].([]interface{})[0].(string)), }) } } @@ -355,6 +389,14 @@ func resourceDomainNameUpdate(ctx context.Context, d *schema.ResourceData, meta }) } + if d.HasChange(names.AttrPolicy) { + operations = append(operations, types.PatchOperation{ + Op: types.OpReplace, + Path: aws.String("/policy"), + Value: aws.String(d.Get(names.AttrPolicy).(string)), + }) + } + if d.HasChange("regional_certificate_arn") { operations = append(operations, types.PatchOperation{ Op: types.OpReplace, @@ -379,16 +421,21 @@ func resourceDomainNameUpdate(ctx context.Context, d *schema.ResourceData, meta }) } - _, err := conn.UpdateDomainName(ctx, &apigateway.UpdateDomainNameInput{ - DomainName: aws.String(d.Id()), + input := &apigateway.UpdateDomainNameInput{ + DomainName: aws.String(domainName), PatchOperations: operations, - }) + } + if domainNameID != "" { + input.DomainNameId = aws.String(domainNameID) + } + + _, err := conn.UpdateDomainName(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating API Gateway Domain Name (%s): %s", d.Id(), err) } - if _, err := waitDomainNameUpdated(ctx, conn, d.Id()); err != nil { + if _, err := waitDomainNameUpdated(ctx, conn, d.Id(), domainNameID); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for API Gateway Domain Name (%s) update: %s", d.Id(), err) } } @@ -400,10 +447,20 @@ func resourceDomainNameDelete(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayClient(ctx) + domainName, domainNameID, err := domainNameParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + input := &apigateway.DeleteDomainNameInput{ + DomainName: aws.String(domainName), + } + if domainNameID != "" { + input.DomainNameId = aws.String(domainNameID) + } + log.Printf("[DEBUG] Deleting API Gateway Domain Name: %s", d.Id()) - _, err := conn.DeleteDomainName(ctx, &apigateway.DeleteDomainNameInput{ - DomainName: aws.String(d.Id()), - }) + _, err = conn.DeleteDomainName(ctx, input) if errs.IsA[*types.NotFoundException](err) { return diags @@ -416,10 +473,43 @@ func resourceDomainNameDelete(ctx context.Context, d *schema.ResourceData, meta return diags } -func findDomainByName(ctx context.Context, conn *apigateway.Client, domainName string) (*apigateway.GetDomainNameOutput, error) { +const domainNameResourceIDSeparator = flex.ResourceIdSeparator + +func domainNameCreateResourceID(domainName, domainNameID string) string { + var id string + + if domainNameID == "" { + id = domainName + } else { + parts := []string{domainName, domainNameID} + id = strings.Join(parts, domainNameResourceIDSeparator) + } + + return id +} + +func domainNameParseResourceID(id string) (string, string, error) { + switch parts := strings.SplitN(id, domainNameResourceIDSeparator, 2); len(parts) { + case 1: + if domainName := parts[0]; domainName != "" { + return domainName, "", nil + } + case 2: + if domainName, domainNameID := parts[0], parts[1]; domainName != "" && domainNameID != "" { + return domainName, domainNameID, nil + } + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected DOMAIN-NAME or DOMAIN-NAME%[2]sDOMAIN-NAME-ID", id, domainNameResourceIDSeparator) +} + +func findDomainNameByTwoPartKey(ctx context.Context, conn *apigateway.Client, domainName, domainNameID string) (*apigateway.GetDomainNameOutput, error) { input := &apigateway.GetDomainNameInput{ DomainName: aws.String(domainName), } + if domainNameID != "" { + input.DomainNameId = aws.String(domainNameID) + } output, err := conn.GetDomainName(ctx, input) @@ -441,13 +531,14 @@ func findDomainByName(ctx context.Context, conn *apigateway.Client, domainName s return output, nil } -func statusDomainName(ctx context.Context, conn *apigateway.Client, domainName string) retry.StateRefreshFunc { +func statusDomainName(ctx context.Context, conn *apigateway.Client, domainName, domainNameID string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := findDomainByName(ctx, conn, domainName) + output, err := findDomainNameByTwoPartKey(ctx, conn, domainName, domainNameID) if tfresource.NotFound(err) { return nil, "", nil } + if err != nil { return nil, "", err } @@ -456,14 +547,14 @@ func statusDomainName(ctx context.Context, conn *apigateway.Client, domainName s } } -func waitDomainNameUpdated(ctx context.Context, conn *apigateway.Client, domainName string) (*types.DomainName, error) { +func waitDomainNameUpdated(ctx context.Context, conn *apigateway.Client, domainName, domainNameID string) (*types.DomainName, error) { const ( timeout = 15 * time.Minute ) stateConf := &retry.StateChangeConf{ Pending: enum.Slice(types.DomainNameStatusUpdating), Target: enum.Slice(types.DomainNameStatusAvailable), - Refresh: statusDomainName(ctx, conn, domainName), + Refresh: statusDomainName(ctx, conn, domainName, domainNameID), Timeout: timeout, Delay: 1 * time.Minute, MinTimeout: 10 * time.Second, diff --git a/internal/service/apigateway/domain_name_data_source.go b/internal/service/apigateway/domain_name_data_source.go index 2bcc319df01f..e78e68a54504 100644 --- a/internal/service/apigateway/domain_name_data_source.go +++ b/internal/service/apigateway/domain_name_data_source.go @@ -54,6 +54,11 @@ func dataSourceDomainName() *schema.Resource { Type: schema.TypeString, Required: true, }, + "domain_name_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "endpoint_configuration": { Type: schema.TypeList, Computed: true, @@ -67,6 +72,10 @@ func dataSourceDomainName() *schema.Resource { }, }, }, + names.AttrPolicy: { + Type: schema.TypeString, + Computed: true, + }, "regional_certificate_arn": { Type: schema.TypeString, Computed: true, @@ -96,11 +105,15 @@ func dataSourceDomainNameRead(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayClient(ctx) + var domainNameID string domainName := d.Get(names.AttrDomainName).(string) - output, err := findDomainByName(ctx, conn, domainName) + if v, ok := d.GetOk("domain_name_id"); ok { + domainNameID = v.(string) + } + output, err := findDomainNameByTwoPartKey(ctx, conn, domainName, domainNameID) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading API Gateway Domain Name (%s): %s", domainName, err) + return sdkdiag.AppendErrorf(diags, "reading API Gateway Domain Name (%s): %s", domainNameCreateResourceID(domainName, domainNameID), err) } d.SetId(aws.ToString(output.DomainName)) @@ -113,9 +126,11 @@ func dataSourceDomainNameRead(ctx context.Context, d *schema.ResourceData, meta d.Set("cloudfront_domain_name", output.DistributionDomainName) d.Set("cloudfront_zone_id", meta.(*conns.AWSClient).CloudFrontDistributionHostedZoneID(ctx)) d.Set(names.AttrDomainName, output.DomainName) + d.Set("domain_name_id", output.DomainNameId) if err := d.Set("endpoint_configuration", flattenEndpointConfiguration(output.EndpointConfiguration)); err != nil { return sdkdiag.AppendErrorf(diags, "setting endpoint_configuration: %s", err) } + d.Set(names.AttrPolicy, output.Policy) d.Set("regional_certificate_arn", output.RegionalCertificateArn) d.Set("regional_certificate_name", output.RegionalCertificateName) d.Set("regional_domain_name", output.RegionalDomainName) diff --git a/internal/service/apigateway/domain_name_data_source_test.go b/internal/service/apigateway/domain_name_data_source_test.go index 000c3e08349c..f594d37c2c45 100644 --- a/internal/service/apigateway/domain_name_data_source_test.go +++ b/internal/service/apigateway/domain_name_data_source_test.go @@ -36,6 +36,7 @@ func TestAccAPIGatewayDomainNameDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "cloudfront_domain_name", dataSourceName, "cloudfront_domain_name"), resource.TestCheckResourceAttrPair(resourceName, "cloudfront_zone_id", dataSourceName, "cloudfront_zone_id"), resource.TestCheckResourceAttrPair(resourceName, names.AttrDomainName, dataSourceName, names.AttrDomainName), + resource.TestCheckResourceAttrPair(resourceName, "domain_name_id", dataSourceName, "domain_name_id"), resource.TestCheckResourceAttrPair(resourceName, "endpoint_configuration.#", dataSourceName, "endpoint_configuration.#"), resource.TestCheckResourceAttrPair(resourceName, "regional_certificate_arn", dataSourceName, "regional_certificate_arn"), resource.TestCheckResourceAttrPair(resourceName, "regional_certificate_name", dataSourceName, "regional_certificate_name"), diff --git a/internal/service/apigateway/domain_name_test.go b/internal/service/apigateway/domain_name_test.go index eefc9073fd32..0cb3f47b4c61 100644 --- a/internal/service/apigateway/domain_name_test.go +++ b/internal/service/apigateway/domain_name_test.go @@ -45,7 +45,7 @@ func TestAccAPIGatewayDomainName_certificateARN(t *testing.T) { testAccCheckResourceAttrRegionalARNEdgeDomainName(resourceName, names.AttrARN, "apigateway", domain), resource.TestCheckResourceAttrPair(resourceName, names.AttrCertificateARN, acmCertificateResourceName, names.AttrARN), resource.TestMatchResourceAttr(resourceName, "cloudfront_domain_name", regexache.MustCompile(`[0-9a-z]+.cloudfront.net`)), - resource.TestCheckResourceAttr(resourceName, "cloudfront_zone_id", "Z2FDTNDATAQYW2"), + resource.TestCheckResourceAttr(resourceName, "cloudfront_zone_id", acctest.Provider.Meta().(*conns.AWSClient).CloudFrontDistributionHostedZoneID(ctx)), resource.TestCheckResourceAttrPair(resourceName, names.AttrDomainName, acmCertificateResourceName, names.AttrDomainName), ), }, @@ -107,7 +107,7 @@ func TestAccAPIGatewayDomainName_certificateName(t *testing.T) { acctest.MatchResourceAttrRegionalARNNoAccount(resourceName, names.AttrARN, "apigateway", regexache.MustCompile(`/domainnames/.+$`)), resource.TestCheckResourceAttr(resourceName, "certificate_name", "tf-acc-apigateway-domain-name"), resource.TestCheckResourceAttrSet(resourceName, "cloudfront_domain_name"), - resource.TestCheckResourceAttr(resourceName, "cloudfront_zone_id", "Z2FDTNDATAQYW2"), + resource.TestCheckResourceAttr(resourceName, "cloudfront_zone_id", acctest.Provider.Meta().(*conns.AWSClient).CloudFrontDistributionHostedZoneID(ctx)), resource.TestCheckResourceAttr(resourceName, names.AttrDomainName, domainName), resource.TestCheckResourceAttrSet(resourceName, "certificate_upload_date"), ), @@ -142,6 +142,7 @@ func TestAccAPIGatewayDomainName_regionalCertificateARN(t *testing.T) { testAccCheckDomainNameExists(ctx, resourceName, &domainName), testAccCheckResourceAttrRegionalARNRegionalDomainName(resourceName, names.AttrARN, "apigateway", rName), resource.TestCheckResourceAttr(resourceName, names.AttrDomainName, rName), + resource.TestCheckResourceAttr(resourceName, "domain_name_id", ""), acctest.MatchResourceAttrRegionalHostname(resourceName, "regional_domain_name", "execute-api", regexache.MustCompile(`d-[0-9a-z]+`)), resource.TestMatchResourceAttr(resourceName, "regional_zone_id", regexache.MustCompile(`^Z`)), ), @@ -229,6 +230,67 @@ func TestAccAPIGatewayDomainName_securityPolicy(t *testing.T) { }) } +func TestAccAPIGatewayDomainName_private(t *testing.T) { + ctx := acctest.Context(t) + var domainName apigateway.GetDomainNameOutput + resourceName := "aws_api_gateway_domain_name.test" + rName := acctest.RandomSubdomain() + key := acctest.TLSRSAPrivateKeyPEM(t, 2048) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, rName) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.APIGatewayServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainNameDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainNameConfig_private(rName, key, certificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainNameExists(ctx, resourceName, &domainName), + resource.TestCheckResourceAttrSet(resourceName, "domain_name_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAPIGatewayDomainName_privatePolicy(t *testing.T) { + ctx := acctest.Context(t) + var domainName apigateway.GetDomainNameOutput + resourceName := "aws_api_gateway_domain_name.test" + rName := acctest.RandomSubdomain() + key := acctest.TLSRSAPrivateKeyPEM(t, 2048) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, rName) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.APIGatewayServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainNameDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainNameConfig_privatePolicy(rName, key, certificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainNameExists(ctx, resourceName, &domainName), + resource.TestCheckResourceAttrSet(resourceName, "domain_name_id"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAPIGatewayDomainName_disappears(t *testing.T) { ctx := acctest.Context(t) var domainName apigateway.GetDomainNameOutput @@ -350,6 +412,41 @@ func TestAccAPIGatewayDomainName_MutualTLSAuthentication_ownership(t *testing.T) }) } +func TestAccAPIGatewayDomainName_updateIDFormat(t *testing.T) { + ctx := acctest.Context(t) + var domainName apigateway.GetDomainNameOutput + resourceName := "aws_api_gateway_domain_name.test" + rName := acctest.RandomSubdomain() + key := acctest.TLSRSAPrivateKeyPEM(t, 2048) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, rName) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.APIGatewayServiceID), + CheckDestroy: testAccCheckDomainNameDestroy(ctx), + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: "5.80.0", + }, + }, + Config: testAccDomainNameConfig_regionalCertificateARN(rName, key, certificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainNameExists(ctx, resourceName, &domainName), + resource.TestCheckNoResourceAttr(resourceName, "domain_name_id"), + ), + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: testAccDomainNameConfig_regionalCertificateARN(rName, key, certificate), + PlanOnly: true, + }, + }, + }) +} + func testAccCheckDomainNameExists(ctx context.Context, n string, v *apigateway.GetDomainNameOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -359,7 +456,7 @@ func testAccCheckDomainNameExists(ctx context.Context, n string, v *apigateway.G conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayClient(ctx) - output, err := tfapigateway.FindDomainByName(ctx, conn, rs.Primary.ID) + output, err := tfapigateway.FindDomainNameByTwoPartKey(ctx, conn, rs.Primary.Attributes[names.AttrDomainName], rs.Primary.Attributes["domain_name_id"]) if err != nil { return err @@ -380,7 +477,7 @@ func testAccCheckDomainNameDestroy(ctx context.Context) resource.TestCheckFunc { continue } - _, err := tfapigateway.FindDomainByName(ctx, conn, rs.Primary.ID) + _, err := tfapigateway.FindDomainNameByTwoPartKey(ctx, conn, rs.Primary.Attributes[names.AttrDomainName], rs.Primary.Attributes["domain_name_id"]) if tfresource.NotFound(err) { continue @@ -531,6 +628,54 @@ resource "aws_api_gateway_domain_name" "test" { `, domainName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key), securityPolicy) } +func testAccDomainNameConfig_private(domainName, key, certificate string) string { + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + certificate_body = "%[2]s" + private_key = "%[3]s" +} + +resource "aws_api_gateway_domain_name" "test" { + domain_name = %[1]q + certificate_arn = aws_acm_certificate.test.arn + + endpoint_configuration { + types = ["PRIVATE"] + } +} +`, domainName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)) +} + +func testAccDomainNameConfig_privatePolicy(domainName, key, certificate string) string { + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + certificate_body = "%[2]s" + private_key = "%[3]s" +} + +resource "aws_api_gateway_domain_name" "test" { + domain_name = %[1]q + certificate_arn = aws_acm_certificate.test.arn + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = { + AWS = "*" + } + Action = "execute-api:Invoke" + Resource = "*" + }] + }) + + endpoint_configuration { + types = ["PRIVATE"] + } +} +`, domainName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)) +} + func testAccDomainNameConfig_mutualTLSAuthentication(rName, rootDomain, domain string) string { return acctest.ConfigCompose( testAccDomainNameConfig_basePublicCert(rootDomain, domain), diff --git a/internal/service/apigateway/exports_test.go b/internal/service/apigateway/exports_test.go index 8933b86d5185..db693178cfd2 100644 --- a/internal/service/apigateway/exports_test.go +++ b/internal/service/apigateway/exports_test.go @@ -38,7 +38,7 @@ var ( FindDeploymentByTwoPartKey = findDeploymentByTwoPartKey FindDocumentationPartByTwoPartKey = findDocumentationPartByTwoPartKey FindDocumentationVersionByTwoPartKey = findDocumentationVersionByTwoPartKey - FindDomainByName = findDomainByName + FindDomainNameByTwoPartKey = findDomainNameByTwoPartKey FindGatewayResponseByTwoPartKey = findGatewayResponseByTwoPartKey FindIntegrationByThreePartKey = findIntegrationByThreePartKey FindIntegrationResponseByFourPartKey = findIntegrationResponseByFourPartKey diff --git a/website/docs/d/api_gateway_domain_name.html.markdown b/website/docs/d/api_gateway_domain_name.html.markdown index a02d43931824..a151733180b0 100644 --- a/website/docs/d/api_gateway_domain_name.html.markdown +++ b/website/docs/d/api_gateway_domain_name.html.markdown @@ -21,6 +21,7 @@ data "aws_api_gateway_domain_name" "example" { ## Argument Reference * `domain_name` - (Required) Fully-qualified domain name to look up. If no domain name is found, an error will be returned. +* `domain_name_id` - (Optional) The identifier for the domain name resource. Supported only for private custom domain names. ## Attribute Reference @@ -34,6 +35,7 @@ This data source exports the following attributes in addition to the arguments a * `cloudfront_zone_id` - For convenience, the hosted zone ID (`Z2FDTNDATAQYW2`) that can be used to create a Route53 alias record for the distribution. * `endpoint_configuration` - List of objects with the endpoint configuration of this domain name. * `types` - List of endpoint types. +* `policy` - A stringified JSON policy document that applies to the execute-api service for this DomainName regardless of the caller and Method configuration. Supported only for private custom domain names. * `regional_certificate_arn` - ARN for an AWS-managed certificate that is used for validating the regional domain name. * `regional_certificate_name` - User-friendly name of the certificate that is used by regional endpoint for this domain name. * `regional_domain_name` - Hostname for the custom domain's regional endpoint. diff --git a/website/docs/r/api_gateway_domain_name.html.markdown b/website/docs/r/api_gateway_domain_name.html.markdown index b74e6d4721a8..2b6ec8a9fb0d 100644 --- a/website/docs/r/api_gateway_domain_name.html.markdown +++ b/website/docs/r/api_gateway_domain_name.html.markdown @@ -157,6 +157,7 @@ This resource supports the following arguments: * `domain_name` - (Required) Fully-qualified domain name to register. * `endpoint_configuration` - (Optional) Configuration block defining API endpoint information including type. See below. * `mutual_tls_authentication` - (Optional) Mutual TLS authentication configuration for the domain name. See below. +* `policy` - (Optional) A stringified JSON policy document that applies to the execute-api service for this DomainName regardless of the caller and Method configuration. Supported only for private custom domain names. * `ownership_verification_certificate_arn` - (Optional) ARN of the AWS-issued certificate used to validate custom domain ownership (when `certificate_arn` is issued via an ACM Private CA or `mutual_tls_authentication` is configured with an ACM-imported certificate.) * `security_policy` - (Optional) Transport Layer Security (TLS) version + cipher suite for this DomainName. Valid values are `TLS_1_0` and `TLS_1_2`. Must be configured to perform drift detection. * `tags` - (Optional) Key-value map of resource tags. 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. @@ -176,7 +177,7 @@ When uploading a certificate, the following arguments are supported: ### endpoint_configuration -* `types` - (Required) List of endpoint types. This resource currently only supports managing a single value. Valid values: `EDGE` or `REGIONAL`. If unspecified, defaults to `EDGE`. Must be declared as `REGIONAL` in non-Commercial partitions. Refer to the [documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/create-regional-api.html) for more information on the difference between edge-optimized and regional APIs. +* `types` - (Required) A list of endpoint types of an API or its custom domain name. For an edge-optimized API and its custom domain name, the endpoint type is `EDGE`. For a regional API and its custom domain name, the endpoint type is `REGIONAL`. For a private API, the endpoint type is `PRIVATE`. ### mutual_tls_authentication @@ -191,6 +192,7 @@ This resource exports the following attributes in addition to the arguments abov * `certificate_upload_date` - Upload date associated with the domain certificate. * `cloudfront_domain_name` - Hostname created by Cloudfront to represent the distribution that implements this domain name mapping. * `cloudfront_zone_id` - For convenience, the hosted zone ID (`Z2FDTNDATAQYW2`) that can be used to create a Route53 alias record for the distribution. +* `domain_name_id` - The identifier for the domain name resource. Supported only for private custom domain names. * `id` - Internal identifier assigned to this domain name by API Gateway. * `regional_domain_name` - Hostname for the custom domain's regional endpoint. * `regional_zone_id` - Hosted zone ID that can be used to create a Route53 alias record for the regional endpoint.