Skip to content

Commit

Permalink
Merge pull request #40364 from DrFaust92/apig-domain-name-private
Browse files Browse the repository at this point in the history
r/apigateway_domain_name - support for private endpoints
  • Loading branch information
ewbankkit authored Dec 4, 2024
2 parents 42a5eed + 179d02a commit 7dbb951
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 41 deletions.
11 changes: 11 additions & 0 deletions .changelog/40364.txt
Original file line number Diff line number Diff line change
@@ -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
```
157 changes: 124 additions & 33 deletions internal/service/apigateway/domain_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"log"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
Expand All @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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](),
},
},
},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
}
Expand All @@ -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)...)
}
Expand All @@ -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())
Expand All @@ -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
}
Expand All @@ -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

Expand All @@ -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)),
})
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
}
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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
}
Expand All @@ -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,
Expand Down
19 changes: 17 additions & 2 deletions internal/service/apigateway/domain_name_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -67,6 +72,10 @@ func dataSourceDomainName() *schema.Resource {
},
},
},
names.AttrPolicy: {
Type: schema.TypeString,
Computed: true,
},
"regional_certificate_arn": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Loading

0 comments on commit 7dbb951

Please sign in to comment.