TerraformでAmazon API Gatewayを構築する(ゲートウェイのレスポンス＆ステージ詳細編)

# はじめに
[以前の記事](https://qiita.com/neruneruo/items/94a0e637ea1c6c95d9e4)で、TerraformでAPI Gatewayを構築することを書いてみたが、基本的な部分過ぎて実用的ではなかったので今回はゲートウェイのレスポンスと、ステージの詳細設定をするためのIaCをまとめる。

例によって、Terraformの公式ドキュメントは書き味が薄くて困るので、内容の薄い部分について、AWSの開発者ガイドやCLIのリファレンス等を組み合わせながらまとめる。

文中の画面キャプチャや画面文言は2020年7月25日時点のものなので、今後文言が変わったり、項目の増減があるかもしれないことをご了承いただきたい。

# 前提条件
- [以前のTerraform＋Amazon API Gatewayの記事](https://qiita.com/neruneruo/items/94a0e637ea1c6c95d9e4)の中身がなんとなく理解できる
- Terraformについてはある程度書き方を理解していて、自力でリファレンスを見ながら書くことができる程度の知識量を期待する

# ゲートウェイのレスポンスを設定する
以下の画面のことを指す。

![キャプチャ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/0f9c9d49-2d9a-554e-7f9f-2eaab064658f.png)

Terraformのリソースとしては、[api_gateway_gateway_response](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_gateway_response)となる。

これを見ると、
> ```response_type``` - (Required) The response type of the associated GatewayResponse.
と書かれていて、いきなり心が折れることになる。
一体画面の項目が何のタイプにマッピングされるのよ……

ということで、TerraformはバックエンドではCLIを動かしていると思われるので、[該当のCLIのリファレンス](https://docs.aws.amazon.com/cli/latest/reference/apigateway/put-gateway-response.html)を見てみる。

> The response type of the associated GatewayResponse . Valid values are
>
> - ACCESS_DENIED
> (以下略)

とある。よしよし、これならいけそうだ。

というわけで、画面の日本語とマッピングしていってみる。

| マネコン画面の日本語 | CLIのパラメータ(Terraformでも使える) | 
|:--|:--|
|アクセスが拒否されました|ACCESS_DENIED|
|API 設定エラー|API_CONFIGURATION_ERROR|
|オーソライザー設定エラー|AUTHORIZER_CONFIGURATION_ERROR|
|オーソライザーエラー|AUTHORIZER_FAILURE|
|リクエスト本文が不正です|BAD_REQUEST_BODY|
|リクエストパラメータが不正です|BAD_REQUEST_PARAMETERS|
|DEFAULT 4XX|DEFAULT_4XX|
|DEFAULT 5XX|DEFAULT_5XX|
|期限切れのトークン|EXPIRED_TOKEN|
|統合の失敗|INTEGRATION_FAILURE|
|統合のタイムアウト|INTEGRATION_TIMEOUT|
|無効な API キー|INVALID_API_KEY|
|無効な署名|INVALID_SIGNATURE|
|認証トークンが見つかりません|MISSING_AUTHENTICATION_TOKEN|
|クォータを超過しました|QUOTA_EXCEEDED|
|リクエストが大きすぎます|REQUEST_TOO_LARGE|
|リソースは存在しません|RESOURCE_NOT_FOUND|
|スロットル済み|THROTTLED|
|権限がありません|UNAUTHORIZED(たぶん)|
|サポートされていないメディアタイプ|UNSUPPORTED_MEDIA_TYPE|
|WAF フィルター|WAF_FILTERED|

ちなみに、CLIのリファレンスやmanには```WAFフィルター```該当するCLI項目がない。
[開発者ガイド](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/supported-gateway-response-types.html)を見ると、どうやら```WAF_FILTERED```らしい。いや、これ日本語おかしいだろ。```WAFにフィルターされた```とかにしないと分かりにくい……。
※ちなみに、リファレンスやmanには載っていないが、```WAF_FILTERED```で設定変更できたので、パラメータとしては有効なようだ。

それでは、よくcurlで間違ったURIとかメソッドを狙って返される

```
{"message":"Missing Authentication Token"}
```

を変更してみよう。```response_type```は```MISSING_AUTHENTICATION_TOKEN```で、デフォルトのHTTPレスポンスコードは403なので、レスポンスコードも変えてみよう(存在しないパスが```403 Forbidden```になるのはある意味正しいので、本来はコードまでは変更する必要がないかもしれないが、今回はおためしということで)。

```
resource "aws_api_gateway_gateway_response" "missing_authentication_token" {
  rest_api_id   = aws_api_gateway_rest_api.my_api.id
  response_type = "MISSING_AUTHENTICATION_TOKEN"
  status_code   = "404"

  response_templates = {
   "application/json" = "{'message':'Resource Not Found'}"
  }

  response_parameters = {
    "gatewayresponse.header.test-header" : "'test'"
  }
}
```

```response_parameters```では、統合レスポンスを使用している場合に、その値をAPIのレスポンスにマッピングすることができる。詳細は[Amazon API Gateway API リクエストおよびレスポンスデータマッピングのリファレンス](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/request-response-data-mappings.html)を見ると良い。
今回のケースでは、このレスポンスのパターンになった場合、```test-header: test```を返す。

では、これを```terraform apply```した後にアクセスしてみよう。

```
curl -i https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/user?id=11111\&name=Taro
HTTP/2 404 
date: Sat, 25 Jul 2020 08:57:45 GMT
content-type: application/json
content-length: 32
x-amzn-requestid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
test-header: test
x-amzn-errortype: MissingAuthenticationTokenException
x-amz-apigw-id: xxxxxxxxxxxxxxxx

{'message':'Resource Not Found'}
```

といった具合で、設定した通りのレスポンスが返却されるようになった。

# ステージの詳細設定
次に以下の画面について。

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/1741c58d-7953-d5f1-0edd-e93424f89d76.png)

主なTerraformのリソースとしては[aws_api_gateway_stage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage)だ。

ここも、マネコン画面に表示されている日本語とTerraformの引数の内容をマッピングしてみる。

## 設定タブ

設定タブについては、[aws_api_gateway_stage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage)の他に[api_gateway_method_settings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_settings)が重要になる。
なぜapi_gateway_method_settings？と思うかもしれないが、このリソースは、```method_path``` のプロパティを ```*/*``` にすることで、```stage_name``` で指定したステージ全体のデフォルトの設定をすることができる。

ステージは以下のリソースのツリーを選択すると、以下のようにラジオボタンが表示される。

![キャプチャ9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/d1014dc3-23ba-c739-7548-431e7af79fdb.png)

ここで、「このメソッドの上書き」を選択すると、

![キャプチャ8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/173b0860-c258-9a57-267c-ca4524d47af9.png)

のように設定項目が展開され、リソースとメソッド固有の設定で上書きすることができる。

Terraformでも同様に、```stage_name``` に「リソースパス/メソッド」を設定することで ```*/*``` で設定した内容を上書きすることができる。

### キャッシュ設定
| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| キャッシュ設定 | cache_cluster_enabled ※true/falseのboolean |

なお、キャッシュ設定は、マネコン画面上ではチェックボックスをONにすると、

![キャプチャ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/13414022-14d6-487d-1c09-0531cbe46e5f.png)

な画面が表示されるが、これを詳細設定するリソースは、[aws_api_gateway_method_settings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_method_settings)になる。
```aws_api_gateway_stage```で設定できるのは、「キャッシュキャパシティー(cache_cluster_size)」までだ。```aws_api_gateway_method_settings```では、以下のようにマッピングされる。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| キャッシュデータを暗号化する | cache_data_encrypted ※true/falseのboolean |
| キャッシュ有効期限 (TTL) | cache_ttl_in_seconds |
| 認可が必要 | require_authorization_for_cache_control ※true/falseのboolean |
| 未認可リクエストの処理 | unauthorized_cache_control_header_strategy |

### デフォルトのメソッドスロットリング
これも、```aws_api_gateway_stage```ではなくて```aws_api_gateway_method_settings```で定義する。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| デフォルトのメソッドスロットリング | スロットリングの制御内容は[開発者ガイド](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-request-throttling.html#apigateway-how-throttling-limits-are-applied)を参照。Terraformでは、以下の throttling_rate_limit か throttling_burst_limit のいずれかが-1だとチェックOFFになる |
| レート | throttling_rate_limit |
| バースト | throttling_burst_limit |

### ウェブアプリケーションファイアウォール (WAF)
これはイマイチよく分からなかった……。
[aws_wafregional_web_acl_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafregional_web_acl_association)のリソースに

>API Gateway Association Example

という項があるので、おそらくこのことだろうか。

### クライアント証明書
実験できていないので、多分この引数で合っていると思うが、詳細は不明。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| 証明書 | client_certificate_id  |

## ログ/トレースタブ
### CloudWatch 設定
この項目は、```aws_api_gateway_stage```ではなくて```aws_api_gateway_method_settings```で定義する。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| CloudWatch ログを有効化 | logging_level(OFFで未チェック、ERRORまたはINFOでチェック済み) |
| 詳細 CloudWatch メトリクスを有効化 | metrics_enabled |

なお、CloudWatch ログを有効化をチェックすると、画面上では以下の項目が表示される。

![キャプチャ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/22a1bcf5-4697-75b8-a03b-6ac7db6e2177.png)

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| ログレベル | logging_level |
| リクエスト/レスポンスをすべてログ | data_trace_enabled ※true/falseのboolean |
| 詳細CloudWatch メトリクスを有効化 | metrics_enabled ※true/falseのboolean |

また、CloudWatch ログを有効化するためには、以下の「設定」で、CloudWatchLogsの書き込み権を持ったIAMロールを設定する必要がある。

![キャプチャ6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/04fc16c2-4e24-e219-c6c7-5df034658d4e.png)

このため、今回は以下のようにIAMロールを定義する。CloudWatchLogsの書き込みポリシについては、```AmazonAPIGatewayPushToCloudWatchLogs```を使えと[AWSのブログ](https://aws.amazon.com/jp/premiumsupport/knowledge-center/api-gateway-cloudwatch-logs/)に書いてあった。

```HCL
################################################################################
# IAM Role for API Gateway Put CloudWatch Logs                                 #
################################################################################
resource "aws_iam_role" "apigateway_putlog" {
  name = local.rest_api_putlog_role_name

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "apigateway_putlog" {
  role       = "${aws_iam_role.apigateway_putlog.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
}
```

これを、以下のように```aws_api_gateway_account```で設定すれば良い。

```HCL
resource "aws_api_gateway_account" "my_api" {
  cloudwatch_role_arn = aws_iam_role.apigateway_putlog.arn
}
```

で、ここで油断してはならないのは、```aws_api_gateway_account``` はそのままだと ```aws_api_gateway_method_settings``` との依存関係がないため、順序性が保証されず、ログ設定がエラーになってしまう。そのため、以下のように依存関係を定義しておく。

```HCL
resource "aws_api_gateway_method_settings" "all" {
  depends_on = [
    aws_api_gateway_account.my_api,
  ]

  rest_api_id = "${aws_api_gateway_rest_api.my_api.id}"
  stage_name  = "${aws_api_gateway_stage.prod.stage_name}"
  method_path = "*/*"

  settings {
    logging_level = "ERROR"
  }
}
```

### カスタムアクセスのログ設定
この項目はなんか日本語が変な気が…。「の」は要らないような。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| アクセスログの有効化 | access_log_settings ※ブロックに以下の項目を記載する |

なお、アクセスログの有効化をチェックすると、画面上では以下の項目が表示される。

![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/00287614-6981-1791-cb47-d86b3bb2d6b9.png)

これは、```aws_api_gateway_stage```のリソースの```access_log_settings```のブロックで定義する。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| Access Log Destination ARN | destination_arn |
| ログの形式 | format |

ログの形式の出力例は、画面のボタンをポチっと押すか、[AWSの開発者ガイド](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-logging.html)を参照。

この項目を有効にするためには、まずはCloudWatch Logsのリソースを作らなければいけないので、以下のように作成する。

```HCL
resource "aws_cloudwatch_log_group" "apigateway_accesslog" {
  name = local.rest_api_accesslog_loggroup_name
}
```

次に、```aws_api_gateway_stage```に以下のように定義したアクセスログを設定すれば良い。

```HCL
resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.my_api.id
  deployment_id = aws_api_gateway_deployment.dev.id

  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.apigateway_accesslog.arn
    format          = "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId"
  }
}
```

### X-Ray トレース
これはシンプルに設定可能。

| マネコン画面の日本語 | Terraformの引数 | 
|:--|:--|
| X-Ray トレースの有効化 | xray_tracing_enabled |

## ステージ変数タブ
これは```variables```の引数で渡すことができる。
[Terraformのドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_stage#variables)では、

> (Optional) A map that defines the stage variables

と書かれているので、渡し方に注意が必要。

## SDKの生成/エクスポート/デプロイ履歴/ドキュメント履歴タブ
この辺は設定項目ではなく参照系であるため、特にTerraformに紐づく項目は無い。

## Canaryタブ
うーん、これはTerraform未実装な気がする。

[CLIのドキュメント](https://docs.aws.amazon.com/cli/latest/reference/apigateway/create-stage.html)には```--canary-settings```オプションが存在するが、Terraformでは難しいということだろうか(いずれにしろ、CodeDeployのような便利なCanaryリリースではないので、あまり使えないのだが)。

# デプロイについて
API Gatewayのリソースについては、以下のようにデプロイを定義する。
triggersは、「このリソースが変更されたら再デプロイする」という機能らしい。
詳細は、[公式のNotes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_deployment#redeployment-triggers)を参照。
lifecycleは、スキマを作らないようにするための施策だ。

```HCL
resource "aws_api_gateway_deployment" "dev" {
  rest_api_id = aws_api_gateway_rest_api.my_api.id
  stage_name  = "dev"

  triggers = {
    redeployment = sha1(join(",", list(
      jsonencode(module.get_user),
      jsonencode(module.put_user),
      jsonencode(aws_api_gateway_gateway_response.missing_authentication_token),
    )))
  }

  lifecycle {
    create_before_destroy = true
  }
}
```

ただ、これだけだと、devにデプロイした機能がprodまで伝播してくれない。
うーむ、prod側にも設定を入れるべきなのだろうか。それとも、そこは別契機でデプロイするということか。