Dev AWSome Day 2018の復習⑤　～Amazon API Gateway編～

[前回](https://qiita.com/mine820/items/006d18ac2885ded3343e)に続き、「[Dev AWSome Day 2018](https://aws.amazon.com/jp/aws-devday-tokyo-2018/dev-awsomeday/)」の復習として、「Amazon API Gateway」を勉強し直します。

今回のハンズオンではアップロードした画像の分類結果がDynamoDBに設定されるのですが、それをAPI Gateway経由で確認することになります。
（実際にはLambda経由になります）

なお、ここではCloudFormationを利用して、３つのリソース(API Gateway/Lambda/IAM Role) を一括で自動作成します。
※もちろん、マネージメントコンソールやAWS CLIで作成することも出来ます。

# デプロイ用のS3の作成
CloudFormationで一括作成するために、そのテンプレートやLambdaのコードなどをS3に保存しておきます。
S3の作成やファイルの保存はAWS CLIから行います。

まずS3にバケットを作成します。

```text
> aws s3 mb s3://devawsome-deploy-mine --region us-west-2
```
※「devawsome-deploy-mine」はデプロイ用のバケット名です

# CloudFormation を利用した3つのリソースの作成
次に、CloudFormationでSAM（サーバレスアプリケーションモデル）テンプレートを指定して3つのリソースを作成します。

先程作成したバケットに、API Gatewayで作成されるリソースを定義したファイルを保存します。

```text
> aws s3 cp ./swagger.yaml s3://devawsome-deploy-mine --region us-west-2
```

「swagger.yaml」が作成するリソースを定義したファイルになります。
内容は以下のようになっています。

```yaml
---
swagger: "2.0"
info:
  version: "1.0"
  title: "DevAWSome-PhotoAPI"
basePath: "/Prod"
schemes:
- "https"
paths:
  /photos/{username}:
    get:
      responses: {}
      security:
      - sigv4: []
      x-amazon-apigateway-integration:
        uri: 
            "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DevAWSomePhotoList.Arn}/invocations"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws_proxy"
    options:
      consumes:
      - "application/json"
      produces:
      - "application/json"
      responses:
        200:
          description: "200 response"
          headers:
            Access-Control-Allow-Origin:
              type: "string"
            Access-Control-Allow-Methods:
              type: "string"
            Access-Control-Allow-Headers:
              type: "string"
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: "200"
            responseParameters:
              method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            responseTemplates:
              application/json: "{}\n"
        passthroughBehavior: "when_no_match"
        requestTemplates:
          application/json: "{\n  \"statusCode\" : 200\n}\n"
        type: "mock"
securityDefinitions:
  sigv4:
    type: "apiKey"
    name: "Authorization"
    in: "header"
    x-amazon-apigateway-authtype: "awsSigv4"
```

更に、テンプレートファイルを指定し、パッケージしたファイルを作成します。

```text
> aws cloudformation package --template-file template.yaml  \
  --s3-bucket devawsome-deploy-mine  \
  --output-template-file packaged.yaml  \
  --region us-west-2
```

テンプレートファイルの中身は、以下のようになっています。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless Specification template describing your function.

Parameters:
  PhotoAppBucketName:
    Type: String
  DeployBucketName:
    Type: String

Globals:
  Api:
     Cors: "'*'"
Resources:
  DevAWSomePhotoApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: 'prod'
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: !Sub 's3://${DeployBucketName}/swagger.yaml'
  DevAWSomePhotoList:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: js/index.handler
      Runtime: nodejs6.10
      Description: ''
      MemorySize: 128
      Timeout: 15
      Role: !GetAtt DevAWSomePhotoListRole.Arn
      Environment:
        Variables:
          BUCKET_NAME: !Sub ${PhotoAppBucketName}
      Events:
        GetAPI:
          Type: Api
          Properties:
            Path: /photos/{username}
            Method: GET
            RestApiId: !Ref DevAWSomePhotoApi
  DevAWSomePhotoListPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName:
        'Fn::GetAtt':
          - DevAWSomePhotoList
          - Arn
      Principal: apigateway.amazonaws.com
      SourceArn:
        'Fn::Sub': 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*/*/*'
  DevAWSomePhotoListRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "dynamodb_access_lambda"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "cloudwatch:*"
                Resource: "*"
              - Effect: "Allow"
                Action: "logs:*"
                Resource: "*"
              - Effect: "Allow"
                Action: "dynamodb:*"
                Resource: "*"
              - Effect: "Allow"
                Action: "s3:*"
                Resource: "*"
Outputs:
  ApiUrl:
    Description: URL of your API endpoint
    Value: !Sub https://${DevAWSomePhotoApi}.execute-api.${AWS::Region}.amazonaws.com/prod
```

最後に、作成したパッケージを使用してリソースを作成します。

```text
> aws cloudformation deploy --template-file ./packaged.yaml \
  --stack-name DevAWSomePhotoAPIStack \
  --parameter-overrides  \
  PhotoAppBucketName=devawsome-photo-mine \
  DeployBucketName=devawsome-deploy-mine \
  --capabilities CAPABILITY_IAM  \
  --region us-west-2
```
※「DevAWSomePhotoAPIStack」はスタック（CloudFormationで作成したリソース群）名になります

アップロードした画像を保存するバケットと今回のデプロイ用のバケットの両方を指定しています。

# エンドポイントの確認
分類結果を取得するためのエンドポイントのURLは、以下のようにして確認します。

1. マネージメントコンソールを開く
2. 「CloudFormation」を選択<br/>![image.png](https://qiita-image-store.s3.amazonaws.com/0/130771/d23a59fd-d5ac-a3be-24dc-5203ae2684c3.png)
3. 作成したスタック（上記の例では「DevAWSomePhotoAPIStack」）をチェック
4. 「出力」タブを選択
5. 「ApiUrl」の値を保存（後で使用します） 

# アプリケーションでの設定
[何回か前](https://qiita.com/mine820/items/0267ce3763612786a62b)で作成したJavaScriptの以下の部分を修正します。

```javascript
API: {
    endpoints: [{
        name: 'DevAWSomeDayAPI',
        endpoint: 'https://abcdefghij.execute-api.us-west-2.amazonaws.com/prod',
        region: 'us-west-2'
    }]
},
```

「endpoint」のところに、先程保存したURLを設定します。
呼び出す部分は以下のようになります。

```javascript
// Task: API Call
console.log("Define Get photos API Call here.");

let apiName = 'DevAWSomeDayAPI';
let path = `/photos/${username}`;
let myInit = {
    headers: {},
    response: true
};
API.get(apiName, path, myInit).then(response => {
        var data = response.data;
        console.log("API call GetPhotos is succeeded!");
        this.setState({
            photos: data.Items
        });
    })
    .catch(err => {
        console.log(err);
        alert("Err: " + err);
    });
```

# Cognitoでの設定
API Gatewayを実行できるように、Cognitoにもアクセス権限の設定をしてあげる必要があります。
やり方は[以前](https://qiita.com/mine820/items/cbfd7913bccd784591ba)と同じです。
※実際にはIAMロールで設定します

1. マネージメントコンソールを開く
2. 「IAM」を選択
3. 「ロール」を選択
4. 以前設定を変更したロールを探し、選択
5. 「ポリシーのアタッチ」を選択
6. 「AmazonAPIGatewayInvokeFullAccess」をチェック
7. 「ポリシーのアタッチ」ボタンをクリック

# まとめ
これで処理はすべて実装されたことになります。
CloudFormationの定義ファイルやテンプレートファイルの内容が理解できていないのですが、このあたりはおいおい勉強していきたいと思います。