Amazon APIGatewayでCI/CDパイプラインと親和性を高めてIaC運用するにはどうするべきか

# はじめに
Amazon APIGatewayは複数のLambda関数や別のALB配下のアプリケーションとかを簡単に統合できて便利。
でも、色々なデプロイ方法が競合して死ぬことってないの？SAMとCloudFormation両方で更新したらどうなってしまうの？というのがよく分からないので、実験してどのように運用すれば良いのかを考えてみる。

サンプルプログラムは、以下の仕様で動作する。

- 初回デプロイ
    - ```/name```のパスに対するPUTメソッドでクエリパラメータとして```id```と```name```を受け取り、DynamoDBにid, name, version(```1.0```)を書き込む
    - ```/names```のパスに対するGETメソッドでクエリパラメータとして```id```を受け取り、DynamoDBからidで指定されたキーを検索してnameを応答する
- 2回目のデプロイ
    - ```/name```のパスに対するPUTメソッドのDynamoDBに書き込むレコードをversion(```2.0```)に変更する
    - ```/names```のパスに対するGETメソッドの応答にversionを追加する

DynamoDBについては、SAM上で```AWS::DynamoDB::Table```を定義するという方法もあるが、基本/共通リソースとサービスリソースを分割した方が良かろうということで、先にDynamoDBテーブルを作成する構成にしている。

あと、Lambda関数中でDynamoDBを扱うので、サービスロールにはDynamoDBを触れるようなIAMポリシーをアタッチしておく。

# DynamoDBの準備
何でも良いが、↓こんな感じで用意しておく。

<details><summary>01_createDynamlDB.yml</summary><div>

```YAML:01_createDynamlDB.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create DynamoDB template for ApigwTest2
  
Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2"
  TableNameSuffix:
    Description: "DynamoDB Table name suffix"
    Type: "String"
    Default: "-Table"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "DynamoDB configuration"
        Parameters:
          - TableNameSuffix

Resources:
  # ------------------------------------------------------------#
  #  DynamoDB
  # ------------------------------------------------------------#
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties: 
      TableName: !Sub ${Prefix}${TableNameSuffix}
      KeySchema: 
        - AttributeName: id
          KeyType: HASH
      AttributeDefinitions: 
        - AttributeName: id
          AttributeType: S
      ProvisionedThroughput: 
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      Tags: 
        - Key: Name
          Value: !Sub ${Prefix}
```

</div></details>

# イベントソースで対応する
SAMテンプレート中の```AWS::Serverless::Function```の中で```Events```指定するのが一番お手軽なので試してみる。

<details><summary>SetName.yml</summary><div>

```YAML:SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-SetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix

Globals:
    Function:
        Timeout: 60

Resources:
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Method: put
            Path: /name
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name']})
            except ClientError as e:
              if e.response['Error']['Message'] == 'The provided key element does not match the schema':
                response_statuscode = 404
              else:
                pprint.pprint(e.response['Error']['Message'])
                response_statuscode = 500
            else:
              response_statuscode = 200

            return {
                'statusCode': response_statuscode,
                'isBase64Encoded': 'false'
            }
```

</div></details>

ポイントは以下の部分。

```YAML
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Method: put
            Path: /name
```

これでAPIGatewayをイベントソースとして、以下の様に取り込むことができる。
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/0a9373dc-8913-e3cd-ac7c-4cd3830c4d1f.png)


だがしかし、この方法だと、APIGatewayの名前が勝手に決まってしまう。
※しかも、割と良い感じの名前になっているが、どうやって抽出しているのだろうか……
これでは、複数のLambdaを1つのAPIGatewayに統合できないので、別の手段が必要だ。

もう少し細かい話は以下のサイトに書いてある。
結局、公式に「変更はできない」と書いてある。ついでに、ステージはProdが適用されるからね！とも書いてあるように見える。
[CloudFormation Resources Generated By SAM](https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api)

たしかに、このSAMテンプレートをCloudFormationに流し込むと、見覚えのないリソースを勝手に作っていた。なるほどこういう仕組みだったのか。
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/7767953a-743c-aa57-94f2-53591432a2c0.png)

# ちゃんとRESTAPIのリソースを定義する
自動作成されるリソースなんぞには任せていられん！
ということで、以下の様にSAMテンプレートを修正する。

リソースを手動作成する場合、作ったAPIGatewayに対するLambdaのパーミッションを設定しなければいけない。

あとは、API GatewayとLambdaの紐付けをSwaggerで定義してあげないといけないようだ。
うーむ、なかなか面倒だ……。
SwaggerはAWS拡張形式で書く必要がある```x-amazon-apigateway-integration```については、ちゃんと[公式のドキュメント](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html)を読もう。PUTだとかGETを扱うからといって、プロキシのメソッドまで変えてしまうと動かなくなる。

<details><summary>SetName.yml</summary><div>

```YAML:SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-GetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"
  APIGatewayName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "ApigwTest2"
  APIGatewayStageName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "default"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix
      - Label:
          default: "API Gateway Configuration"
        Parameters:
          - APIGatewayName
          - APIGatewayStageName

Globals:
    Function:
        Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  API Gateway
  # ------------------------------------------------------------#
  APIGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub ${APIGatewayName}
      StageName: !Sub ${APIGatewayStageName}
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "Created by SAM template"
          version: "1.0.0"
          title: "ApiGatewayTest2"
        basePath: "/default"
        schemes:
          - "https"
        paths:
          /names:
            get:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
        definitions:
          Empty:
            type: "object"
            title: "Empty Schema"
      Tags: 
        Key: Name
        Value: !Sub ${APIGatewayName}
        
  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Path: /names
            Method: get
            RestApiId: !Ref APIGateway
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.get_item(Key={'id': event['queryStringParameters']['id']})
            except ClientError as e:
              response_statuscode = 500
              pprint.pprint(e.response['Error']['Message'])
            else:
              if 'Item' not in response:
                response_statuscode = 404
                response_body = json.dumps({'name': 'null'})
              else:
                response_statuscode = 200
                response_body =json.dumps({'name': response['Item']['name']})

            return {
                'statusCode': response_statuscode,
                'body': response_body,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunction
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com
```

</div></details>

だが、これで任意の名前でAPI Gatewayを作成することができるようになった。
でも、なぜかこれ、指定したステージ名じゃなくて勝手に```Stage```とかいうのまで作ってしまうんだよなぁ……バグなのか？？
![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/f28ccd5b-43cb-867c-bae0-38921dce63d6.png)

で、ここからが本題。
別のLambda関数のSAMテンプレートから、上手くこのAPI Gatewayを更新することができるだろうか。

これまで作ってきたDynamoDBにレコードを挿入するAPIに対して、同じAPI Gatewayにレコードを参照するAPIをぶら下げてみよう。

<details><summary>GetName.yml</summary><div>

```YAML:GetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-GetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"
  APIGatewayName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "ApigwTest2"
  APIGatewayStageName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "default"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix
      - Label:
          default: "API Gateway Configuration"
        Parameters:
          - APIGatewayName
          - APIGatewayStageName

Globals:
    Function:
        Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  API Gateway
  # ------------------------------------------------------------#
  APIGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub ${APIGatewayName}
      StageName: !Sub ${APIGatewayStageName}
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "Created by SAM template"
          version: "1.0.0"
          title: "ApiGatewayTest2"
        basePath: "/default"
        schemes:
          - "https"
        paths:
          /names:
            get:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
        definitions:
          Empty:
            type: "object"
            title: "Empty Schema"
      Tags: 
        Key: Name
        Value: !Sub ${APIGatewayName}
        
  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Path: /names
            Method: get
            RestApiId: !Ref APIGateway
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.get_item(Key={'id': event['queryStringParameters']['id']})
            except ClientError as e:
              response_statuscode = 500
              pprint.pprint(e.response['Error']['Message'])
            else:
              if 'Item' not in response:
                response_statuscode = 404
                response_body = json.dumps({'name': 'null'})
              else:
                response_statuscode = 200
                response_body =json.dumps({'name': response['Item']['name']})

            return {
                'statusCode': response_statuscode,
                'body': response_body,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunction
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com
```

</div></details>

さて、これでCloudFormationを実行してみると、
![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c504f282-5c97-da62-5c53-a5ab1f8910da.png)

残念なことに、同じAPI名が二つ作られてしまった。ガーン……。

# APIGatewayのリソースを分離したSAMテンプレートにする
結論としては、この方法でやりたいことを実現できた。

以下の構成とする。
- GetName.yml … Lambda関数①と、Lambda関数へのアクセス許可を定義
- SetName.yml … Lambda関数②と、Lambda関数へのアクセス許可を定義
- APIGateway.yml … Lambda関数①②にアクセスするAPI Gateway(REST API)の定義
たぶん、API Gateway.ymlはCloudFormationテンプレートでもTerraformのHCLでも作ることは可能だが、色々と面倒な手間を省くことを考えると、ここもSAMにしてしまった方が手っ取り早いと思う。

## Lambda関数の定義
以下の様にSAMテンプレートを定義する。

テンプレート中の```DeploymentPreference```プロパティで指定している```CodeDeploy.LambdaCanaryHalf3minutes```はデフォルトには無い。検証のために今回作成しているので、ここは適当なものを作っておく。

<details><summary>GetName.yml</summary><div>

```YAML:GetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-GetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix

Globals:
  Function:
    Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Description: Created by SAM template
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      AutoPublishAlias: Prod
      DeploymentPreference: 
        Type: CodeDeploy.LambdaCanaryHalf3minutes
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.get_item(Key={'id': event['queryStringParameters']['id']})
            except ClientError as e:
              response_statuscode = 500
              pprint.pprint(e.response['Error']['Message'])
            else:
              if 'Item' not in response:
                response_statuscode = 404
                response_body = json.dumps({'name': 'null'})
              else:
                response_statuscode = 200
                response_body =json.dumps({'name': response['Item']['name']})

            return {
                'statusCode': response_statuscode,
                'body': response_body,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunctionAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

Outputs:
  LambdaFunction:
    Description: ApigwTest2 GetName Lambda function ARN
    Value: !Ref LambdaFunctionAliasProd
    Export:
      Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn
```

</div></details>

<details><summary>SetName.yml</summary><div>

```YAML:SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-SetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix

Globals:
  Function:
    Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Description: Created by SAM template
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      AutoPublishAlias: Prod
      DeploymentPreference: 
        Type: CodeDeploy.LambdaCanaryHalf3minutes
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '1.0'})
            except ClientError as e:
              if e.response['Error']['Message'] == 'The provided key element does not match the schema':
                response_statuscode = 404
              else:
                pprint.pprint(e.response['Error']['Message'])
                response_statuscode = 500
            else:
              response_statuscode = 200

            return {
                'statusCode': response_statuscode,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunctionAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

Outputs:
  LambdaFunction:
    Description: ApigwTest2 SetName Lambda function ARN
    Value: !Ref LambdaFunctionAliasProd
    Export:
      Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn
```

</div></details>

ポイントは

```YAML
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunctionAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com
```

の部分。SAMテンプレートでは、```AWS::Serverless::Function```の```Event```プロパティについて、中で良い感じにAPI Gatewayのリソースを作ってアクセス許可も与えるように動いてくれる(というのが```Transform: AWS::Serverless-2016-10-31```の正体。こういう便利なことをやってくれる反面、本質的な動作が分かりにくくなる……)が、今回はAPI Gatewayのリソースを切り出しているので、自力でアクセス許可を与える必要がある。

参照している```LambdaFunctionAliasProd```というリソースは自分では定義していないが、これもSAMテンプレートの変換の中で良い感じに動いてくれる部分。詳細は以前[この記事](https://qiita.com/neruneruo/items/73906cdc3c2aa699644f)に書いた。

また、API Gateway側で作ったLambda関数のARNが必要になるため、

```YAML
Outputs:
  LambdaFunction:
    Description: ApigwTest2 GetName Lambda function ARN
    Value: !Ref LambdaFunctionAliasProd
    Export:
      Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn
```

で参照可能な状態にしておく。

実行は、

```
$ aws cloudformation deploy --template-file GetName.yml --stack-name ApigwTest2-GetName --capabilities CAPABILITY_IAM
```

```
$ aws cloudformation deploy --template-file SetName.yml --stack-name ApigwTest2-SetName --capabilities CAPABILITY_IAM
```

な感じで行う。アクセス許可を変更するために、```--capabilities CAPABILITY_IAM```が必要。

## API Gateway(REST API)の定義

たいして難しいわけではない。
```x-amazon-apigateway-integration```で、前節のLambda関数のARNを表現するのが少し面倒なくらい。

<details><summary>APIGateway.yml</summary><div>

```YAML:APIGateway.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description:
  Create APIGateway template for ApigwTest2
  
Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2"
  RESTApiNameSuffix:
    Description: "REST API name suffix"
    Type: "String"
    Default: "-RESTAPI"
  RESTApiStageName:
    Description: "REST API stage name suffix"
    Type: "String"
    Default: "default"
  GetLambdaFunctionNameSuffix:
    Description: "Lambda Function name suffix"
    Type: "String"
    Default: "-GetName-LambdaFunction"
  SetLambdaFunctionNameSuffix:
    Description: "Lambda Function name suffix"
    Type: "String"
    Default: "-SetName-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "API Gateway configuration"
        Parameters:
          - RESTApiNameSuffix
          - RESTApiStageName
          - GetLambdaFunctionNameSuffix
          - SetLambdaFunctionNameSuffix

Resources:
  # ------------------------------------------------------------#
  #  API Gateway
  # ------------------------------------------------------------#
  APIGATEWAY:
    Type: AWS::Serverless::Api
    Properties: 
      Name: !Sub ${Prefix}${RESTApiNameSuffix}
      StageName: !Sub ${RESTApiStageName}
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "Created by CloudFormation template"
          version: "1.0.0"
          title: "ApiGatewayTest2"
        basePath: "/default"
        schemes:
          - "https"
        paths:
          /names:
            get:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri: !Sub
                  - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetLambdaFunctionArn}/invocations
                  - GetLambdaFunctionArn: {'Fn::ImportValue': !Sub '${Prefix}${GetLambdaFunctionNameSuffix}-Arn'}
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
          /name:
            put:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri: !Sub
                  - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SetLambdaFunctionArn}/invocations
                  - SetLambdaFunctionArn: {'Fn::ImportValue': !Sub '${Prefix}${SetLambdaFunctionNameSuffix}-Arn'}
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
        definitions:
          Empty:
            type: "object"
            title: "Empty Schema"
      EndpointConfiguration: REGIONAL
      Tags: 
        Key: Name
        Value: !Sub ${Prefix}
```

</div></details>

これで

```
$ aws cloudformation deploy --stack-name ApigwTest2-APIGateway --template-file APIGateway.yml
```

すると、以下のように複数のパス・メソッドを定義したAPI Gatewayを作れる。

![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/d0965652-32c8-d3d9-85e2-73e886fc4cda.png)

![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c07b6cd4-70db-e45c-2d5e-fa560b2154eb.png)

![キャプチャ7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/6551ccab-08fd-21dd-bdee-ef49cb79df7b.png)

## アプリケーションのデプロイを行ってみる

例えば、以下のような感じでアプリケーションを修正する。

```diff:GetName.ymlの修正内容
65c64
<                 response_body = json.dumps({'name': 'null'})
---
>                 response_body = json.dumps({'name': 'null', 'version': 'null'})
68c67
<                 response_body =json.dumps({'name': response['Item']['name']})
---
>                 response_body =json.dumps({'name': response['Item']['name'], 'version': response['Item']['version']})
```

```diff:SetName.ymlの修正内容
58c58
<               response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '1.0'})
---
>               response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '2.0'})
```

それぞれ、もう一度 ```aws cloudformation deploy```で確認してみると、ちゃんとCanaryなリリースが行われているし、それぞれの機能やデプロイにも干渉しなかった。

SAMテンプレートで完結しないもやっと感はあるが、アプリケーションの大幅改修が無い限りは、API GatewayのREST API定義とのライフサイクルが異なる気がするので、これで特に大きな問題にはならないだろう。