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

  • 0
    いいね
  • 0
    コメント

    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