AWS CloudFormationでLamdaを排他制御するStep Functionsを構築しよう

# はじめに
AWS CloudFormationを利用してLamdaを排他制御するStep Functions構築のテンプレートのサンプルです。
API GatewayはStep Functionsを使用します。また、Step FunctionsではSQSとLambdaにて排他制御を実現します。
さらに、Lambdaは、S3アーティファクトを使用します。
API Gatewayは非同期処理となり、実行状況をポーリングで確認できるようにしています。
また、API GatewayにはCORSの設定をしています。

テンプレートの概要が分からない場合は、[はじめてのAWS CloudFormationテンプレートを理解する](https://qiita.com/miyabiz/items/eb7b0fa1675b5199b9b7)を参考にしてください。

コードは[GitHub](https://github.com/miyabiz-room/CloudFormation-Template/tree/step-funcs-01)にもあります。

今回は、akane というシステムの dev 環境を想定しています。
同じ構成で違う環境を作成する場合は、{環境名}-parameters.jsonを別途作成します。

```text:ディレクトリ構成
akane (システム)
  ├── apigw (スタック)
  │   ├── apigw.yml (CFnテンプレート)
  │   └── dev-parameters.json (dev 環境のパラメータ)
  ├── lambda (スタック)
  │   ├── createSQSMessage (スタック)
  │   │   ├── code
  │   │   │   └── createSQSMessage.py (S3アーティファクトソース)
  │   │   ├── createSQSMessage.yml (CFnテンプレート)
  │   │   ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
  │   │   ├── dev-parameters.json (dev 環境のパラメータ)
  │   │   ├── mkzip.sh (S3アーティファクト作成シェル)
  │   │   └── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
  │   ├── getLock (スタック)
  │   │   ├── code
  │   │   │   └── getLock.py (S3アーティファクトソース)
  │   │   ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
  │   │   ├── dev-parameters.json (dev 環境のパラメータ)
  │   │   ├── getLock.yml (CFnテンプレート)
  │   │   ├── mkzip.sh (S3アーティファクト作成シェル)
  │   │   └── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
  │   ├── setLock (スタック)
  │   │   ├── code
  │   │   │   └── setLock.py (S3アーティファクトソース)
  │   │   ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
  │   │   ├── dev-parameters.json (dev 環境のパラメータ)
  │   │   ├── mkzip.sh (S3アーティファクト作成シェル)
  │   │   ├── setLock.yml (CFnテンプレート)
  │   │   └── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
  │   └── waitSecs (スタック)
  │       ├── code
  │       │   └── waitSecs.py (S3アーティファクトソース)
  │       ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
  │       ├── dev-parameters.json (dev 環境のパラメータ)
  │       ├── mkzip.sh (S3アーティファクト作成シェル)
  │       ├── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
  │       └── waitSecs.yml (CFnテンプレート)
  ├── role (スタック)
  │   ├── dev-parameters.json (dev 環境のパラメータ)
  │   └── role.yml (CFnテンプレート)
  ├── s3 (スタック)
  │   ├── all-parameters.json (all) 環境のパラメータ)
  │   └── s3.yml (CFnテンプレート)
  ├── sqs (スタック)
  │   ├── dev-parameters.json (dev 環境のパラメータ)
  │   └── sqs.yml (CFnテンプレート)
  └── step-funcs (スタック)
      ├── dev-parameters.json (dev 環境のパラメータ)
      └── step-funcs.yml (CFnテンプレート)
```

# AWS リソース構築内容
  1. apigwスタック
      - CloudWatch ログのロール
      - API Gateway
      - 使用量プラン
      - API キー
  2. lambdaスタック
      - Lambda (createSQSMessage, getLock, setLock, waitSecs)
  3. roleスタック
      - Lambdaロール
      - Step Functionsロール
      - API Gatewayロール
  4. s3スタック
      - s3バケット (akane-all-s3-artifacts)
      - バケットポリシー (s3:GetObject)
  5. sqsスタック
      - SQSキュー
      - アクセスポリシー
  6. step-funcsスタック
      - Step Functionsステートマシン

# 実行環境の準備
[AWS CloudFormationを動かすためのAWS CLIの設定](https://qiita.com/miyabiz/items/fed11796f0ea2b7608f4)を参考にしてください。

# AWS リソース構築手順
1.  下記を実行してスタックを作成

    ```
    ./create_stacks.sh
    ```

2.  下記を実行してLambdaの動作を確認

    ```
    ./test_lambda.sh
    ```

3.  下記を実行してStep Functionsの動作を確認

    ```
    ./test_step-funcs.sh
    ```

4.  下記を実行してAPI Gatewayの動作を確認

    ```
    ./test_apigw.sh <URL> <API_KEY>
    ```

5.  下記を実行してスタックを削除

    ```
    ./delete_stacks.sh
    ```

# 構築テンプレート
**1. s3スタック**
 
```yaml:s3.yml
AWSTemplateFormatVersion: 2010-09-09
Description: S3 For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.

# Mappings

# Conditions

# Transform

Resources:
  # S3 Bucket作成
  akaneS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      BucketName: !Sub
        - ${SystemName}-${EnvType}-s3-artifacts
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-s3-artifacts
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  # S3 BucketPolicy作成
  akaneS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    DependsOn: akaneS3Bucket
    Properties:
      Bucket: !Ref akaneS3Bucket
      PolicyDocument:
        Statement:
          - Action:
              - s3:GetObject
            Effect: Allow
            Resource: !Join
              - ''
              - - 'arn:aws:s3:::'
                - !Ref akaneS3Bucket
                - /*
            Principal:
              AWS: '*'

Outputs:
  akaneS3Bucket:
    Value: !Ref akaneS3Bucket
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-s3-artifacts
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
```

```json:all-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "all"
        }
    ]
}
```

**2. roleスタック**
 
```yaml:role.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Role For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.

# Mappings

# Conditions

# Transform

Resources:
  # Lambda Role 作成
  akaneRoleForLambda:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSQSFullAccess
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: '/'
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType

  # StepFunctions Role 作成
  akaneRoleForStepFunctions:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - ${SystemName}-${EnvType}-role-step-funcs-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-role-step-funcs-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Join
                  - ''
                  - - 'states.'
                    - !Ref AWS::Region
                    - '.amazonaws.com'
            Action:
              - sts:AssumeRole
      Path: '/'
      Policies:
        - PolicyName: LambdaInvokeFunction
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: !Join
                  - ''
                  - - 'arn:aws:lambda:'
                    - !Ref AWS::Region
                    - ':'
                    - !Ref AWS::AccountId
                    - ':function:'
                    - !Ref SystemName
                    - '-'
                    - !Ref EnvType
                    - '-*'
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-role-step-funcs-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType

  # ApiGateway Role 作成
  akaneRoleForApiGateway:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - ${SystemName}-${EnvType}-role-apigw-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-role-apigw-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - apigateway.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: '/'
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-role-apigw-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType

Outputs:
  akaneRoleForLambdaArn:
    Value: !GetAtt akaneRoleForLambda.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
  akaneRoleForStepFunctionsArn:
    Value: !GetAtt akaneRoleForStepFunctions.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-role-step-funcs-arn-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
  akaneRoleForApiGatewayArn:
    Value: !GetAtt akaneRoleForApiGateway.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-role-apigw-arn-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        }
    ],
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ]
}
```

**3. sqsスタック**

```yaml:sqs.yml
AWSTemplateFormatVersion: 2010-09-09
Description: SQS For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.

# Mappings

# Conditions

# Transform

Resources:
  akaneSQSQueue:
    Type: AWS::SQS::Queue
    Properties:
      DelaySeconds: 0
      MaximumMessageSize: 262144
      MessageRetentionPeriod: 1209600
      QueueName: !Sub
        - ${SystemName}-${EnvType}-sqs-lock
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      ReceiveMessageWaitTimeSeconds: 0
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-sqs-lock
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      VisibilityTimeout: 660

  akaneSQSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref akaneSQSQueue
      PolicyDocument:
        Version: 2012-10-17
        Id: __default_policy_ID
        Statement:
          - Sid: __owner_statement
            Action: SQS:*
            Effect: Allow
            Resource: !GetAtt akaneSQSQueue.Arn
            Principal:
              AWS: !Join
                - ''
                - - 'arn:aws:iam::'
                  - !Ref AWS::AccountId
                  - ':root'
          - Sid: __sender_statement
            Action: SQS:SendMessage
            Effect: Allow
            Resource: !GetAtt akaneSQSQueue.Arn
            Principal:
              AWS:
                Fn::ImportValue: !Sub
                  - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
                  - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          - Sid: __receiver_statement
            Action:
              - SQS:ChangeMessageVisibility
              - SQS:DeleteMessage
              - SQS:ReceiveMessage
            Effect: Allow
            Resource: !GetAtt akaneSQSQueue.Arn
            Principal:
              AWS:
                Fn::ImportValue: !Sub
                  - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
                  - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}

Outputs:
  akaneSQSQueueName:
    Value: !GetAtt akaneSQSQueue.QueueName
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-sqs-lock-queue-name
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        }
    ]
}
```

**4. lambdaスタック (waitSecs)**

```yaml:lambda.yml(waitSecs)
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  ArtifactS3Bucket:
    Type: String
  LambdaName:
    Type: String
  LambdaMemorySize:
    Type: Number
  LambdaRuntime:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  # Lambda作成
  akaneLambdaWaitSecs:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref ArtifactS3Bucket
        S3Key: !Sub
          - code-lambda-${LambdaName}.zip
          - {LambdaName: !Ref LambdaName}
      Description: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      FunctionName: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Handler: !Sub
        - ${LambdaName}.handler
        - {LambdaName: !Ref LambdaName}
      MemorySize: !Ref LambdaMemorySize
      Role:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
          - { SystemName: !Ref SystemName, EnvType: !Ref EnvType }
      Runtime: !Ref LambdaRuntime
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-lambda-${LambdaName}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      Timeout: 900
      TracingConfig:
        Mode: PassThrough

Outputs:
  akaneLambdaWaitSecs:
    Value: !Ref akaneLambdaWaitSecs
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}

  akaneLambdaWaitSecsArn:
    Value: !GetAtt akaneLambdaWaitSecs.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-arn-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "ArtifactS3Bucket",
            "ParameterValue": "akane-all-s3-artifacts"
        },
        {
            "ParameterKey": "LambdaName",
            "ParameterValue": "waitSecs"
        },
        {
            "ParameterKey": "LambdaMemorySize",
            "ParameterValue": "128"
        },
        {
            "ParameterKey": "LambdaRuntime",
            "ParameterValue": "python3.8"
        }
    ]
}
```

**5. lambdaスタック (getLock)**

```yaml:lambda.yml(getLock)
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  ArtifactS3Bucket:
    Type: String
  LambdaName:
    Type: String
  LambdaMemorySize:
    Type: Number
  LambdaRuntime:
    Type: String
  VisibilityTimeout:
    Type: Number
  RetryWaitSecs:
    Type: Number

# Mappings

# Conditions

# Transform

Resources:
  # Lambda作成
  akaneLambdaGetLock:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref ArtifactS3Bucket
        S3Key: !Sub
          - code-lambda-${LambdaName}.zip
          - {LambdaName: !Ref LambdaName}
      Description: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Environment:
        Variables:
          SQS_QUEUE_NAME:
            Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-sqs-lock-queue-name
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          VISIBILITY_TIMEOUT: !Ref VisibilityTimeout
          RETRY_WAIT_SECS: !Ref RetryWaitSecs
      FunctionName: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Handler: !Sub
        - ${LambdaName}.handler
        - {LambdaName: !Ref LambdaName}
      MemorySize: !Ref LambdaMemorySize
      Role:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
          - { SystemName: !Ref SystemName, EnvType: !Ref EnvType }
      Runtime: !Ref LambdaRuntime
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-lambda-${LambdaName}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      Timeout: 900
      TracingConfig:
        Mode: PassThrough

Outputs:
  akaneLambdaGetLock:
    Value: !Ref akaneLambdaGetLock
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}

  akaneLambdaGetLockArn:
    Value: !GetAtt akaneLambdaGetLock.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-arn-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "ArtifactS3Bucket",
            "ParameterValue": "akane-all-s3-artifacts"
        },
        {
            "ParameterKey": "LambdaName",
            "ParameterValue": "getLock"
        },
        {
            "ParameterKey": "LambdaMemorySize",
            "ParameterValue": "128"
        },
        {
            "ParameterKey": "LambdaRuntime",
            "ParameterValue": "python3.8"
        },
        {
            "ParameterKey": "VisibilityTimeout",
            "ParameterValue": "60"
        },
        {
            "ParameterKey": "RetryWaitSecs",
            "ParameterValue": "30"
        }
    ]
}
```

**6. lambdaスタック (setLock)**

```yaml:lambda.yml(setLock)
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  ArtifactS3Bucket:
    Type: String
  LambdaName:
    Type: String
  LambdaMemorySize:
    Type: Number
  LambdaRuntime:
    Type: String
  LambdaNameToGetLock:
    Type: String
  SQSMessageBody:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  # Lambda作成
  akaneLambdaSetLock:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref ArtifactS3Bucket
        S3Key: !Sub
          - code-lambda-${LambdaName}.zip
          - {LambdaName: !Ref LambdaName}
      Description: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Environment:
        Variables:
          SQS_QUEUE_NAME:
            Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-sqs-lock-queue-name
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          LAMBDA_NAME_TO_GET_LOCK: !Ref LambdaNameToGetLock
          SQS_MESSAGE_BODY: !Ref SQSMessageBody
      FunctionName: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Handler: !Sub
        - ${LambdaName}.handler
        - {LambdaName: !Ref LambdaName}
      MemorySize: !Ref LambdaMemorySize
      Role:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
          - { SystemName: !Ref SystemName, EnvType: !Ref EnvType }
      Runtime: !Ref LambdaRuntime
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-lambda-${LambdaName}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      Timeout: 900
      TracingConfig:
        Mode: PassThrough

Outputs:
  akaneLambdaSetLock:
    Value: !Ref akaneLambdaSetLock
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}

  akaneLambdaSetLockArn:
    Value: !GetAtt akaneLambdaSetLock.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-arn-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "ArtifactS3Bucket",
            "ParameterValue": "akane-all-s3-artifacts"
        },
        {
            "ParameterKey": "LambdaName",
            "ParameterValue": "setLock"
        },
        {
            "ParameterKey": "LambdaMemorySize",
            "ParameterValue": "128"
        },
        {
            "ParameterKey": "LambdaRuntime",
            "ParameterValue": "python3.8"
        },
        {
            "ParameterKey": "LambdaNameToGetLock",
            "ParameterValue": "getLock"
        },
        {
            "ParameterKey": "SQSMessageBody",
            "ParameterValue": "ExclusionControlLock"
        }
    ]
}
```

**7. lambdaスタック (createSQSMessage)**

```yaml:lambda.yml(createSQSMessage)
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  ArtifactS3Bucket:
    Type: String
  LambdaName:
    Type: String
  LambdaMemorySize:
    Type: Number
  LambdaRuntime:
    Type: String
  SQSMessageBody:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  # Lambda作成
  akaneLambdaCreateSQSMessage:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref ArtifactS3Bucket
        S3Key: !Sub
          - code-lambda-${LambdaName}.zip
          - {LambdaName: !Ref LambdaName}
      Description: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Environment:
        Variables:
          SQS_QUEUE_NAME:
            Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-sqs-lock-queue-name
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          SQS_MESSAGE_BODY: !Ref SQSMessageBody
      FunctionName: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Handler: !Sub
        - ${LambdaName}.handler
        - {LambdaName: !Ref LambdaName}
      MemorySize: !Ref LambdaMemorySize
      Role:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-role-lambda-arn-${AWS::Region}
          - { SystemName: !Ref SystemName, EnvType: !Ref EnvType }
      Runtime: !Ref LambdaRuntime
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-lambda-${LambdaName}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      Timeout: 900
      TracingConfig:
        Mode: PassThrough

  # スケジュール 作成
  akaneEventsRule:
    Type: AWS::Events::Rule
    Properties:
      Description: !Sub
        - ${SystemName}-${EnvType}-rule-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Name: !Sub
        - ${SystemName}-${EnvType}-rule-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      ScheduleExpression: "rate(24 hours)"
      State: ENABLED
      Targets:
        - Arn: !GetAtt akaneLambdaCreateSQSMessage.Arn
          Id: !Sub
            - ${SystemName}-${EnvType}-rule-lambda-${LambdaName}
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}

  # LambdaPermission 作成
  akaneLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt akaneLambdaCreateSQSMessage.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt akaneEventsRule.Arn

Outputs:
  akaneLambdaCreateSQSMessage:
    Value: !Ref akaneLambdaCreateSQSMessage
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName }

  akaneLambdaCreateSQSMessageArn:
    Value: !GetAtt akaneLambdaCreateSQSMessage.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-arn-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "ArtifactS3Bucket",
            "ParameterValue": "akane-all-s3-artifacts"
        },
        {
            "ParameterKey": "LambdaName",
            "ParameterValue": "createSQSMessage"
        },
        {
            "ParameterKey": "LambdaMemorySize",
            "ParameterValue": "128"
        },
        {
            "ParameterKey": "LambdaRuntime",
            "ParameterValue": "python3.8"
        },
        {
            "ParameterKey": "SQSMessageBody",
            "ParameterValue": "ExclusionControlLock"
        }
    ]
}
```

**8. step-funcsスタック**

```yaml:step-funcs.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Step Functions For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  StepFuncsName:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  akanewaitSecsStepFunctionsStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      StateMachineName: !Sub
        - ${SystemName}-${EnvType}-step-funcs-${StepFuncsName}
        - { SystemName: !Ref SystemName, EnvType: !Ref EnvType, StepFuncsName: !Ref StepFuncsName }
      DefinitionString: !Sub
        - |
            {
              "Comment": "${StepFuncsName}",
              "StartAt": "getLock",
              "States": {
                "getLock": {
                  "Type": "Task",
                  "Resource": "${LambdaArnToGetLock}",
                  "ResultPath": "$.getLock",
                  "Next": "checkLock"
                },
                "checkLock": {
                  "Type" : "Choice",
                  "Choices": [
                    {
                      "Variable": "$.getLock.isSucceededLock",
                      "NumericEquals": 0,
                      "Next": "retryWaitToGetLock"
                    },
                    {
                      "Variable": "$.getLock.isSucceededLock",
                      "NumericEquals": 1,
                      "Next": "waitSecs"
                    }
                  ],
                  "Default": "retryWaitToGetLock"
                },
                "retryWaitToGetLock": {
                  "Type": "Wait",
                  "SecondsPath": "$.getLock.retryWaitSecs",
                  "Next": "getLock"
                },
                "waitSecs": {
                  "Type": "Task",
                  "Resource": "${LambdaArnToWaitSecs}",
                  "ResultPath": "$.waitSecs",
                  "Next": "setLock"
                },
                "setLock": {
                  "Type": "Task",
                  "Resource": "${LambdaArnToSetLock}",
                  "ResultPath": "$.setLock",
                  "End": true
                }
              }
            }
        - StepFuncsName: !Ref StepFuncsName
          LambdaArnToWaitSecs:
            Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-lambda-arn-waitSecs
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          LambdaArnToGetLock:
            Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-lambda-arn-getLock
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          LambdaArnToSetLock:
            Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-lambda-arn-setLock
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      RoleArn:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-role-step-funcs-arn-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      StateMachineType: STANDARD
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      Tags:
        - Key: Name
          Value: !Sub
            - ${SystemName}-${EnvType}-step-funcs-${StepFuncsName}
            - { SystemName: !Ref SystemName, EnvType: !Ref EnvType, StepFuncsName: !Ref StepFuncsName }
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType

Outputs:
  akanewaitSecsStepFunctionsStateMachine:
    Value: !Ref akanewaitSecsStepFunctionsStateMachine
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-step-funcs-${StepFuncsName}
        - { SystemName: !Ref SystemName, EnvType: !Ref EnvType, StepFuncsName: !Ref StepFuncsName }
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "StepFuncsName",
            "ParameterValue": "waitSecs"
        }
    ]
}
```

**9. apigwスタック**

```yaml:apigw.yml
AWSTemplateFormatVersion: 2010-09-09
Description: API Gateway For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  EnvTypeCommon:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  APIGatewayStage:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  # ApiGateway CloudWatchRole 作成
  akaneCloudWatchRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - apigateway.amazonaws.com
            Action: sts:AssumeRole
      Description: !Sub
        - ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
      ManagedPolicyArns:
        -  arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
      Path: /
      RoleName: !Sub
        - ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvTypeCommon
          Value: !Ref EnvTypeCommon
  akaneApiGatewayAccount:
    Type: AWS::ApiGateway::Account
    Properties:
      CloudWatchRoleArn: !GetAtt akaneCloudWatchRole.Arn

  # ApiGateway 作成
  akaneApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
    DependsOn: akaneApiGatewayAccount
    Properties:
      Name: !Sub
        - ${SystemName}-${EnvType}-apigw
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      EndpointConfiguration:
        Types:
          - REGIONAL
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType

  # WaitSecs
  akaneApiGatewayResourceForWaitSecs:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      PathPart: 'waitsecs'
      ParentId: !GetAtt akaneApiGatewayRestApi.RootResourceId

  # WaitSecs: POST
  akaneApiGatewayMethodPostForWaitSecs:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      ResourceId: !Ref akaneApiGatewayResourceForWaitSecs
      HttpMethod: POST
      AuthorizationType: NONE
      ApiKeyRequired: true
      RequestParameters: {}
      RequestModels: {}
      MethodResponses:
        - ResponseModels: {}
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: '200'
      Integration:
        CacheNamespace: !Ref akaneApiGatewayResourceForWaitSecs
        Credentials:
          Fn::ImportValue: !Sub
            - ${SystemName}-${EnvType}-role-apigw-arn-${AWS::Region}
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        IntegrationHttpMethod: POST
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'POST'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates:
              application/json: "{\"taskid\": $input.json('$.executionArn')}"
            StatusCode: '200'
        PassthroughBehavior: WHEN_NO_TEMPLATES
        RequestParameters: {}
        RequestTemplates:
          application/json: !Sub
            - |
              {
                "input": "$util.escapeJavaScript($input.json('$'))",
                "stateMachineArn": "${stateMachineArn}"
              }
            - stateMachineArn:
                Fn::ImportValue: !Sub
                  - ${SystemName}-${EnvType}-step-funcs-waitSecs
                  - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        TimeoutInMillis: 29000
        Type: AWS
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:states:action/StartExecution

  # Institution: OPTIONS
  akaneApiGatewayMethodOptionsForWaitSecs:
    Type: AWS::ApiGateway::Method
    DependsOn: akaneApiGatewayMethodPostForWaitSecs
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      ResourceId: !Ref akaneApiGatewayResourceForWaitSecs
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      ApiKeyRequired: true
      RequestParameters: {}
      RequestModels: {}
      MethodResponses:
        - ResponseModels:
            application/json: 'Empty'
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: '200'
      Integration:
        CacheNamespace: !Ref akaneApiGatewayResourceForWaitSecs
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates: {}
            StatusCode: '200'
        PassthroughBehavior: NEVER
        RequestParameters: {}
        RequestTemplates:
          application/json: "{\"statusCode\": 200}"
        TimeoutInMillis: 29000
        Type: MOCK

  # Status
  akaneApiGatewayResourceForStatus:
    Type: AWS::ApiGateway::Resource
    DependsOn: akaneApiGatewayMethodOptionsForWaitSecs
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      PathPart: 'status'
      ParentId: !GetAtt akaneApiGatewayRestApi.RootResourceId

  # Status: POST
  akaneApiGatewayMethodPostForStatus:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      ResourceId: !Ref akaneApiGatewayResourceForStatus
      HttpMethod: POST
      AuthorizationType: NONE
      ApiKeyRequired: true
      RequestParameters:
        method.request.header.task_id: true
      RequestModels: {}
      MethodResponses:
        - ResponseModels: {}
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: '200'
      Integration:
        CacheNamespace: !Ref akaneApiGatewayResourceForStatus
        Credentials:
          Fn::ImportValue: !Sub
            - ${SystemName}-${EnvType}-role-apigw-arn-${AWS::Region}
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        IntegrationHttpMethod: POST
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'POST'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates:
              application/json: "{\"status\": $input.json('$.status'), \"result\": $input.json('$.output')}"
            StatusCode: '200'
        PassthroughBehavior: WHEN_NO_TEMPLATES
        RequestParameters: {}
        RequestTemplates:
          application/json: "{\"executionArn\": $input.json('$.taskid')}"
        TimeoutInMillis: 29000
        Type: AWS
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:states:action/DescribeExecution

  # Status: OPTIONS
  akaneApiGatewayMethodOptionsForStatus:
    Type: AWS::ApiGateway::Method
    DependsOn: akaneApiGatewayMethodPostForStatus
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      ResourceId: !Ref akaneApiGatewayResourceForStatus
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      ApiKeyRequired: true
      RequestParameters: {}
      RequestModels: {}
      MethodResponses:
        - ResponseModels:
            application/json: 'Empty'
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: '200'
      Integration:
        CacheNamespace: !Ref akaneApiGatewayResourceForStatus
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates: {}
            StatusCode: '200'
        PassthroughBehavior: NEVER
        RequestParameters: {}
        RequestTemplates:
          application/json: "{\"statusCode\": 200}"
        TimeoutInMillis: 29000
        Type: MOCK

  # ApiGateway Deployment 作成
  akaneApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: akaneApiGatewayMethodOptionsForStatus
    Properties:
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-deployment
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      RestApiId: !Ref akaneApiGatewayRestApi
  akaneApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref akaneApiGatewayDeployment
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-stage
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      MethodSettings:
        - CacheDataEncrypted: false
          CachingEnabled: false
          DataTraceEnabled: true # CloudWatch ログを有効化
          HttpMethod: '*'
          LoggingLevel: ERROR
          MetricsEnabled: false # 詳細 CloudWatch メトリクスを有効化
          ResourcePath: '/*'
      RestApiId: !Ref akaneApiGatewayRestApi
      StageName: !Ref APIGatewayStage
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw-stage
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      TracingEnabled: false # X-Ray トレースの有効化

  # ApiGateway APIキー 作成
  akaneApiGatewayKey:
    Type: AWS::ApiGateway::ApiKey
    Properties:
      Name: !Sub
        - ${SystemName}-${EnvType}-apigw-key
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-key
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Enabled: true
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw-key
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  akaneApiGatewayUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: akaneApiGatewayStage
    Properties:
      ApiStages:
        - ApiId: !Ref akaneApiGatewayRestApi
          Stage: !Ref APIGatewayStage
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-usage-plan
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      UsagePlanName: !Sub
        - ${SystemName}-${EnvType}-apigw-usage-plan
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw-usage-plan
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  akaneApiGatewayUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref akaneApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref akaneApiGatewayUsagePlan
```

```json:dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "EnvTypeCommon",
            "ParameterValue": "all"
        },
        {
            "ParameterKey": "APIGatewayStage",
            "ParameterValue": "v1"
        }
    ],
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ]
}
```

**10. 実行ファイル**

```sh:create_stacks.sh
#!/bin/sh

cd `dirname $0`

SYSTEM_NAME=akane

create_stack () {
    ENV_TYPE=$1
    STACK_NAME=$2
    aws cloudformation create-stack \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME} \
    --template-body file://./${SYSTEM_NAME}/${STACK_NAME}/${STACK_NAME}.yml \
    --cli-input-json file://./${SYSTEM_NAME}/${STACK_NAME}/${ENV_TYPE}-parameters.json

    aws cloudformation wait stack-create-complete \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}

create_stack all s3
create_stack dev role
create_stack dev sqs
./create_lambda_stacks.sh
create_stack dev step-funcs
create_stack dev apigw

exit 0
```

```sh:test_lambda.sh
#!/bin/sh

cd `dirname $0`

echo " --- \033[0;33m TEST LAMBDA \033[0;39m --- "

LAMBDA_NAME=akane-dev-lambda-waitMinutes
OUTPUT_FILE=response.json

aws lambda invoke --function-name ${LAMBDA_NAME} --log-type Tail ${OUTPUT_FILE} --query 'LogResult'  --output text |  base64 -D

cat ${OUTPUT_FILE}

exit 0
```

```sh:test_step-funcs.sh
#!/bin/sh

cd `dirname $0`

./create_sqs_message.sh

echo " --- \033[0;33m TEST STEP_FUNCS \033[0;39m --- "

STEP_FUNCS_NAME=akane-dev-step-funcs-waitSecs
SLEEP_SEC=3

get_step_funcs_result() {
  RESPONSE=$1
  ARN_ID=`echo ${RESPONSE} | jq -r '.executionArn'`

  if [ "${ARN_ID}" == "null" ]; then
    echo ${RESPONSE}
    exit 1
  fi
  echo ${ARN_ID}
  step_funcs_status=$(aws stepfunctions describe-execution --execution-arn ${ARN_ID} | jq -r '.status')
  echo " --- \033[0;35m STEP FUNCTIONS STATUS \033[0;39m --- "
  while [ ${step_funcs_status} == "RUNNING" ] ; do
    echo "     \033[0;33m${step_funcs_status} \033[0;39m "
    sleep ${SLEEP_SEC}
    step_funcs_status=$(aws stepfunctions describe-execution --execution-arn ${ARN_ID} | jq -r '.status')
  done
  if [ $step_funcs_status == "SUCCEEDED" ]; then
    echo -e "\033[0;32m  ${step_funcs_status}\033[0;39m"
    aws stepfunctions describe-execution --execution-arn ${ARN_ID} | jq -r '.output'
  else
    echo -e "\033[0;31m  ${step_funcs_status}\033[0;39m"
  fi
}

STATE_MACHINE_ARN=`aws stepfunctions list-state-machines --query 'stateMachines' | jq -r ".[] | select(.name == \"${STEP_FUNCS_NAME}\") | .stateMachineArn"`

response=`aws stepfunctions start-execution --state-machine-arn ${STATE_MACHINE_ARN}`

get_step_funcs_result "${response}"

exit 0
```

```sh:test_apigw.sh
#!/bin/sh

set -e

if [ $# -ne 2 ]; then
    echo " --- \033[0;31m [ERROR]: The following arguments are required \033[0;39m --- "
    echo "      \033[0;33m$0 <URL> <API_KEY>\033[0;39m"
    echo "      \033[0;33m<URL> Example: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1\033[0;39m"
    exit 1
fi

cd `dirname $0`

./create_sqs_message.sh

echo " --- \033[0;33m TEST APIGW \033[0;39m --- "

SLEEP_SEC=3

get_apigw_result() {
  RESPONSE=$1
  ARN_ID=`echo ${RESPONSE} | jq -r '.taskid'`

  if [ "${ARN_ID}" == "null" ]; then
    echo ${RESPONSE}
    exit 1
  fi
  echo ${ARN_ID}

  step_funcs_status=`curl -s -X POST -H 'Content-Type: application/json' \
    -H "x-api-key: ${API_KEY}" \
    -d "{\"taskid\": \"${ARN_ID}\"}" \
    "${URL}/status" | jq -r '.status'`
  echo " --- \033[0;35m STEP FUNCTIONS STATUS \033[0;39m --- "
  while [ ${step_funcs_status} == "RUNNING" ] ; do
    echo "     \033[0;33m${step_funcs_status} \033[0;39m "
    sleep ${SLEEP_SEC}
    step_funcs_status=`curl -s -X POST -H 'Content-Type: application/json' \
      -H "x-api-key: ${API_KEY}" \
      -d "{\"taskid\": \"${ARN_ID}\"}" \
      "${URL}/status" | jq -r '.status'`
  done
  if [ $step_funcs_status == "SUCCEEDED" ]; then
    echo -e "\033[0;32m  ${step_funcs_status}\033[0;39m"
    curl -s -X POST -H 'Content-Type: application/json' \
      -H "x-api-key: ${API_KEY}" \
      -d "{\"taskid\": \"${ARN_ID}\"}" \
      "${URL}/status" | jq -r '.result'
  else
    echo -e "\033[0;31m  ${step_funcs_status}\033[0;39m"
  fi
}

URL=$1
API_KEY=$2
WAIT_SECS=15

response=`curl -s -X POST -H 'Content-Type: application/json' \
-H "x-api-key: ${API_KEY}" \
-d "{\"wait_secs\": \"${WAIT_SECS}\"}" \
"${URL}/waitsecs"`
echo ${response}

get_apigw_result "${response}"

exit 0
```

```sh:delete_stacks.sh
#!/bin/sh

cd `dirname $0`

SYSTEM_NAME=akane

delete_stack () {
    ENV_TYPE=$1
    STACK_NAME=$2
    aws cloudformation delete-stack \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}

    aws cloudformation wait stack-delete-complete \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}

delete_stack dev apigw
delete_stack dev step-funcs
./delete_lambda_stacks.sh
delete_stack dev sqs
delete_stack dev role
delete_stack all s3

exit 0
```

**4. アーティファクト関連ファイル**

```sh:mkzip.sh
#!/bin/sh

cd `dirname $0`

ENV_TYPE=dev
LAMBDA_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')

FILE=../code-lambda-${LAMBDA_NAME}.zip

cd ./code

ls . |  grep -v -E ".gitignore|${LAMBDA_NAME}.py" | xargs rm -rf

rm -f ${FILE}
zip -r ${FILE} ./*

exit 0
```

```sh:upload_artifact.dev.sh
#!/bin/sh

cd `dirname $0`

BASENAME=$(basename $0)
FILENAME=${BASENAME%.*}
ENV_TYPE=${FILENAME##*.}

BUCKET_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "ArtifactS3Bucket").ParameterValue')

ARTIFACT_NAME=code-lambda-$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')

aws s3 cp ${ARTIFACT_NAME}.zip s3://${BUCKET_NAME}/${ARTIFACT_NAME}.zip

exit 0
```

```sh:delete_artifact.dev.sh
#!/bin/sh

cd `dirname $0`

BASENAME=$(basename $0)
FILENAME=${BASENAME%.*}
ENV_TYPE=${FILENAME##*.}

BUCKET_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "ArtifactS3Bucket").ParameterValue')

ARTIFACT_NAME=code-lambda-$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')

aws s3 rm s3://${BUCKET_NAME}/${ARTIFACT_NAME}.zip

exit 0
```