DynamoDBの操作を行うAPI Gatewayをterraformで準備するときのメモ

API Gatewayは、GUIでぽちぽちやると、かなり入力項目が多く、
コード化しておいた方が何かと便利なため、表題の構成をterraformでコード化したときのメモです。
参考にしたのは、以下のサイト

Amazon API GatewayでAPIキー認証を設定する
Amazon API Gateway で AWS Service Proxy を使って DynamoDB にアクセスする
API Gatewayから、AWS Lambdaを使わずにDynamoDBにアクセスする

1.構成図

図解するほどでもないですが、各種構成は以下の通り.
apigw4dynamodb.PNG

2.各種terraformファイル

2-1. AWS環境設定部分

#####################################
# Provider Settings
#####################################
provider "aws" {
    access_key = "********************"
    secret_key = "*********************************"
    region = "ap-northeast-1"
}

2-2. DynamoDB設定部分

#####################################
# DynamoDB Settings
#####################################
resource "aws_dynamodb_table" "sample" {
    name = "sample"
    read_capacity = 5
    write_capacity = 5
    hash_key = "primary_key"
    attribute {
        name = "primary_key"
        type = "S"
    }
}

primary_keyというキーで、DynamoDBに文字列を格納するだけです。

2-3. API Gateway設定部分

#####################################
#API Gateway Settings
#####################################
resource "aws_api_gateway_rest_api" "sample" {
    name = "sample"
    description = "sample"
}

resource "aws_api_gateway_api_key" "sample" {
    name = "sample"
}

2-4. API GatewayのPOSTリクエスト部分

#############################################
#POST method
#############################################
resource "aws_api_gateway_model" "post-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  name = "PostSample"
  description = "post-sample"
  content_type = "application/json"
  schema = <<EOF
{
  "type" : "object",
  "properties" : {
    "key": { "type": "string" }
  }
}
EOF
}

resource "aws_api_gateway_method" "post-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
  http_method = "POST"
  authorization = "NONE"
  api_key_required = "true"
  request_models = {
     "application/json" = "${aws_api_gateway_model.post-sample.name}"
  }
}

resource "aws_api_gateway_integration" "post-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
  http_method = "${aws_api_gateway_method.post-sample.http_method}"
  type = "AWS"
  uri = "arn:aws:apigateway:ap-northeast-1:dynamodb:action/PutItem"
  integration_http_method = "POST"
  credentials = "${aws_iam_role.post-sample.arn}"
  passthrough_behavior = "WHEN_NO_TEMPLATES"
  request_templates  = {
    "application/json" = "${file("request_templates/post-sample.json")}"
  }
}

resource "aws_api_gateway_integration_response" "post-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
  http_method = "${aws_api_gateway_method.post-sample.http_method}"
  status_code = "${aws_api_gateway_method_response.post-sample.status_code}"
  selection_pattern = "200"
  response_templates = {
    "application/json" = "{'message':'Success'}"
  }
}

resource "aws_api_gateway_method_response" "post-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
  http_method = "${aws_api_gateway_method.post-sample.http_method}"
  status_code = "200"
  response_models = {
    "application/json" = "Empty"
  }
}

POSTするときに、keyというデータを送信するようにしています。
DynamoDBの中身によって、このschemaやresponse_templatesのマッピング方法を変更すればよいかと。

2-5. API GatewayのGETリクエスト部分

#############################################
#GET method
#############################################
resource "aws_api_gateway_model" "get-sample" {
    rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
    name = "GetSample"
    description = "get-sample"
    content_type = "application/json"
    schema = <<EOF
{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "primary_key": {
                "type": "string"
            }
        }
    }
}
EOF
}

resource "aws_api_gateway_method" "get-sample" {
    rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
    resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
    http_method = "GET"
    authorization = "NONE"
    api_key_required = "true"
}

resource "aws_api_gateway_integration" "get-sample" {
    rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
    resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
    http_method = "${aws_api_gateway_method.get-sample.http_method}"
    type = "AWS"
    uri = "arn:aws:apigateway:ap-northeast-1:dynamodb:action/Scan"
    integration_http_method = "POST"
    credentials = "${aws_iam_role.get-sample.arn}"
    passthrough_behavior = "WHEN_NO_TEMPLATES"
    request_templates  = {
        "application/json" = "${file("request_templates/get-sample.json")}"
    }
}

resource "aws_api_gateway_integration_response" "get-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
  http_method = "${aws_api_gateway_method.get-sample.http_method}"
  status_code = "${aws_api_gateway_method_response.get-sample.status_code}"
  selection_pattern = "200"
  response_templates = {
    "application/json" = "${file("request_templates/get-sample-response.json")}"
  }
}

resource "aws_api_gateway_method_response" "get-sample" {
  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  resource_id = "${aws_api_gateway_rest_api.sample.root_resource_id}"
  http_method = "${aws_api_gateway_method.get-sample.http_method}"
  status_code = "200"
  response_models = {
    "application/json" = "${aws_api_gateway_model.get-sample.name}"
  }
}

array型を返してますが、ここもPOST側と同じく、用途に応じてマッピングを変更すればよいかと。
#というか、ほとんどがこのマッピング部分の設定次第ですw

2-6. API Gatewayのデプロイ部分

#############################################
#Deploy
#############################################
resource "aws_api_gateway_deployment" "sample" {
  depends_on = [
    "aws_api_gateway_method.get-sample",
    "aws_api_gateway_method.post-sample",
  ]

  rest_api_id = "${aws_api_gateway_rest_api.sample.id}"
  stage_name = "sample"

}

内容に変更がなければ、terraformで初回applyのみデプロイされる模様

2-7. IAMの設定部分

###############################################
#IAM Settings
###############################################
resource "aws_iam_role" "get-sample" {
    name = "get-sample"
    path = "/"
    assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "dynamodb.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": "2"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "get-sample" {
    name = "get-sample"
    role = "${aws_iam_role.get-sample.id}"

    policy = "${file("policy/get-sample.json")}"
}

resource "aws_iam_role" "post-sample" {
    name = "post-sample"
    path = "/"
    assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "dynamodb.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": "2"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "post-sample" {
    name = "post-sample"
    role = "${aws_iam_role.post-sample.id}"

    policy = "${file("policy/post-sample.json")}"
}

POSTとGETの違いは、policy配下のjsonで設定しています。

2-8. policyの設定部分

POST用

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DynamoDBPutItem",
      "Effect": "Allow",
      "Action": [
        "dynamodb:DescribeTable",
        "dynamodb:Query",
        "dynamodb:PutItem"
      ],
      "Resource": [
        "arn:aws:dynamodb:ap-northeast-1:**********:table/sample"
      ]
    },
    {
      "Sid": "cloudwatchlog",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams",
        "logs:PutLogEvents",
        "logs:GetLogEvents",
        "logs:FilterLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

GET用

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DynamoDBScanItem",
      "Effect": "Allow",
      "Action": [
        "dynamodb:DescribeTable",
        "dynamodb:Query",
        "dynamodb:Scan"
      ],
      "Resource": [
        "arn:aws:dynamodb:ap-northeast-1:**********:table/sample"
      ]
    },
    {
      "Sid": "cloudwatchlog",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams",
        "logs:PutLogEvents",
        "logs:GetLogEvents",
        "logs:FilterLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

2-9. request_templeteの設定部分

POSTのリクエストテンプレート

#set($inputRoot = $input.path('$'))
{
  "TableName": "sample",
  "Item": {
    "primary_key": {
      "S": "${inputRoot.key}"
    }
  }
}

GETのリクエストテンプレート

{
  "TableName": "sample"
}

GETのレスポンステンプレート

#set($items = $input.path('$.Items'))
[
#foreach($item in $items)
  {
    "key": "${item.primary_key.S}"
  }#if($foreach.hasNext),#end
#end
]

ここの部分は、用途によって変更すればよいかと。

3.実行結果

3-1.POSTリクエスト

$ curl -XPOST --data '{"key":"test"}' -H 'Content-Type:application/json' -H 'x-api-key:*********************' https://**********.execute-api.ap-northeast-1.amazonaws.com/sample
{'message':'Success'}

dynamodb.PNG
データ投入後はこんな感じです。

3-2.GETリクエスト

$ curl -H'x-api-key:*********************' https://**********.execute-api.ap-northeast-1.amazonaws.com/sample           
[                                                                                                                                                      
  {                                                                                                                                                    
    "key": "test"                                                                                                                                      
  }]                                                                                                                                                   

DynamoDBの値を取得できました^^

4.まとめ

少しコード量が多い気がしますが、terraformでもAPI Gatewayのコード化ができました。
今回はDynamoDBで設定しましたが、同じような感じで他のAWSのサービスと連携できるかなと。
なお、検証で使ったコードは、以下にあげてあります。
https://github.com/CkReal/apigw4dynamodb