Skip to content

Commit

Permalink
docs/service/apigateway: aws_api_gateway_deployment usage overhaul to…
Browse files Browse the repository at this point in the history
… discourage stage_name and further encourage create_before_destroy (#17230)

* docs/service/apigateway: aws_api_gateway_deployment usage overhaul to discourage stage_name and further encourage create_before_destroy

Reference: #11344

Adds new end-to-end example of an OpenAPI REST API and also encourages the usage of OpenAPI specifications for configuring the REST API. Support for the other API Gateway resources is not going anywhere, but the dependency management aspect of deployments can be more difficult in that model and it is much easier to discover the API Gateway resources over the OpenAPI support.

In the future, it may be worth considering deprecating the `stage_name` and friends arguments since having a Terraform resource manage two remote resources is an anti-pattern and not well supported.

Output from example:

```console
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_acm_certificate.example will be created
  + resource "aws_acm_certificate" "example" {
      + arn                       = (known after apply)
      + certificate_body          = (known after apply)
      + domain_name               = (known after apply)
      + domain_validation_options = (known after apply)
      + id                        = (known after apply)
      + private_key               = (sensitive value)
      + status                    = (known after apply)
      + subject_alternative_names = (known after apply)
      + validation_emails         = (known after apply)
      + validation_method         = (known after apply)
    }

  # aws_api_gateway_base_path_mapping.example will be created
  + resource "aws_api_gateway_base_path_mapping" "example" {
      + api_id      = (known after apply)
      + domain_name = (known after apply)
      + id          = (known after apply)
      + stage_name  = "example"
    }

  # aws_api_gateway_deployment.example will be created
  + resource "aws_api_gateway_deployment" "example" {
      + created_date  = (known after apply)
      + execution_arn = (known after apply)
      + id            = (known after apply)
      + invoke_url    = (known after apply)
      + rest_api_id   = (known after apply)
      + triggers      = {
          + "redeployment" = "e042aae1faf8de8d7c7c98c063a986025f058c69"
        }
    }

  # aws_api_gateway_domain_name.example will be created
  + resource "aws_api_gateway_domain_name" "example" {
      + arn                      = (known after apply)
      + certificate_upload_date  = (known after apply)
      + cloudfront_domain_name   = (known after apply)
      + cloudfront_zone_id       = (known after apply)
      + domain_name              = (known after apply)
      + id                       = (known after apply)
      + regional_certificate_arn = (known after apply)
      + regional_domain_name     = (known after apply)
      + regional_zone_id         = (known after apply)
      + security_policy          = (known after apply)

      + endpoint_configuration {
          + types = [
              + "REGIONAL",
            ]
        }
    }

  # aws_api_gateway_method_settings.example will be created
  + resource "aws_api_gateway_method_settings" "example" {
      + id          = (known after apply)
      + method_path = "*/*"
      + rest_api_id = (known after apply)
      + stage_name  = "example"

      + settings {
          + cache_data_encrypted                       = (known after apply)
          + cache_ttl_in_seconds                       = (known after apply)
          + caching_enabled                            = (known after apply)
          + data_trace_enabled                         = (known after apply)
          + logging_level                              = (known after apply)
          + metrics_enabled                            = true
          + require_authorization_for_cache_control    = (known after apply)
          + throttling_burst_limit                     = -1
          + throttling_rate_limit                      = -1
          + unauthorized_cache_control_header_strategy = (known after apply)
        }
    }

  # aws_api_gateway_rest_api.example will be created
  + resource "aws_api_gateway_rest_api" "example" {
      + api_key_source               = (known after apply)
      + arn                          = (known after apply)
      + binary_media_types           = (known after apply)
      + body                         = jsonencode(
            {
              + info    = {
                  + title   = "api-gateway-rest-api-openapi-example"
                  + version = "1.0"
                }
              + openapi = "3.0.1"
              + paths   = {
                  + /path1 = {
                      + get = {
                          + x-amazon-apigateway-integration = {
                              + httpMethod           = "GET"
                              + payloadFormatVersion = "1.0"
                              + type                 = "HTTP_PROXY"
                              + uri                  = "https://ip-ranges.amazonaws.com/ip-ranges.json"
                            }
                        }
                    }
                }
            }
        )
      + created_date                 = (known after apply)
      + description                  = (known after apply)
      + disable_execute_api_endpoint = (known after apply)
      + execution_arn                = (known after apply)
      + id                           = (known after apply)
      + minimum_compression_size     = -1
      + name                         = "api-gateway-rest-api-openapi-example"
      + policy                       = (known after apply)
      + root_resource_id             = (known after apply)

      + endpoint_configuration {
          + types            = [
              + "REGIONAL",
            ]
          + vpc_endpoint_ids = (known after apply)
        }
    }

  # aws_api_gateway_stage.example will be created
  + resource "aws_api_gateway_stage" "example" {
      + arn           = (known after apply)
      + deployment_id = (known after apply)
      + execution_arn = (known after apply)
      + id            = (known after apply)
      + invoke_url    = (known after apply)
      + rest_api_id   = (known after apply)
      + stage_name    = "example"
    }

  # tls_private_key.example will be created
  + resource "tls_private_key" "example" {
      + algorithm                  = "RSA"
      + ecdsa_curve                = "P224"
      + id                         = (known after apply)
      + private_key_pem            = (sensitive value)
      + public_key_fingerprint_md5 = (known after apply)
      + public_key_openssh         = (known after apply)
      + public_key_pem             = (known after apply)
      + rsa_bits                   = 2048
    }

  # tls_self_signed_cert.example will be created
  + resource "tls_self_signed_cert" "example" {
      + allowed_uses          = [
          + "key_encipherment",
          + "digital_signature",
          + "server_auth",
        ]
      + cert_pem              = (known after apply)
      + dns_names             = [
          + "example.com",
        ]
      + early_renewal_hours   = 0
      + id                    = (known after apply)
      + key_algorithm         = "RSA"
      + private_key_pem       = (sensitive value)
      + ready_for_renewal     = true
      + validity_end_time     = (known after apply)
      + validity_period_hours = 12
      + validity_start_time   = (known after apply)

      + subject {
          + common_name  = "example.com"
          + organization = "ACME Examples, Inc"
        }
    }

Plan: 9 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + domain_url       = (known after apply)
  + stage_invoke_url = (known after apply)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

tls_private_key.example: Creating...
tls_private_key.example: Creation complete after 0s [id=c1129fc488709c4293493669e43d40b60144999d]
tls_self_signed_cert.example: Creating...
tls_self_signed_cert.example: Creation complete after 0s [id=199729227385231255426302845367097804347]
aws_api_gateway_rest_api.example: Creating...
aws_acm_certificate.example: Creating...
aws_api_gateway_rest_api.example: Creation complete after 2s [id=halquax36h]
aws_api_gateway_deployment.example: Creating...
aws_acm_certificate.example: Creation complete after 3s [id=arn:aws:acm:us-west-2:123456789012:certificate/35cc4fc5-072f-4543-99d1-a1336ac05a41]
aws_api_gateway_domain_name.example: Creating...
aws_api_gateway_deployment.example: Creation complete after 1s [id=tj62g3]
aws_api_gateway_stage.example: Creating...
aws_api_gateway_stage.example: Creation complete after 1s [id=ags-halquax36h-example]
aws_api_gateway_method_settings.example: Creating...
aws_api_gateway_method_settings.example: Creation complete after 1s [id=halquax36h-example-*/*]
aws_api_gateway_domain_name.example: Creation complete after 3s [id=example.com]
aws_api_gateway_base_path_mapping.example: Creating...
aws_api_gateway_base_path_mapping.example: Creation complete after 1s [id=example.com/]

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

domain_url = "curl -H 'Host: example.com' https://d-orixhuv0o9.execute-api.us-west-2.amazonaws.com/path1 # may take a minute to become available on initial deploy"
stage_invoke_url = "curl https://halquax36h.execute-api.us-west-2.amazonaws.com/example/path1"

$ curl -s https://halquax36h.execute-api.us-west-2.amazonaws.com/example/path1 | jq '.createDate'
"2021-01-21-00-44-18"

$ curl -H 'Host: example.com' -s https://d-orixhuv0o9.execute-api.us-west-2.amazonaws.com/path1 | jq '.createDate'
"2021-01-21-00-44-18"

$ terraform apply -var 'rest_api_path=/path2'
tls_private_key.example: Refreshing state... [id=c1129fc488709c4293493669e43d40b60144999d]
tls_self_signed_cert.example: Refreshing state... [id=199729227385231255426302845367097804347]
aws_api_gateway_rest_api.example: Refreshing state... [id=halquax36h]
aws_acm_certificate.example: Refreshing state... [id=arn:aws:acm:us-west-2:123456789012:certificate/35cc4fc5-072f-4543-99d1-a1336ac05a41]
aws_api_gateway_deployment.example: Refreshing state... [id=tj62g3]
aws_api_gateway_domain_name.example: Refreshing state... [id=example.com]
aws_api_gateway_stage.example: Refreshing state... [id=ags-halquax36h-example]
aws_api_gateway_base_path_mapping.example: Refreshing state... [id=example.com/]
aws_api_gateway_method_settings.example: Refreshing state... [id=halquax36h-example-*/*]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place
+/- create replacement and then destroy

Terraform will perform the following actions:

  # aws_api_gateway_deployment.example must be replaced
+/- resource "aws_api_gateway_deployment" "example" {
      ~ created_date  = "2021-01-22T02:59:46Z" -> (known after apply)
      ~ execution_arn = "arn:aws:execute-api:us-west-2:123456789012:halquax36h/" -> (known after apply)
      ~ id            = "tj62g3" -> (known after apply)
      ~ invoke_url    = "https://halquax36h.execute-api.us-west-2.amazonaws.com/" -> (known after apply)
      ~ triggers      = { # forces replacement
          ~ "redeployment" = "e042aae1faf8de8d7c7c98c063a986025f058c69" -> "e6742b53b5eed7039e6fec056113bb049954d64b"
        }
        # (1 unchanged attribute hidden)
    }

  # aws_api_gateway_rest_api.example will be updated in-place
  ~ resource "aws_api_gateway_rest_api" "example" {
      ~ body                         = jsonencode(
          ~ {
              ~ paths   = {
                  - /path1 = {
                      - get = {
                          - x-amazon-apigateway-integration = {
                              - httpMethod           = "GET"
                              - payloadFormatVersion = "1.0"
                              - type                 = "HTTP_PROXY"
                              - uri                  = "https://ip-ranges.amazonaws.com/ip-ranges.json"
                            }
                        }
                    } -> null
                  + /path2 = {
                      + get = {
                          + x-amazon-apigateway-integration = {
                              + httpMethod           = "GET"
                              + payloadFormatVersion = "1.0"
                              + type                 = "HTTP_PROXY"
                              + uri                  = "https://ip-ranges.amazonaws.com/ip-ranges.json"
                            }
                        }
                    }
                }
                # (2 unchanged elements hidden)
            }
        )
        id                           = "halquax36h"
        name                         = "api-gateway-rest-api-openapi-example"
        tags                         = {}
        # (8 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # aws_api_gateway_stage.example will be updated in-place
  ~ resource "aws_api_gateway_stage" "example" {
      ~ deployment_id         = "tj62g3" -> (known after apply)
        id                    = "ags-halquax36h-example"
        tags                  = {}
        # (8 unchanged attributes hidden)
    }

Plan: 1 to add, 2 to change, 1 to destroy.

Changes to Outputs:
  ~ domain_url       = "curl -H 'Host: example.com' https://d-orixhuv0o9.execute-api.us-west-2.amazonaws.com/path1 # may take a minute to become available on initial deploy" -> "curl -H 'Host: example.com' https://d-orixhuv0o9.execute-api.us-west-2.amazonaws.com/path2 # may take a minute to become available on initial deploy"
  ~ stage_invoke_url = "curl https://halquax36h.execute-api.us-west-2.amazonaws.com/example/path1" -> "curl https://halquax36h.execute-api.us-west-2.amazonaws.com/example/path2"

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_api_gateway_rest_api.example: Modifying... [id=halquax36h]
aws_api_gateway_rest_api.example: Modifications complete after 1s [id=halquax36h]
aws_api_gateway_deployment.example: Creating...
aws_api_gateway_deployment.example: Creation complete after 1s [id=9vc6zm]
aws_api_gateway_stage.example: Modifying... [id=ags-halquax36h-example]
aws_api_gateway_stage.example: Modifications complete after 1s [id=ags-halquax36h-example]
aws_api_gateway_deployment.example: Destroying... [id=tj62g3]
aws_api_gateway_deployment.example: Destruction complete after 0s

Apply complete! Resources: 1 added, 2 changed, 1 destroyed.

Outputs:

domain_url = "curl -H 'Host: example.com' https://d-orixhuv0o9.execute-api.us-west-2.amazonaws.com/path2 # may take a minute to become available on initial deploy"
stage_invoke_url = "curl https://halquax36h.execute-api.us-west-2.amazonaws.com/example/path2"

$ curl -s https://halquax36h.execute-api.us-west-2.amazonaws.com/example/path2 | jq '.createDate'
"2021-01-21-00-44-18"

$ curl -H 'Host: example.com' -s https://d-orixhuv0o9.execute-api.us-west-2.amazonaws.com/path2 | jq '.createDate'
"2021-01-21-00-44-18"
```

* docs/service/apigateway: Adjust for main branch rename

* examples/api-gateway-rest-api-openapi: Add curl_ prefix to output names
  • Loading branch information
bflad authored Jan 29, 2021
1 parent 9fc2537 commit 0739656
Show file tree
Hide file tree
Showing 19 changed files with 580 additions and 226 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
*.exe
.DS_Store
example.tf
.terraform.lock.hcl
.terraform.tfstate.lock.info
terraform.tfplan
terraform.tfstate
bin/
Expand Down
11 changes: 11 additions & 0 deletions examples/api-gateway-rest-api-openapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# API Gateway REST API OpenAPI Example

This example demonstrates how to create an end-to-end AWS API Gateway REST API setup with an OpenAPI configuration that proxies the [AWS IP Address Ranges](https://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html) JSON, enables CloudWatch metrics, and sets up a domain with a self-signed TLS certificate to mimic a real-world endpoint. The outputs will provide sample `curl` commands to verify the REST API deployment.

## Running this Example

Terraform variables are available to modify this example, see the `variables.tf` file. They can be provided by `cp terraform.template.tfvars terraform.tfvars`, modifying `terraform.tfvars` with your variables, and running `terraform apply`. Alternatively, the variables can be provided as flags by running:

```shell
terraform apply -var="aws_region=us-west-2"
```
18 changes: 18 additions & 0 deletions examples/api-gateway-rest-api-openapi/domain.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Domain Setup
#

resource "aws_api_gateway_domain_name" "example" {
domain_name = aws_acm_certificate.example.domain_name
regional_certificate_arn = aws_acm_certificate.example.arn

endpoint_configuration {
types = ["REGIONAL"]
}
}

resource "aws_api_gateway_base_path_mapping" "example" {
api_id = aws_api_gateway_rest_api.example.id
domain_name = aws_api_gateway_domain_name.example.domain_name
stage_name = aws_api_gateway_stage.example.stage_name
}
7 changes: 7 additions & 0 deletions examples/api-gateway-rest-api-openapi/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12"
}

provider "aws" {
region = var.aws_region
}
15 changes: 15 additions & 0 deletions examples/api-gateway-rest-api-openapi/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Outputs
#

output "curl_domain_url" {
depends_on = [aws_api_gateway_base_path_mapping.example]

description = "API Gateway Domain URL (self-signed certificate)"
value = "curl -H 'Host: ${var.rest_api_domain_name}' https://${aws_api_gateway_domain_name.example.regional_domain_name}${var.rest_api_path} # may take a minute to become available on initial deploy"
}

output "curl_stage_invoke_url" {
description = "API Gateway Stage Invoke URL"
value = "curl ${aws_api_gateway_stage.example.invoke_url}${var.rest_api_path}"
}
39 changes: 39 additions & 0 deletions examples/api-gateway-rest-api-openapi/rest-api.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
resource "aws_api_gateway_rest_api" "example" {
body = jsonencode({
openapi = "3.0.1"
info = {
title = var.rest_api_name
version = "1.0"
}
paths = {
(var.rest_api_path) = {
get = {
x-amazon-apigateway-integration = {
httpMethod = "GET"
payloadFormatVersion = "1.0"
type = "HTTP_PROXY"
uri = "https://ip-ranges.amazonaws.com/ip-ranges.json"
}
}
}
}
})

name = var.rest_api_name

endpoint_configuration {
types = ["REGIONAL"]
}
}

resource "aws_api_gateway_deployment" "example" {
rest_api_id = aws_api_gateway_rest_api.example.id

triggers = {
redeployment = sha1(jsonencode(aws_api_gateway_rest_api.example.body))
}

lifecycle {
create_before_destroy = true
}
}
19 changes: 19 additions & 0 deletions examples/api-gateway-rest-api-openapi/stage.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# Stage and Stage Settings
#

resource "aws_api_gateway_stage" "example" {
deployment_id = aws_api_gateway_deployment.example.id
rest_api_id = aws_api_gateway_rest_api.example.id
stage_name = "example"
}

resource "aws_api_gateway_method_settings" "example" {
rest_api_id = aws_api_gateway_rest_api.example.id
stage_name = aws_api_gateway_stage.example.stage_name
method_path = "*/*"

settings {
metrics_enabled = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
aws_region = "us-west-2"
rest_api_domain_name = "example.com"
rest_api_name = "api-gateway-rest-api-openapi-example"
rest_api_path = "/path1"
29 changes: 29 additions & 0 deletions examples/api-gateway-rest-api-openapi/tls.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# Self-Signed TLS Certificate for Testing
#

resource "tls_private_key" "example" {
algorithm = "RSA"
}

resource "tls_self_signed_cert" "example" {
allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth",
]
dns_names = [var.rest_api_domain_name]
key_algorithm = tls_private_key.example.algorithm
private_key_pem = tls_private_key.example.private_key_pem
validity_period_hours = 12

subject {
common_name = var.rest_api_domain_name
organization = "ACME Examples, Inc"
}
}

resource "aws_acm_certificate" "example" {
certificate_body = tls_self_signed_cert.example.cert_pem
private_key = tls_private_key.example.private_key_pem
}
23 changes: 23 additions & 0 deletions examples/api-gateway-rest-api-openapi/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
variable "aws_region" {
default = "us-west-2"
description = "AWS Region to deploy example API Gateway REST API"
type = string
}

variable "rest_api_domain_name" {
default = "example.com"
description = "Domain name of the API Gateway REST API for self-signed TLS certificate"
type = string
}

variable "rest_api_name" {
default = "api-gateway-rest-api-openapi-example"
description = "Name of the API Gateway REST API (can be used to trigger redeployments)"
type = string
}

variable "rest_api_path" {
default = "/path1"
description = "Path to create in the API Gateway REST API (can be used to trigger redeployments)"
type = string
}
16 changes: 9 additions & 7 deletions website/docs/r/api_gateway_base_path_mapping.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ custom domain name.

## Example Usage

An end-to-end example of a REST API configured with OpenAPI can be found in the [`/examples/api-gateway-rest-api-openapi` directory within the GitHub repository](https:/hashicorp/terraform-provider-aws/tree/main/examples/api-gateway-rest-api-openapi).

```hcl
resource "aws_api_gateway_deployment" "example" {
# See aws_api_gateway_rest_api docs for how to create this
rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id
stage_name = "live"
resource "aws_api_gateway_stage" "example" {
deployment_id = aws_api_gateway_deployment.example.id
rest_api_id = aws_api_gateway_rest_api.example.id
stage_name = "example"
}
resource "aws_api_gateway_domain_name" "example" {
Expand All @@ -30,9 +32,9 @@ resource "aws_api_gateway_domain_name" "example" {
certificate_private_key = file("${path.module}/example.com/example.key")
}
resource "aws_api_gateway_base_path_mapping" "test" {
api_id = aws_api_gateway_rest_api.MyDemoAPI.id
stage_name = aws_api_gateway_deployment.example.stage_name
resource "aws_api_gateway_base_path_mapping" "example" {
api_id = aws_api_gateway_rest_api.example.id
stage_name = aws_api_gateway_stage.example.stage_name
domain_name = aws_api_gateway_domain_name.example.domain_name
}
```
Expand Down
Loading

0 comments on commit 0739656

Please sign in to comment.