AWSを使って不特定多数の猫に誕生日を祝われたい（リファクタリング編）

# はじめに
皆さん、こんにちは。
今年はありがたいことに、Japan AWS Top Engineers 2024 に選出されました。また、Top EngineersのAdvent Calendarがあるという情報を発見し、記念に参加することにしました。

偶然にも私は12月17日が誕生日なので、この日を投稿日としてエントリーしました。
今回は、 **殺伐とした日々の生活に少しでも癒やしを与えるような題材** にしました。
ということで、作成されたのがこちらの記事です。

[AWSを使って不特定多数の猫に誕生日を祝われたい（タイムアタック編）](https://qiita.com/siruko/items/6c8385a4b7d0202e0d0d)

よく見るとあんまりAWSに関係がありません。このままだと期中にTop Engineerをはく奪されてしまいそうなので、AWSに寄せたリファクタリングをかけようと思います。

① アプリケーション部分のIaC化
② アプリケーションリリースのCI/CD化
③ パイプラインのIaC化

前回同様、 **「記事を書きながら実装をタイムアタック形式で行う」** という追い込みルールで進めたいと思います。

今回は120分でリファクタリングを書けるという目標にします！（結局タイムアタック）
またもや、21時を過ぎてから開始します。
（ここまで20分...）

## 作成したもの
https://github.com/higurashit/topengineer-advent-calendar-2024-meow/tree/v1.0.0

## ① アプリケーション部分のIaC化
CloudFormationが好きなので、ネイティブCfn yamlで書いていきます。
対象は以下の通り。
- Lambda
- StateMachine

[IaCジェネレーター](https://ap-northeast-1.console.aws.amazon.com/cloudformation/home#/iac-generator
)に遷移します。
スキャンします。長いなぁ...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/88345b4c-595f-815b-51ab-1b0fc4b70df1.png)

スキャンが終わりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/da3612ab-f716-4f86-6809-1a8f8593c2bb.png)

テンプレートを作っていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/90dabbb0-653d-a7c1-676d-29c2de6c995b.png)

LambdaとStateMachineを選んだだけなのに、IAMやSchedulerまでレコメンドしてくれました。
とてもありがたいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/1408398f-8dad-4fe7-ea37-b53f8134ee6b.png)

テンプレートができました。アカウントIDとAPIキーを削除したり、名前を変えたり、順序を変えたり、余計なものを削除したyamlがこちらです。

:::note warn
<details><summary>コードを見る</summary>

```yaml:/iac/template.yaml
Resources:
  ################################
  # Lambda and IAM
  ################################
  # meowAdvice function
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code: ../functions/meowAdvice/
      Role: !GetAtt IAMRoleLFMeowAdvice.Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
          DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
          LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
          GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
          LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64

  # role for meowAdvice function
  IAMRoleLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyLFMeowAdvice
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFMeowAdvice
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
  IAMManagedPolicyLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFMeowAdvice
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
            Action: logs:CreateLogGroup
            Effect: Allow
          - Resource:
              - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/meowAdvice:*
            Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
      Roles: []
      Users: []

  # findCatsGroup function
  LambdaFunctionFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 3
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code: ../functions/findCatsGroup/
      Role: !GetAtt IAMRoleLFFindCatsGroup.Arn
      FileSystemConfigs: []
      FunctionName: findCatsGroup
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/findCatsGroup
      RecursiveLoop: Terminate
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64

  # role for findCatsGroup function
  IAMRoleLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyLFFindCatsGroup
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFFindCatsGroup
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
  IAMManagedPolicyLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFFindCatsGroup
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
            Action: logs:CreateLogGroup
            Effect: Allow
          - Resource:
              - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/findCatsGroup:*
            Action:
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
      Roles: []
      Users: []

  ################################
  # StateMachne
  ################################
  # statemachine GetMeowAdvicesFlow
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
      DefinitionS3Location: ../statemachine/getMeowAdvicesFlow.asl.json
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn: !GetAtt IAMRoleSFGetMeowAdvicesFlow.Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false

  # role for GetMeowAdvicesFlow
  IAMRoleSFGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyExecuteFunction
        - !Ref IAMManagedPolicyXray
      MaxSessionDuration: 3600
      RoleName: IAMRoleSFGetMeowAdvicesFlow
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: states.amazonaws.com

  IAMManagedPolicyExecuteFunction:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyExecuteFunction
      Path: /service-role/
      Description: Allow AWS Step Functions to invoke Lambda functions on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice:*
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup:*
            Action:
              - lambda:InvokeFunction
            Effect: Allow
          - Resource:
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup
            Action:
              - lambda:InvokeFunction
            Effect: Allow
      Roles: []
      Users: []

  IAMManagedPolicyXray:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyXray
      Path: /service-role/
      Description: Allow AWS Step Functions to call X-Ray daemon on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
              - '*'
            Action:
              - xray:PutTraceSegments
              - xray:PutTelemetryRecords
              - xray:GetSamplingRules
              - xray:GetSamplingTargets
            Effect: Allow
      Roles: []
      Users: []

  ################################
  # Scheduler
  ################################
  # scheduler
  SchedulerExecGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::Scheduler::Schedule
    DeletionPolicy: Retain
    Properties:
      GroupName: default
      ScheduleExpression: cron(0 0 17 12 ? *)
      Target:
        Arn: !Ref StateMachineGetMeowAdvicesFlow
        RetryPolicy:
          MaximumEventAgeInSeconds: 86400
          MaximumRetryAttempts: 185
        RoleArn: !GetAtt IAMRoleEBScheduler.Arn
      Description: cats give advice.
      State: ENABLED
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpressionTimezone: Asia/Tokyo
      Name: ExecGetMeowAdvicesFlowScheduler

  IAMRoleEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - !Ref IAMManagedPolicyEBScheduler
      MaxSessionDuration: 3600
      RoleName: IAMRoleEBScheduler
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Condition:
              StringEquals:
                aws:SourceAccount: !Sub '${AWS::AccountId}'
            Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: scheduler.amazonaws.com

  IAMManagedPolicyEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyEBScheduler
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
              - !Ref SchedulerExecGetMeowAdvicesFlow
            Action:
              - states:StartExecution
            Effect: Allow
      Roles: []
      Users: []
```

</details>
:::

CI/CD化を見越して、[LambdaとStepFunctionsは別ソースを指定](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html)しています。これでインフラとアプリケーションのソースコードを分離できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/6562496a-61b4-f64a-e17f-6094858d6b24.png)

加えて、Lambdaの環境変数にはSystemsManagerのParameter Store値を指定します。

```diff_yaml:/iac/template.yaml -> LambdaFunctionMeowAdvice
  # meowAdvice function
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
+     Code: ../functions/meowAdvice/
      Role: !GetAtt IAMRoleLFMeowAdvice.Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
+         DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
+         LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
+         GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
+         LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64
```
```diff_yaml:/iac/template.yaml -> StateMachineGetMeowAdvicesFlow
  # statemachine GetMeowAdvicesFlow
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
+     DefinitionS3Location: ../statemachine/getMeowAdvicesFlow.asl.json
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn: !GetAtt IAMRoleSFGetMeowAdvicesFlow.Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false
```

ここまでで80分かかっていました。目標はあと40分です...！
続いて、[GitHubリポジトリ](https://github.com/higurashit/topengineer-advent-calendar-2024-meow)を作成します。
まだ空です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/b393f339-55c8-81c8-21fa-02cd7961030f.png)

```
topengineer-advent-calendar-2024-meow
├ LICENSE
└ README.md
```

このご時世に、masterブランチで作ってしまいました。。
後で変えるとして、yamlとlambdaのソースコードをコミットします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/83be7d73-ffb9-8d1d-a23d-472cc8191021.png)

```diff
topengineer-advent-calendar-2024-meow
+ ├ functions
+ │ ├ findCatsGroup
+ │ │  └ lambda_function.py
+ │ └ meowAdvice
+ │    └ lambda_function.py
+ ├ iac
+ │ └ template.yaml
  ├ LICENSE
  └ README.md
```

statemachineの設定をコンソールから取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/ceebeeb2-ebc3-7858-9106-1a9331f627e7.png)

この時、Lambda関数のARNは[DefinitionSubstitutions](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html#aws-resource-stepfunctions-statemachine--examples--Using_DefinitionSubstitutions)を利用して外部jsonに引き渡します。
CloudFormationのyamlでは、StateMachineのDefinitionSubstitutionsをこのように指定します。

```diff_yaml:/iac/template.yaml -> StateMachineGetMeowAdvicesFlow
  # statemachine GetMeowAdvicesFlow
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
      DefinitionS3Location: ../statemachine/getMeowAdvicesFlow.asl.json
+     DefinitionSubstitutions:
+       LambdaFunctionMeowAdviceArn: !GetAtt LambdaFunctionMeowAdvice.Arn
+       LambdaFunctionFindCatsGroupArn: !GetAtt LambdaFunctionFindCatsGroup.Arn
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn: !GetAtt IAMRoleSFGetMeowAdvicesFlow.Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false
```

StateMachineの定義である外部jsonは、[.asl.json拡張子](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-amazon-states-language.html)で定義します。
マネジメントコンソール上のDefault形式は[JSONata](https://jsonata.org/)になっていましたので、そのままJSONataで記述します。
CloudFormationから受け取る変数は、以下のハイライトのように設定します。
（後で出てきますが、この記述は間違っていますので、、[Git](https://github.com/higurashit/topengineer-advent-calendar-2024-meow/blob/v1.0.0/statemachine/getMeowAdvicesFlow.asl.json#L10)を参照ください）

```diff_json:/statemachine/getMeowAdvicesFlow.asl.json
{
  "Comment": "A description of my state machine",
  "StartAt": "猫の群れを探す",
  "States": {
    "猫の群れを探す": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Output": "{% $states.result.Payload %}",
      "Arguments": {
+       "FunctionName": "{$LambdaFunctionFindCatsGroupArn}",
        "Payload": "{% $states.input %}"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "Next": "猫の群れに入る"
    },
    "猫の群れに入る": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "それぞれの猫にお祝いの言葉を言ってもらう",
        "States": {
          "それぞれの猫にお祝いの言葉を言ってもらう": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Output": "{% $states.result.Payload %}",
            "Arguments": {
+             "FunctionName": "{$LambdaFunctionMeowAdviceArn}"
            },
            "Retry": [
              {
                "ErrorEquals": [
                  "Lambda.ServiceException",
                  "Lambda.AWSLambdaException",
                  "Lambda.SdkClientException",
                  "Lambda.TooManyRequestsException"
                ],
                "IntervalSeconds": 1,
                "MaxAttempts": 3,
                "BackoffRate": 2,
                "JitterStrategy": "FULL"
              }
            ],
            "End": true
          }
        }
      },
      "End": true,
      "Items": "{% $states.input.myCats %}",
      "MaxConcurrency": 3
    }
  },
  "QueryLanguage": "JSONata"
}
```

statemachineのjsonファイルをGitに入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/c5526a08-2e3b-12b0-f203-f6d6d152c9c9.png)

```diff
topengineer-advent-calendar-2024-meow
  ├ functions
  │ ├ findCatsGroup
  │ │  └ lambda_function.py
  │ └ meowAdvice
  │    └ lambda_function.py
  ├ iac
  │ └ template.yaml
+ ├ statemachine
+ │ └ getMeowAdvicesFlow.asl.json
  ├ LICENSE
  └ README.md
```

この状態で、CloudFormation CLIを使ってIaCからリソースを作成してみます。  
準備として、CloudShellを起動し、`git clone` を実行します。

コマンドは[このページ](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html)を参照して作成しました。コマンドを実行します。

```sh
aws cloudformation package --template-file ./iac/template.yaml --s3-bucket siruko-cloudformation-templetes --output-template-file packaged-template.yaml
```

作成されたyamlがこちらです。別ファイルで定義したLambdaのソースコードや、StepFunctionsの定義jsonが指定したS3にアップロードされ、そのURLが指定されています。

:::note warn
<details><summary>コードを見る</summary>

```yaml:/iac/template.yaml
Resources:
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code:
        S3Bucket: siruko-cloudformation-templetes
        S3Key: 802afc518ce90d2fbd6d590b3150333d
      Role:
        Fn::GetAtt:
        - IAMRoleLFMeowAdvice
        - Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
          DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
          LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
          GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
          LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
      - x86_64
  IAMRoleLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyLFMeowAdvice
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFMeowAdvice
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
  IAMManagedPolicyLFMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFMeowAdvice
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
            Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
          Action: logs:CreateLogGroup
          Effect: Allow
        - Resource:
          - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/meowAdvice:*
          Action:
          - logs:CreateLogStream
          - logs:PutLogEvents
          Effect: Allow
      Roles: []
      Users: []
  LambdaFunctionFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 3
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code:
        S3Bucket: siruko-cloudformation-templetes
        S3Key: 39a90f217524dd9b5d2ee8c7fb0d1485
      Role:
        Fn::GetAtt:
        - IAMRoleLFFindCatsGroup
        - Arn
      FileSystemConfigs: []
      FunctionName: findCatsGroup
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/findCatsGroup
      RecursiveLoop: Terminate
      EphemeralStorage:
        Size: 512
      Architectures:
      - x86_64
  IAMRoleLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyLFFindCatsGroup
      MaxSessionDuration: 3600
      RoleName: IAMRoleLFFindCatsGroup
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
  IAMManagedPolicyLFFindCatsGroup:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyLFFindCatsGroup
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
            Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
          Action: logs:CreateLogGroup
          Effect: Allow
        - Resource:
          - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/findCatsGroup:*
          Action:
          - logs:CreateLogStream
          - logs:PutLogEvents
          Effect: Allow
      Roles: []
      Users: []
  StateMachineGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::StepFunctions::StateMachine
    DeletionPolicy: Retain
    Properties:
      DefinitionS3Location:
        Bucket: siruko-cloudformation-templetes
        Key: 2f7f170dc9785b84b19ca51282777ef2
      DefinitionSubstitutions:
        LambdaFunctionMeowAdviceArn:
          Fn::GetAtt:
          - LambdaFunctionMeowAdvice
          - Arn
        LambdaFunctionFindCatsGroupArn:
          Fn::GetAtt:
          - LambdaFunctionFindCatsGroup
          - Arn
      EncryptionConfiguration:
        Type: AWS_OWNED_KEY
      LoggingConfiguration:
        IncludeExecutionData: false
        Level: 'OFF'
      StateMachineName: GetMeowAdvicesFlow
      RoleArn:
        Fn::GetAtt:
        - IAMRoleSFGetMeowAdvicesFlow
        - Arn
      Tags: []
      StateMachineType: STANDARD
      TracingConfiguration:
        Enabled: false
  IAMRoleSFGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyExecuteFunction
      - Ref: IAMManagedPolicyXray
      MaxSessionDuration: 3600
      RoleName: IAMRoleSFGetMeowAdvicesFlow
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: states.amazonaws.com
  IAMManagedPolicyExecuteFunction:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyExecuteFunction
      Path: /service-role/
      Description: Allow AWS Step Functions to invoke Lambda functions on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice:*
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup:*
          Action:
          - lambda:InvokeFunction
          Effect: Allow
        - Resource:
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:meowAdvice
          - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:findCatsGroup
          Action:
          - lambda:InvokeFunction
          Effect: Allow
      Roles: []
      Users: []
  IAMManagedPolicyXray:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyXray
      Path: /service-role/
      Description: Allow AWS Step Functions to call X-Ray daemon on your behalf
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
          - '*'
          Action:
          - xray:PutTraceSegments
          - xray:PutTelemetryRecords
          - xray:GetSamplingRules
          - xray:GetSamplingTargets
          Effect: Allow
      Roles: []
      Users: []
  SchedulerExecGetMeowAdvicesFlow:
    UpdateReplacePolicy: Retain
    Type: AWS::Scheduler::Schedule
    DeletionPolicy: Retain
    Properties:
      GroupName: default
      ScheduleExpression: cron(0 0 17 12 ? *)
      Target:
        Arn:
          Ref: StateMachineGetMeowAdvicesFlow
        RetryPolicy:
          MaximumEventAgeInSeconds: 86400
          MaximumRetryAttempts: 185
        RoleArn:
          Fn::GetAtt:
          - IAMRoleEBScheduler
          - Arn
      Description: cats give advice.
      State: ENABLED
      FlexibleTimeWindow:
        Mode: 'OFF'
      ScheduleExpressionTimezone: Asia/Tokyo
      Name: ExecGetMeowAdvicesFlowScheduler
  IAMRoleEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
      - Ref: IAMManagedPolicyEBScheduler
      MaxSessionDuration: 3600
      RoleName: IAMRoleEBScheduler
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Condition:
            StringEquals:
              aws:SourceAccount:
                Fn::Sub: ${AWS::AccountId}
          Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: scheduler.amazonaws.com
  IAMManagedPolicyEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyEBScheduler
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Resource:
          - Ref: SchedulerExecGetMeowAdvicesFlow
          Action:
          - states:StartExecution
          Effect: Allow
      Roles: []
      Users: []
```
</details>
:::

それでは、このままCloudShellでCloudFormationを実行してみましょう。

```sh
aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack
```

はい、エラーです。

:::note alert
Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Circular dependency between resources: [IAMManagedPolicyEBScheduler, IAMRoleEBScheduler, SchedulerExecGetMeowAdvicesFlow]
:::

相互参照してしまっているようですね。IaCジェネレーターにも可愛いところがあります。
と思ったら、リファクタリングでStateMachineのArnを!Ref関数に変えたところでした。

```diff_yaml:/iac/template.yaml -> IAMManagedPolicyEBScheduler
  IAMManagedPolicyEBScheduler:
    UpdateReplacePolicy: Retain
    Type: AWS::IAM::ManagedPolicy
    DeletionPolicy: Retain
    Properties:
      ManagedPolicyName: IAMManagedPolicyEBScheduler
      Path: /service-role/
      Description: ''
      Groups: []
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Resource:
-             - !Ref SchedulerExecGetMeowAdvicesFlow
+             - !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:GetMeowAdvicesFlow
            Action:
              - states:StartExecution
            Effect: Allow
      Roles: []
      Users: []
```

もう一度実行します。

```sh
aws cloudformation package --template-file ./iac/template.yaml --s3-bucket siruko-cloudformation-templetes --output-template-file packaged-template.yaml
aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack
```

はい、エラーです。

:::note alert
Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Parameters: [ssm-secure:/Test/DEEPL_AUTH_KEY ssm-secure:/Test/GEMINI_API_KEY ssm-secure:/Test/LINE_ACCESS_TOKEN ssm-secure:/Test/LINE_USER_ID] cannot be found.
:::

SystemsManagerのParameter Store値を作っていませんでした。凡ミスですね。
作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/f792d2bc-89ef-db31-1059-7d2f551fd5b8.png)

GMT表記のため少し分かりづらいですが、日本時間では23:12です。  
開始から2時間が経過しつつあります。少しテンションが下がったので、ウイスキーを用意します。
もう一度実行します。

```sh
aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack
```

はい、エラーです。

:::note alert
Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Requires capabilities : [CAPABILITY_NAMED_IAM]
:::

これはもはやエラーではありません。[カスタム名を持つIAMリソースをCloudFormationで作成する際](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-action-createstack.html)のパラメータが不足しているだけです。
```diff_shell
- aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack
+ aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack --capabilities CAPABILITY_NAMED_IAM
```

はい、エラーです。

:::note alert
Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: SSM Secure reference is not supported in: [AWS::Lambda::Function/Properties/Environment/Variables/GEMINI_API_KEY,AWS::Lambda::Function/Properties/Environment/Variables/LINE_ACCESS_TOKEN,AWS::Lambda::Function/Properties/Environment/Variables/DEEPL_AUTH_KEY,AWS::Lambda::Function/Properties/Environment/Variables/LINE_USER_ID]
:::

[Lambdaの環境変数はセキュア文字列がサポートされてない](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/dynamic-references-ssm-secure-strings.html)んですね。すっかり忘れていました。plaintextに変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/4d44747b-a594-c957-fb61-2711544a9784.png)

CloudFormationのyamlも更新します。

```diff_yaml:/iac/template.yaml -> LambdaFunctionMeowAdvice
  # meowAdvice function
  LambdaFunctionMeowAdvice:
    UpdateReplacePolicy: Retain
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain
    Properties:
      MemorySize: 128
      Description: ''
      TracingConfig:
        Mode: PassThrough
      Timeout: 10
      RuntimeManagementConfig:
        UpdateRuntimeOn: Auto
      Handler: lambda_function.lambda_handler
      Code: ../functions/meowAdvice/
      Role: !GetAtt IAMRoleLFMeowAdvice.Arn
      FileSystemConfigs: []
      FunctionName: meowAdvice
      Runtime: python3.12
      PackageType: Zip
      LoggingConfig:
        LogFormat: Text
        LogGroup: /aws/lambda/meowAdvice
      Environment:
        Variables:
-         DEEPL_AUTH_KEY: '{{resolve:ssm-secure:/Test/DEEPL_AUTH_KEY}}'
-         LINE_USER_ID: '{{resolve:ssm-secure:/Test/LINE_USER_ID}}'
-         GEMINI_API_KEY: '{{resolve:ssm-secure:/Test/GEMINI_API_KEY}}'
-         LINE_ACCESS_TOKEN: '{{resolve:ssm-secure:/Test/LINE_ACCESS_TOKEN}}'
+         DEEPL_AUTH_KEY: '{{resolve:ssm:/Test/DEEPL_AUTH_KEY}}'
+         LINE_USER_ID: '{{resolve:ssm:/Test/LINE_USER_ID}}'
+         GEMINI_API_KEY: '{{resolve:ssm:/Test/GEMINI_API_KEY}}'
+         LINE_ACCESS_TOKEN: '{{resolve:ssm:/Test/LINE_ACCESS_TOKEN}}'
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64
```

もう一度実行します。

```sh
aws cloudformation package --template-file ./iac/template.yaml --s3-bucket siruko-cloudformation-templetes --output-template-file packaged-template.yaml
aws cloudformation deploy --template-file /home/cloudshell-user/topengineer-advent-calendar-2024-meow/packaged-template.yaml --stack-name testMeowStack --capabilities CAPABILITY_NAMED_IAM
```

成功しました！！

```
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - testMeowStack
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/95c5e4d4-300f-1f5b-1f8f-a71ccadc884a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/8b6a3fa3-efde-d563-4d7c-88cd6bee5fb3.png)

これにてIaC化は完了しました。

## ② アプリケーションリリースのCI/CD化
次に、GitHubのpushをトリガーに起動するCI/CDパイプラインを作っていきます。
今回はこのようなCodePipelineを作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/6da31ad8-374e-7523-9fb3-117ffd6274c1.png)

### Source
GitHubからソースコードを取得します。
mainブランチに反応するように設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/f9e4025f-9923-9961-8765-dde96c08f117.png)

### Package
前述の `aws cloudformation package` コマンドをCodeBuildで実行します。  
Packageの結果で生成されるpackaged-template.yamlは、BuildArtifactに格納されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/17de419a-b1bb-e530-e534-53c6efaa47ca.png)

ビルドプロジェクトの設定は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/9d8528e0-d90e-7e55-cc52-0fe571e95730.png)

buildspec.yamlはGitHubのパスを指定しておきます。現時点では未作成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/a38569d1-5ae5-d9d0-aaff-e5082d539e5b.png)

### CreateChangeset
`aws cloudformation package` コマンドの結果であるpackaged-template.yamlから、CloudFormationの変更セットを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/a8c7b9f4-2468-bb25-bdca-b5a8df16dfe4.png)

### Approval
誤リリース抑止用に、変更セットの作成が成功したら一旦ストップし、手動承認を待ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/a8f944a9-d689-b47b-25b3-5cc889c1f074.png)

### ExecuteChangeset
変更セットを実行します。これでLambda, StateMachine, EventBridge Schedulerがデプロイされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/4ba6cb65-037c-1a7e-9d26-f14e6a920618.png)

それでは、buildspec.yamlを作成します。

```diff_yaml
version: 0.2

phases:
  install:
    commands:
      - echo Start package...
  build:
    commands:
      - echo Packageing...
      - aws cloudformation package 
        --template-file ./iac/template.yaml 
        --s3-bucket siruko-cloudformation-templetes
        --output-template-file packaged-template.yaml
      - ls -l packaged-template.yaml
artifacts:
  files:
    - packaged-template.yaml
```

GitHubにbuildspec.yamlをcommitしpushします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/d82eb98a-3ea3-5786-04e7-202a7f8e214b.png)

```diff
topengineer-advent-calendar-2024-meow
  ├ functions
  │ ├ findCatsGroup
  │ │  └ lambda_function.py
  │ └ meowAdvice
  │    └ lambda_function.py
  ├ iac
+ │ ├ buldspec.yaml
  │ └ template.yaml
  ├ statemachine
  │ └ getMeowAdvicesFlow.asl.json
  ├ LICENSE
  └ README.md
```

いよいよ、パイプラインを動かします。
パイプラインはmainブランチに反応するようため、mainブランチを作成します。

```sh
git checkout -b main origin/master
git push origin head
```

動き始めました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/37733ea3-48a9-dedc-0e54-8b4766542c15.png)

色々ありましたが、なんとか成功しました！
- buildspec.yamlのファイル名を間違えていた
- CodeBuildの権限が不足していた
- mainブランチの変更に反応しない
- CloudFormation Stackの権限が足りない
- 変更セット名の大文字小文字を間違えた
- IaCジェネレータのデフォルトのDeletionPolicyがRetainのため削除が面倒だった（愚痴です）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/c184fceb-cd27-f55d-0b20-7d356d93944b.png)

意気揚々とStep Functionsを動かしたところ、エラー…！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/e3cc3232-ef87-4800-5621-9804d99b96af.png)

jsonの記述が間違っていました。
```diff_json:/statemachine/getMeowAdvicesFlow.asl.json
{
  "Comment": "A description of my state machine",
  "StartAt": "猫の群れを探す",
  "States": {
    "猫の群れを探す": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Output": "{% $states.result.Payload %}",
      "Arguments": {
-       "FunctionName": "{$LambdaFunctionFindCatsGroupArn}",
+       "FunctionName": "${LambdaFunctionFindCatsGroupArn}",
        "Payload": "{% $states.input %}"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "Next": "猫の群れに入る"
    },
    "猫の群れに入る": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "それぞれの猫にお祝いの言葉を言ってもらう",
        "States": {
          "それぞれの猫にお祝いの言葉を言ってもらう": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Output": "{% $states.result.Payload %}",
            "Arguments": {
-             "FunctionName": "{$LambdaFunctionMeowAdviceArn}"
+             "FunctionName": "${LambdaFunctionMeowAdviceArn}"
            },
            "Retry": [
              {
                "ErrorEquals": [
                  "Lambda.ServiceException",
                  "Lambda.AWSLambdaException",
                  "Lambda.SdkClientException",
                  "Lambda.TooManyRequestsException"
                ],
                "IntervalSeconds": 1,
                "MaxAttempts": 3,
                "BackoffRate": 2,
                "JitterStrategy": "FULL"
              }
            ],
            "End": true
          }
        }
      },
      "End": true,
      "Items": "{% $states.input.myCats %}",
      "MaxConcurrency": 3
    }
  },
  "QueryLanguage": "JSONata"
}
```

再度パイプラインからリリースを行い、Step Functionsを手動で実行したところ、成功しました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/fded3a08-3889-543a-cb04-59faae4ecb98.png)


もう2:50です。
ここまで120分を目標としていましたが、実際には終わらず330分かかってしまいました。
本日はここまでとします…。

## ③ パイプラインのIaC化
1週間以上間が空いてしまいましたが、前回の続きを進めます。  
最後のリファクタリング作業として、パイプラインをIaC化し、可搬性や再現性を確保します。  
本日も遅めの時間（21:55）スタートなので、タイムアタックです。目標は120分！

### IaCジェネレータ
CloudFormationのIaCジェネレータを使用して、パイプラインのyamlを取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/02184968-9784-f399-8edb-adcb0698343e.png)

テンプレートを作成していきます。  
ここで前回の反省を活かし、削除ポリシーと置換ポリシーを「削除」に設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/871bb8b1-500f-e125-d589-d546d97305c2.png)

スキャンしたリソースにパイプラインがありません。  
どうやら2024年12月時点では、IaCジェネレータはCodePipelineに対応していないようです。  
早くも暗雲が立ち込めました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/ed2de580-4d94-2ba4-ca71-34d3cd1c249f.png)

パイプラインをテンプレートから作成するオプションを使うことで、テンプレートの自動生成を試みます。GitHub接続は作成済なので、サクサク進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/09d23b89-7c85-dfe2-ae73-ae89a08e6cae.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/7fbd995c-a99b-f256-427c-754898d2a7a5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/a161f1eb-5a1f-8b1f-f4cb-c857ce7a2f3a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/cb40f35f-0844-604d-444e-3dc694030d34.png)

この結果、なんとパイプラインのyamlテンプレートが作成されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/32b86da9-d3ba-cacc-e080-7f476eceb084.png)

このテンプレートを元に、手動で作成したCodePipelineと同じものを作成していきます。  
結局、ほぼ一から作ることになりました。
- GitHub接続のARNを反映
- Stack名を修正
- ChangeSet名を修正
- 各ステージ（Build、CreateChangeSet、Approval、ExecuteChangeSet）の追加
- CodeBuildリソースの追加
- 各ロール（CodePipeline、CodeBuild、CreateChangeSet、ExecuteChangeSet）の追加

リファクタリング済のyamlは以下です。

:::note warn

<details><summary>コードを見る</summary>

```yaml:/iac/codepipeline.yaml
Resources:
  ################################
  # S3 Bucket
  ################################
  CodePipelineArtifactsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub GetMeowAdvicesArtifactBucket-${AWS::AccountId}

  ################################
  # CodePipeline
  ################################
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref CodePipelineArtifactsBucket
        Type: S3
      ExecutionMode: QUEUED
      Name: GetMeowAdvicesAppPipeline
      PipelineType: V2
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
        ################################
        # Get Source from GitHub
        ################################
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection
                Version: 1
              Configuration:
                ConnectionArn: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
                FullRepositoryId: higurashit/topengineer-advent-calendar-2024-meow
                BranchName: main
              OutputArtifacts:
                - Name: SourceAtf

        ################################
        # Package CloudFormation template
        ################################
        - Name: Build
          Actions:
            - Name: BuildByCodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Ref CodeBuildProjectForPackageCfnTemplate
              InputArtifacts:
                - Name: SourceAtf
              OutputArtifacts:
                - Name: BuildAtf
              Region: !Ref AWS::Region
              Namespace: BuildByCodeBuild

        ################################
        # Create CloudFormation changeset
        ################################
        - Name: CreateChangeset
          Actions:
            - InputArtifacts:
                - Name: BuildAtf
              Name: CreateChangeset
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              OutputArtifacts:
                - Name: BdOutput
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                ChangeSetName: GetMeowAdvicesAppStackChangeSet
                RoleArn: !GetAtt CreateChangesetRole.Arn
                Capabilities: CAPABILITY_NAMED_IAM
                StackName: GetMeowAdvicesAppStack
                TemplatePath: BuildAtf::packaged-template.yaml

        ################################
        # Manual Approbal
        ################################
        - Name: Approval
          Actions:
            - Name: ApproveExecuteChangeset
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Version: 1
                Provider: Manual

        ################################
        # Execte CloudFormation changeset
        ################################
        - Name: Deploy
          Actions:
            - Name: ExecuteChangeset
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                ChangeSetName: GetMeowAdvicesAppStackChangeSet
                RoleArn: !GetAtt ExecuteChangesetRole.Arn
                StackName: GetMeowAdvicesAppStack

  ################################
  # CodeBuild
  ################################
  CodeBuildProjectForPackageCfnTemplate:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: GetMeowAdvicesBuild
      Source:
        BuildSpec: ./iac/buildspec.yml
        InsecureSsl: false
        Type: CODEPIPELINE
      Artifacts:
        EncryptionDisabled: false
        Name: CodeBuildProjectForPackageCfnTemplate
        Packaging: NONE
        Type: CODEPIPELINE
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux-x86_64-standard:5.0
        ImagePullCredentialsType: CODEBUILD
        PrivilegedMode: false
        Type: LINUX_CONTAINER
      ServiceRole: !GetAtt CodeBuildRole.Arn
      TimeoutInMinutes: 15
      QueuedTimeoutInMinutes: 480
      EncryptionKey: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3
      BadgeEnabled: false
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
        S3Logs:
          Status: DISABLED
          EncryptionDisabled: false

  ################################
  # IAM Role
  ################################
  # CodePipeline
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'

  # CreateChangeset
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:CreateChangeSet
                Resource:
                  - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: s3:*
                Resource: '*'

  # ExecuteChangesetRole
  ExecuteChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineExecuteChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineExecuteChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: '*'
                Action:
                  - iam:*
              - Effect: Allow
                Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/*
                Action:
                  - iam:PassRole
              - Effect: Allow
                Resource: '*'
                Action:
                  - states:*
                  - logs:*
                  - cloudwatch:*
                  - lambda:*
                  - events:*
                  - s3:*

  # CodeBuild
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesBuildRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesBuildRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:BatchPutCodeCoverages
                  - codebuild:BatchPutTestCases
                  - codebuild:CreateReport
                  - codebuild:CreateReportGroup
                  - codebuild:UpdateReport
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/GetMeowAdvicesBuild-*
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/GetMeowAdvicesBuild
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/GetMeowAdvicesBuild:*
              - Effect: Allow
                Action:
                  - cloudformation:*
                  - codepipeline:*
                  - iam:PassRole
                  - s3:*
                Resource: '*'
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/*
```
</details>

:::

バックアップを取ってリソースを消していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/14aa4225-b435-f312-3f56-aaf31a80b2ea.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/bf210848-fefb-26fb-bc0f-dc442836897d.png)

作成したyamlをCloudFormationで流していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/a356b8e6-aa17-c101-b0b0-06c356848b4f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/d219a073-01c4-6cd9-137b-97c044460ddd.png)

ここにチェックを入れて次へ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/93a455c9-7659-c902-6028-4c02b5995fde.png)

変更セットを作ります。一発でまあまあいい感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/ad5daf4e-d623-7017-370f-712dfe6afbe7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/382f4898-9389-0323-3dd6-05dd4c838b43.png)

はい、エラーです。S3バケットに大文字を使っています。焦っています。

:::note alert
Resource handler returned message: "Bucket name should not contain uppercase characters" (RequestToken: 574a6ebb-83fb-9a8d-ff01-5a319135ef8d, HandlerErrorCode: GeneralServiceException)
:::

```diff_yaml:/iac/codepipeline.yaml -> CodePipelineArtifactsBucket
  CodePipelineArtifactsBucket:
    Type: AWS::S3::Bucket
    Properties:
-     BucketName: !Sub GetMeowAdvicesArtifactBucket-${AWS::AccountId}
+     BucketName: !Sub get-meow-advices-artifact-bucket-${AWS::AccountId}
```

再実行したところ、エラーが発生しました。ロール名が被っていました。  
コンソールからロールを削除して再実行します。現在、2時間が経過しました…。

:::note alert
Resource handler returned message: "Resource of type 'AWS::IAM::Role' with identifier 'GetMeowAdvicesAppPipelineRole' already exists." (RequestToken: 329744bb-d197-9e75-301d-a8c5c9e69d73, HandlerErrorCode: AlreadyExists)
:::

奇跡的にうまくいきました！リソースも作成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/3d1a8e13-d60d-cf5d-45f8-48859be85ffc.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/abcda17c-71a3-4445-ff63-711af4d7483c.png)

### パイプラインの実行
Sourceステージでエラーが起きていました。パイプラインのロール不足が起きています。

:::note alert
Unable to use Connection: arn:aws:codeconnections:ap-northeast-1:xxxxxxxxxxxx:connection/6a7eb867-8d4f-400e-b124-494d8874c175. The provided role does not have sufficient permissions.
:::

```diff_yaml:/iac/codepipeline.yaml -> CodePipelineRole
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'
+             - Effect: Allow
+               Action: codestar-connections:*
+               Resource: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
```

CloudFormationからパイプラインを更新し、再実行します。Sourceステージはクリアし、Buildステージでエラーとなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/d921c79e-14bf-21c9-77fb-ed24df92c9f1.png)

パイプラインロールにまだ権限が足りないようです。

:::note alert
Error calling startBuild: User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineRole/1734015821781 is not authorized to perform: codebuild:StartBuild on resource: arn:aws:codebuild:ap-northeast-1:xxxxxxxxxxxx:project/GetMeowAdvicesBuild because no identity-based policy allows the codebuild:StartBuild action (Service: AWSCodeBuild; Status Code: 400; Error Code: AccessDeniedException; Request ID: 847ed4de-d80c-4870-a73c-cc0b680e7f88; Proxy: null)
:::

CodeBuildの実行権限を付与します。Buildジョブが増えた時のためにResourceは多少緩めにしています。

```diff_yaml:/iac/codepipeline.yaml -> CodePipelineRole
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'
              - Effect: Allow
                Action: codestar-connections:*
                Resource: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
+             - Effect: Allow
+               Action: codebuild:*
+               Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/GetMeowAdvices*
```

CloudFormationからパイプラインを更新し、再実行します。
またもやBuildステージでエラーが発生しました。ファイル名の拡張子が誤っていました。

:::note alert
[Container] 2024/12/12 15:09:14.517515 Phase context status code: YAML_FILE_ERROR Message: stat /codebuild/output/src489700486/src/iac/buildspec.yml: no such file or directory
:::

```diff_yaml:/iac/codepipeline.yaml -> CodeBuildProjectForPackageCfnTemplate
  CodeBuildProjectForPackageCfnTemplate:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: GetMeowAdvicesBuild
      Source:
-       BuildSpec: ./iac/buildspec.yml
+       BuildSpec: ./iac/buildspec.yaml
        InsecureSsl: false
        Type: CODEPIPELINE
      Artifacts:
        EncryptionDisabled: false
        Name: CodeBuildProjectForPackageCfnTemplate
        Packaging: NONE
        Type: CODEPIPELINE
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux-x86_64-standard:5.0
        ImagePullCredentialsType: CODEBUILD
        PrivilegedMode: false
        Type: LINUX_CONTAINER
      ServiceRole: !GetAtt CodeBuildRole.Arn
      TimeoutInMinutes: 15
      QueuedTimeoutInMinutes: 480
      EncryptionKey: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3
      BadgeEnabled: false
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
        S3Logs:
          Status: DISABLED
          EncryptionDisabled: false
```

CloudFormationからパイプラインを更新し、再実行します。
Buildフェーズは通過しましたが、CreateChangeSetステージでエラーが発生しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/97e70b84-78d1-08ce-aafd-8193591a143f.png)

パイプラインロールにまだ権限が足りないようです。

:::note alert
User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineRole/1734016376890 is not authorized to perform: cloudformation:DescribeStacks on resource: arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/GetMeowAdvicesAppStack/cc187d20-b266-11ef-957c-06871864c701 because no identity-based policy allows the cloudformation:DescribeStacks action (Service: AmazonCloudFormation; Status Code: 403; Error Code: AccessDenied; Request ID: 3ea878d6-0e03-4b1a-8e38-9f600c477739; Proxy: null)
:::

:::note alert
User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineRole/1734016838437 is not authorized to perform: iam:PassRole on resource: arn:aws:iam::xxxxxxxxxxxx:role/GetMeowAdvicesAppPipelineCreateChangesetRole because no identity-based policy allows the iam:PassRole action (Service: AmazonCloudFormation; Status Code: 403; Error Code: AccessDenied; Request ID: 5a0d8225-a907-4137-8ebc-7d4439909586; Proxy: null)
:::

アプリ側のスタック操作権限と、CreateChangesetとExecuteChangeset用のロールへのパスロール権限を与えます。

```diff_yaml:/iac/codepipeline.yaml -> CodePipelineRole
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:Abort*
                  - s3:DeleteObject*
                  - s3:GetBucket*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:PutObjectTagging
                  - s3:PutObjectVersionTagging
                Resource:
                  - !GetAtt CodePipelineArtifactsBucket.Arn
                  - !Join
                    - /
                    - - !GetAtt CodePipelineArtifactsBucket.Arn
                      - '*'
              - Effect: Allow
                Action:
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource: !Join
                  - /
                  - - !GetAtt CodePipelineArtifactsBucket.Arn
                    - '*'
              - Effect: Allow
                Action: codestar-connections:*
                Resource: !Sub arn:aws:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/6a7eb867-8d4f-400e-b124-494d8874c175
              - Effect: Allow
                Action: codebuild:*
                Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/GetMeowAdvices*
+             - Effect: Allow
+               Action: cloudformation:*
+               Resource:
+                 - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack
+                 - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
+             - Effect: Allow
+               Action: iam:PassRole
+               Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/GetMeowAdvicesAppPipeline*
```

CloudFormationからパイプラインを更新し、再実行します。
まだCreateChangeSetでエラーが出ています。Systems ManagerのParameter Storeへのアクセス権限が足りないようです。

:::note alert
User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: ssm:GetParameters on resource: arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/Test/LINE_ACCESS_TOKEN because no identity-based policy allows the ssm:GetParameters action (Service: AWSSimpleSystemsManagement; Status Code: 400; Error Code: AccessDeniedException; Request ID: 7d2d28b5-d969-496f-aa16-8eb1726bec52; Proxy: null)
:::

名前空間を/Testにしたのを後悔しつつ、権限を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/1d812a5d-2afb-1224-a656-4c0192b1cc12.png)

```diff_yaml:/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: s3:*
                Resource: '*'
+             - Effect: Allow
+               Action: ssm:GetParameters
+               Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*

```

CloudFormationからパイプラインを更新し、再実行します。
CreateChangeSetが成功しました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/1fff0552-0c35-fbf7-a14f-673d104abaf1.png)

Approvalステージのレビューウインドウで何をしたら良いかわからないため、レビュー用リンクとメッセージを設定します。
レビュー用リンクからは変更セットに遷移できるようしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/d598ed20-1e7b-76bc-a4d2-8cfe43595395.png)

以下が「変更セットのタブかつ、Stack名を絞り込んだ状態」のURLです。
```
https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/changesets?filteringText=GetMeowAdvicesAppStack
```

```diff_yaml:/iac/codepipeline.yaml -> CodePipeline -> Approval stage
        - Name: Approval
          Actions:
            - Name: ApproveExecuteChangeset
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Version: 1
                Provider: Manual
+             Configuration:
+               ExternalEntityLink: !Sub https://${AWS::Region}.console.aws.amazon.com/cloudformation/home?region=${AWS::Region}#/stacks/changesets?filteringText=GetMeowAdvicesAppStack
+               CustomData: レビュー用URLから変更セットを確認し、リリース承認をお願いします。
```

CloudFormationからパイプラインを更新し、再実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/5af3fe0e-76f0-2988-7d63-abd2fabcbec7.png)

レビュー用URLをクリックすると、スタック一覧画面が表示されます。
URLで指定したスタック名で絞り込まれ、スタックが未選択の状態です。
画面左側のスタック名をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/88764dbf-a837-117d-9d05-dd04aba08fa7.png)

変更セットが出てきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/1bc9cd7b-8959-cc44-8b0b-55202565c4ad.png)

アプリ側のtemplate.yamlは修正していないため、「変更はありません」と表記されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/9f0ff186-3870-23a5-843d-9dab91c4eac8.png)

CodePipelineの画面に戻り、承認して先に進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/c8fc9984-6679-e28d-ff8d-0cadc698838f.png)

成功しました！！8日ぶりのリリースです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/4253d91d-b9f4-0489-52e6-7c35742c5a01.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/5f3c7fe8-e96d-032b-a2d9-8a6ba17e3e15.png)

## アプリのリファクタリング
ここまでで目標120分に対し180分かかっていますが、せっかくCI/CDパイプラインができたのでリファクタリングをします。
誕生日を祝ってもらうアプリから、いつでもアドバイスをもらえるアプリに生まれ変わりました。
happybirthday系の変数名などを直していきます。

```diff_python:/function/meowAdvice/lambda_function.py
- happyMeowthdayToLine(imeowge, f'{admeowce}\n{admeowce_jp}')
+ postAdmeowceToLine(imeowge, f'{admeowce}\n{admeowce_jp}')
```
```diff_python:/function/meowAdvice/lambda_function.py
- def happyMeowthdayToLine(imeowge, meowssage):
-   _msg = f'Happy Meowthday!!\n{meowssage}'
    _url = 'https://api.line.me/v2/bot/message/push'
+ def postAdmeowceToLine(imeowge, meowssage):
+   _msg = f'あなたにアドバイスをあげるにゃん。\n{meowssage}'
    _url = 'https://api.line.me/v2/bot/message/push'
```

```diff_json:/statemachine/getMeowAdvicesFlow.asl.json
    "猫の群れに入る": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
-       "StartAt": "それぞれの猫にお祝いの言葉を言ってもらう",
+       "StartAt": "それぞれの猫にアドバイスをもらう",
        "States": {
-         "それぞれの猫にお祝いの言葉を言ってもらう": {
+         "それぞれの猫にアドバイスをもらう": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Output": "{% $states.result.Payload %}",
            "Arguments": {
              "FunctionName": "${LambdaFunctionMeowAdviceArn}"
            },
```

GitHubでmainブランチにマージをします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/41b3849a-5371-b53d-aad1-275e91299a15.png)

パイプラインが反応しません。。
今日は一旦忘れて、パイプラインを手動で実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/4b5f202e-f940-1364-8a28-94d17a089042.png)

変更セットにLambda関数とStateMachineが出てきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/320bd54b-79a5-fa30-6520-0b472eb3ff06.png)


Approval -> ExecuteChangesetを実行するとエラーが発生しました。
ExecuteChangesetの実行には、CreateChangesetRoleにiam:GetRoleが必要ということです。よくわかりませんが、わかりました。

:::note alert
Unable to retrieve Arn attribute for AWS::IAM::Role, with error message User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: iam:GetRole on resource: role IAMRoleLFMeowAdvice because no identity-based policy allows the iam:GetRole action (Service: Iam, Status Code: 403, Request ID: 3c8e24b2-d466-4984-8da1-f407afb1c3c6)
:::

:::note alert
Unable to retrieve Arn attribute for AWS::IAM::Role, with error message User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: iam:GetRole on resource: role IAMRoleLFFindCatsGroup because no identity-based policy allows the iam:GetRole action (Service: Iam, Status Code: 403, Request ID: f06bc86a-77fb-47c2-8751-8fc94defa548)
:::

```diff_yaml:/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: ssm:GetParameters
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*
+             - Effect: Allow
+               Action: iam:GetRole
+               Resource: '*'
```

再度実行しても、エラーが発生しました。
ExecuteChangesetの実行には、CreateChangesetRoleにlambda:UpdateFunctionCodeの権限が必要とのことです。

:::note alert
Resource handler returned message: "User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: lambda:UpdateFunctionCode on resource: arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:meowAdvice because no identity-based policy allows the lambda:UpdateFunctionCode action (Service: Lambda, Status Code: 403, Request ID: e757ea4a-4b98-4a7b-ae89-b5a3695d3603)" (RequestToken: fb31b61b-519e-baef-f803-d5912c53122a, HandlerErrorCode: AccessDenied)
:::

```diff_yaml:/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: ssm:GetParameters
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*
              - Effect: Allow
                Action: iam:GetRole
                Resource: '*'
+             - Effect: Allow
+               Action: lambda:UpdateFunctionCode
+               Resource: '*'
```

次はS3とStep Functionsでエラーが発生しました。  
S3はbuildspec.yamlのpackageコマンドで指定した配置先S3バケットがテスト用のものになっていました。  
パイプラインのアーティファクト用バケットに変更します。

:::note alert
Resource handler returned message: "Your access has been denied by S3, please make sure your request credentials have permission to GetObject for siruko-cloudformation-templetes/44ac053ec6f3eb15ffed38be576c18b8.
:::

```diff_yaml:/iac/codepipeline.yaml -> CodePipeline -> Build stage
        - Name: Build
          Actions:
            - Name: BuildByCodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
+               EnvironmentVariables: !Sub
+                 - |
+                   [{
+                     "name": "bucket",
+                     "value": "${CodePipelineArtifactsBucket}",
+                     "type": "PLAINTEXT"
+                   }]
+                 - Env: !Ref CodePipelineArtifactsBucket
                ProjectName: !Ref CodeBuildProjectForPackageCfnTemplate
              InputArtifacts:
                - Name: SourceAtf
              OutputArtifacts:
                - Name: BuildAtf
              Region: !Ref AWS::Region
              Namespace: BuildByCodeBuild
```

```diff_yaml:/iac/buildspec.yaml
version: 0.2

phases:
  install:
    commands:
      - echo Start package...
      - echo ${bucket}
  build:
    commands:
      - echo Packageing...
      - aws cloudformation package
        --template-file ./iac/template.yaml
-       --s3-bucket siruko-cloudformation-templetes
+       --s3-bucket ${bucket}
        --output-template-file packaged-template.yaml
      - ls -l packaged-template.yaml
      - cat packaged-template.yaml | grep ${bucket}
artifacts:
  files:
    - packaged-template.yaml

```

次はStep Functionsの更新権限が無いとエラーが発生しました。
どうやらCreateChangesetRoleの方にリソースの作成権限、更新権限、削除権限を付けないといけないようです。

:::note alert
Resource handler returned message: "User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/GetMeowAdvicesAppPipelineCreateChangesetRole/AWSCloudFormation is not authorized to perform: states:UpdateStateMachine on resource: arn:aws:states:ap-northeast-1:xxxxxxxxxxxx:stateMachine:GetMeowAdvicesFlow because no identity-based policy allows the states:UpdateStateMachine action 
:::

```diff_yaml:/iac/codepipeline.yaml -> CreateChangesetRole
  CreateChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineCreateChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineCreateChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:CreateChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
              - Effect: Allow
                Action: ssm:GetParameters
                Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/Test/*
+             - Effect: Allow
+               Action:
+                 - iam:*
+                 - lambda:*
+                 - states:*
+                 - scheduler:*
+               Resource: '*'
+             - Effect: Allow
+               Action:
+                 - s3:GetObject*
+               Resource:
+                 - !GetAtt CodePipelineArtifactsBucket.Arn
+                 - !Join
+                   - /
+                   - - !GetAtt CodePipelineArtifactsBucket.Arn
+                     - '*'
```

反対に、ExecutionChangesetには各リソースを操作する権限は不要なので、最小権限にしておきます。

```diff_yaml:/iac/codepipeline.yaml -> ExecuteChangesetRole
  ExecuteChangesetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GetMeowAdvicesAppPipelineExecuteChangesetRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: GetMeowAdvicesAppPipelineExecuteChangesetRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudformation:ExecuteChangeSet
                Resource: !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/GetMeowAdvicesAppStack/*
```

これでExecuteChangesetが正常終了し、パイプラインが成功するようになったので、Step Functionsを動かしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/f0453a01-c559-b3f7-6fab-f9a2b0f37b2c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/a624788b-d864-7021-1505-e7747bcb6dd4.png)

はい、今日も４時間かかりました。。
タイムアタックは2時間以内にしたいですね。お疲れ様でした。

---

ここから3日目に入ります。
まず、GitHub接続は、[GitHub側](https://github.com/settings/installations)で承認が必要のようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/2d9cada1-2baa-0bcb-2eb5-6c6ece6b7b65.png)

承認後に払い出された接続ARNに差し替えると、自動でパイプラインが起動するようになりました。

```diff_yaml:/iac/codepipeline.yaml -> CodePipeline -> Source stage
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection
                Version: 1
              Configuration:
+               ConnectionArn: !Sub arn:aws:codeconnections:us-east-1:${AWS::AccountId}:connection/866c95b8-75a9-4c3b-bec9-1f31ed5b9b31
                FullRepositoryId: higurashit/topengineer-advent-calendar-2024-meow
                BranchName: main
              OutputArtifacts:
                - Name: SourceAtf
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/f85e71e6-9c60-7fd5-2060-d5623ff27468.png)


## おまけ
呼び出した際に0匹となって寂しいことがあったのと、Mapのループが無機質だったのでfindCatsGroup関数を以下の通り改善します。
- 最小匹数を1に設定
- [猫に名前](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%AA%E3%82%B9%E3%81%A8%E3%83%9C%E3%83%96)をつける

```diff_python:/function/findCatsGroup/lambda_function.py
import random

def lambda_handler(event, context):

    names = [
        'Alice', 'Bob', 'Carol', 'Charlie', 'Dave', 'Ellen', 'Eve', 'Frank', 'Isaac', 'Ivan',
        'Justin', 'Mallory', 'Marvin', 'Mallet', 'Matilda', 'Oscar', 'Pat', 'Peggy', 'Victor',
        'Plod', 'Steve', 'Trent', 'Trudy', 'Walter', 'Zoe', 'Darwin'
    ]
    my_cats = []
    my_cats_length = random.randrange(6) + 1 # 1～7匹
    
    for cat in range(my_cats_length):
        name = pick_name(names, my_cats)
        my_cats.append({'name': name})

    return {
        "my_cats": my_cats
    }

def pick_name(names, ignores):
    name = random.choice(names)
    if name in ignores:
        return pick_name(names, ignores)
    return name
```

```diff_python:/function/meowAdvice/lambda_function.py
~~~~
def lambda_handler(event, context):
+   # 猫の名前を取得
+   name = get_name(event)

    # 猫画像取得
    imeowge = get_imeowge()

    # アドバイス取得
    admeowce = get_admeowce()
    admeowce_jp = get_admeowce_jp(admeowce)
    admeowce_jp = translate_meow(admeowce_jp)

+   # メッセージ
+   meowssage = f'{name}「{admeowce_jp} ({admeowce}) 」'

    # LINEに送信
-   post_admeowce_to_line(imeowge, f'{admeowce}\n{admeowce_jp}')
+   post_admeowce_to_line(imeowge, meowssage)

+def get_name(event):
+   default_cat_name = 'Cat'
+   return event.key('name', default_cat_name)
~~~~
```

StateMachineを手動実行します。
Victorという名前の1匹の猫が見つかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/97d3a1b2-e046-5c29-e5ab-39491d03e160.png)

LINEを見ると、Victorからアドバイスがきています！
Victor、いいキャラしてますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/89cb7489-f05b-3e02-fad4-f66ee1abe014.png)

はい、ここまででリファクタリング(?)は一旦完了とします。
[Gitにタグを打ち](https://github.com/higurashit/topengineer-advent-calendar-2024-meow/releases/tag/v1.0.0)、次のアイデアが降りてくるのを待つことにします。

# 所要時間
目標を240分に設定していたのですが、実際には640分かかってしまいました。
- 序章：20分
- ① アプリケーション部分のIaC化：60分
- ② アプリケーションリリースのCI/CD化：250分
- ③ パイプラインのIaC化：180分
- アプリのリファクタリング：60分
- GitHub対応：10分
- おまけ：60分

全てにおいて見立てより遅いです。
リリースパイプラインの作成とIaC化で半分以上かかっています。うん、反省。

# まとめ
これで予約投稿します。  
久しぶりに作業すると、思わぬところでハマりますね。アウトプットしないと忘れてしまうということを、改めて実感できた良い機会でした。

私個人の所感として、タイムアタックは、
+ **適当でも良いのでとにかくアウトプットする**
+ **時間をあまり使わない**

という2点から、投稿のモチベーションを保つのに役立つと考えました。
今後もタイムアタックを仕掛けながら、このアプリを大きくしていこうと思います。
あとはもうちょいAWSの深淵を覗かないとな、、と思いました。

それでは皆さん、良いクリスマスを！！