AWS CloudFormationでCodePipelineを使ったCI/CD環境構築

WebアプリケーションをEC2で稼働させる時、毎回EC2にアクセスしてファイルを更新するのが手間と感じたので、CodeBuild, CodeDeploy,CodePipelineを使用して自動化してみました。

似たような環境を複数(再度)使用する可能性があったので 、CloudFormationを使用して一気に作成することにします。

完成アーキテクチャはこんな感じ
![AWSArchitecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3517045/0c1feba9-549b-4744-3bc3-a4bd43c2bbd4.png)

①ローカルで開発したWebアプリケーションをGitHubにプッシュ
②CodePipelineが定期的にGitHubに確認に行き更新がないかチェック
③更新があったら最新コードを取得
④S3に取得したコードを保存
⑤CodeBuildにビルド実行指示
⑥S3からビルド用コードを取得 
⑦ビルドを実行してアーティファクト(.zip)を作成
⑧S3に保存
⑨CodePipelineからCodeDeployにデプロイの実行指示
⑩CodeDeployの設定に合わせてデプロイを開始
⑪CodeDeployからEC2にデプロイ指示
⑫EC2からS3にアプリケーションを取得しに行って実行

これでローカル編集したコードをGitHubにPushすれば、自動で公開されているEC2のWebアプリケーションが更新されます。

※GitHubの情報をSecretManagerから参照させるようにしているので、事前にOwner, Repo, OAuthToken 情報をSecretManagerに登録しておいてください(CloudFormationのコードに直接入力して動作確認でも可)。

CloudFormationのコードが300行超えているのでサービス別のコードの最後に全体を載せます。

### 共通部分
・パラメータとしてCloudFormationスタック作成時値を指定します。
WebAppS3BucketName: 作成するS3のバケット名
GitHubRepoURL: コードを保存しているGitHubのリポジトリURL
EC2InstanceName: Webアプリを実行するEC2名(デフォルト値設定あり)
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: |
  Example CloudFormation template for creating an S3 bucket and a CodeBuild project for a web application pushed to GitHub.

Parameters:
  WebAppS3BucketName:
    Type: String
    Description: Enter the name for the S3 bucket to host the web application static files

  GitHubRepoURL:
    Type: String
    Description: Enter the URL of your GitHub repository

  EC2InstanceName:
    Type: String
    Default: WebApplicationPipelineEC2
    Description: Enter the name for the EC2 instance
```
### EC2
・EC2を設置するVPCとSubnetを作成
```yaml
Resources:
  # VPC 作成
  WebAppVPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: WebApplicationPipelineVPC

  # Subnet 作成
  WebAppSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref WebAppVPC
      CidrBlock: '10.0.0.0/24'
      AvailabilityZone: 'ap-northeast-1a'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: WebApplicationPipelineSubnet

```
### EC2用のセキュリティグループ作成
・インバウンド許可ポート: SSH/22, HTML/80, Webアプリケーション/8080
```yaml
  # EC2用SecurityGroup 作成
  MyEC2SecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: EC2 Security Group for MyEC2Instance
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0  # SSHアクセスを許可するCIDR
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0  # HTTPアクセスを許可するCIDR
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          CidrIp: 0.0.0.0/0  # その他のポートにアクセスを許可するCIDR
```
### Webアプリケーションを実行するEC2作成
・SecurityGroupIds: 作成したSecutiryGroup[MyEC2SecurityGroup]を指定
・KeyName: 事前に作成していたキーペア名を指定(各自で作成)
・IamInstanceProfile: IAM Roleを割り当てたインスタンスプロファイル(後で作成)
・Tags: - Key: Name Value: !Ref EC2InstanceName:  Parametersで入力したEC2名を設定
・UserData: EC2起動時に実行するコマンド 内容はコメント参考
 &nbsp;&nbsp; (OrenoApplicationは各自のアプリケーション名に変更)
```yaml
  MyEC2Instance: # EC2インスタンス 作成
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-04e0b6d6cfa432943  # EC2インスタンスに使用するAMIのIDを指定してください
      InstanceType: t2.micro  # EC2インスタンスタイプ
      SecurityGroupIds:
        - !Ref MyEC2SecurityGroup  # セキュリティグループ
      KeyName: OrenoSecurityKey  # キーペア名(作成済)
      IamInstanceProfile: !Ref EC2InstanceProfile
      Tags:
        - Key: Name
          Value: !Ref EC2InstanceName
      UserData:
        Fn::Base64: |
          #!/bin/bash
          # モジュールの最新化
          sudo yum update -y

          # Javaのインストール
          sudo yum install -y java-17

          # CodeDeploy エージェントインストール
          sudo yum install -y ruby
          wget https://aws-codedeploy-ap-northeast-1.s3.amazonaws.com/latest/install
          chmod +x ./install
          sudo ./install auto

          # Create a sample service script
          cat > /etc/systemd/system/orenoapplication.service << EOF
          [Unit]
          Description=OrenoApplication
          After=syslog.target

          [Service]
          User=ec2-user
          ExecStart=/usr/bin/java -jar /home/ec2-user/app/OrenoApplication-0.0.1-SNAPSHOT.jar
          WorkingDirectory=/home/ec2-user/app

          [Install]
          WantedBy=multi-user.target
          EOF

          # Enable and start the service
          systemctl daemon-reload
  ```
### EC2用IAM Role作成
※EC2はインスタンスプロファイルを作成してEC2に設定する必要あり
  ```yaml
  EC2InstanceProfile: # EC2用インスタンスプロファイル
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: "/"
      Roles:
        - !Ref EC2Role
  
  EC2Role: # EC2用ロール
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: S3FullAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:*'
                Resource: '*'

```
### S3バケット作成
・Properties: BucketName: !Ref WebAppS3BucketName: Parametersで入力したS3名を設定
※同じ名前のS3バケットは作成できない為、別環境で作成する場合のS3バケット名重複注意
```yaml
  # S3 バケット
  WebAppS3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref WebAppS3BucketName
```

### CodeBuildプロジェクト作成
・Location: !Sub 'arn:aws:s3:::${WebAppS3Bucket}': 先に作成したS3のARNを取得
・その他は各行のコメントを参考にしてください(一番最初に作ったのでコメント過多)
※SourceでGitHubのURLを指定していますが後ほど作るCodePipelineでは、GitHubのソースコードをS3に一度保存します。CodeBuildはS3に保存されたソースコードに対して実行されます(作成されたCodeBuildを手動で実行するとGitHubから直接ビルドを行います)。
```yaml
  # CodeBuildプロジェクト定義
  CodeBuildProject:
    Type: 'AWS::CodeBuild::Project' # リソースタイプ AWS CodeBuild Project
    Properties:
      Name: WebApplicationPipelineCodeBuild # プロジェクト名
      Description: CodeBuild project for building and deploying web application # プロジェクト説明
      ServiceRole: !GetAtt CodeBuildRole.Arn # CodeBuildが使用するIAMロールARN
      Artifacts: # アーティファクト設定
        Type: S3 # 保存先 S3
        Location: !Sub 'arn:aws:s3:::${WebAppS3Bucket}' # S3バケットのARNを指定
        Packaging: ZIP # アーティファクトのパッケージング ZIP
      Environment: # ビルド環境
        ComputeType: BUILD_GENERAL1_SMALL # コンピューティングタイプ
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0 # コンピューティングイメージ
        Type: LINUX_CONTAINER # コンピューティングタイプ
        EnvironmentVariables: # 環境変数
          - Name: S3_BUCKET # 環境変数名 S3バケット
            Type: PLAINTEXT # 環境変数タイプ プレーンテキストで処理
            Value: WebAppS3BucketName # 環境変数値 S3バケット名
      Source: # ソースの設定
        Type: GITHUB # ソースの種類 GitHub
        Location: !Ref GitHubRepoURL # GitHubリポジトリのURL
        GitCloneDepth: 1 # ソースのクローン数
        BuildSpec: buildspec.yml # ソースのビルドスペック
```
### CodeBuildRole用IAM Role作成
・アーティファクトを保存するのにS3へのPutObject, ビルドする為のコードを取得する為にGetObjectを設定
・logs: 関連の実行許可も必須
・ここもその他は各行のコメントを参考にしてください。
```yaml
  CodeBuildRole: # CodeBuild IAM ロール定義
    Type: 'AWS::IAM::Role' # リソースタイプ AWS IAM Role
    Properties: # プロパティ設定
      AssumeRolePolicyDocument: # IAMロールのAssumeRolePolicyDocument
        Version: '2012-10-17' # IAMロールポリシーのバージョン
        Statement: # ステートメント
          - Effect: Allow # 許可設定
            Principal: # プリンシパル設定
              Service: codebuild.amazonaws.com # IAMロールのサービス CodeBuild
            Action: 'sts:AssumeRole' # IAMロールのアクション sts:AssumeRole 一時的セキュリティトーン取得
      Policies: # ロールポリシー
        - PolicyName: CodeBuildPolicy # ロールポリシー名
          PolicyDocument: # ロールポリシーのドキュメント
            Version: '2012-10-17' # ロールポリシーのバージョン
            Statement: # ステートメント
              - Effect: Allow # 許可設定
                Action: # 実行許可アクション
                  - 's3:PutObject' # S3へのアップロード
                  - 's3:GetObject' # S3からのダウンロード
                Resource: !Sub 'arn:aws:s3:::${WebAppS3Bucket}/*' # 特定のS3バケットのオブジェクトへのアクセス
              - Effect: Allow # 許可設定
                Action: # 実行許可アクション
                  - 'logs:CreateLogGroup' # ロググループの作成
                  - 'logs:CreateLogStream' # ログストリームの作成
                  - 'logs:PutLogEvents' # ログの記録
                Resource: '*' # すべにアクセス
```
### CodeDeployアプリケーション作成
```yaml
  CodeDeployApplication: # CodeDeployアプリケーション
    Type: 'AWS::CodeDeploy::Application'
    Properties:
      ApplicationName: CodeDeployApplication
```
### CodeDeployGroup作成
・ApplicationName: 先に作成したCodeDeployApplication名を取得
・DeploymentConfigName: AllAtOnce(デプロイを一度に行う)
・Ec2TagFilters: Key: Name, Value: !Ref EC2InstanceName: KeyとValueを使用してデプロイメントグループを特定するためTypeはKEY_AND_VALUEを指定。
```yaml
  CodeDeployDeploymentGroup:
    Type: 'AWS::CodeDeploy::DeploymentGroup'
    Properties:
      DeploymentGroupName: CodeDeployDeploymentGroup
      ApplicationName: !Ref CodeDeployApplication
      ServiceRoleArn: !GetAtt CodeDeployRole.Arn  # CodeDeployが使用するIAMロールのARNを指定してください
      DeploymentConfigName: CodeDeployDefault.AllAtOnce  # デプロイメントの構成
      Ec2TagFilters:  # EC2タグを使用してデプロイメントグループをフィルタリング
        - Key: Name
          Value: !Ref EC2InstanceName # EC2インスタンス名(今回は作成済)
          Type: KEY_AND_VALUE # KEY_ONLY: 指定したキーのみ, VALUE_ONLY: 指定した値のみ, KEY_AND_VALUE: 指定したキーと値
```
### CodeDeploy用IAM Roleを作成
・全て許可にしているので、本来は絞るべき
```yaml
  CodeDeployRole: # CodeDeploy用ロール
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codedeploy.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: CodeDeployPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: '*'
                Resource: '*'
```
### CodePipelineを作成
・作成したCodeBuild, CodeDeployの情報を基に設定
・SourceAction: GitHubのリポジトリを指定する
&nbsp;&nbsp; リポジトリのオーナー Owner
&nbsp;&nbsp; リポジトリ名 Repo
&nbsp;&nbsp; OAuthトークン OAuthToken(事前にGitHubで作成 設定する権限注意)
'{{resolve:secretsmanager:GitHubSecret:SecretString:Owner}}': SecretManagerのGitHubSecretという作成したシークレット内の値を参照させる(シークレットは別途作成しておいてください)
```yaml
  WebAppCodePipeline: # CodePipeline
    Type: 'AWS::CodePipeline::Pipeline'
    Properties:
      ArtifactStore:
        Location: !Ref WebAppS3BucketName
        Type: S3
      RoleArn: !GetAtt CodePipelineRole.Arn
      
      # ソースステージの設定
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Provider: GitHub
                Version: 1
              Configuration:
                Owner: '{{resolve:secretsmanager:GitHubSecret:SecretString:Owner}}' # GitHubのリポジトリのオーナー
                Repo: '{{resolve:secretsmanager:GitHubSecret:SecretString:Repo}}'  # GitHubのリポジトリ名
                Branch: main
                OAuthToken: '{{resolve:secretsmanager:GitHubSecret:SecretString:OAuthToken}}'  # GitHubのOAuthトークン
              OutputArtifacts:
                - Name: SourceOutput
          
        # ビルドステージの設定
        - Name: PiplineBuild
          Actions:
            - Name: BuildAction
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: WebApplicationPipelineCodeBuild
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput
          
        # デプロイステージの設定
        - Name: PiplineDeploy
          Actions:
            - Name: DeployAction
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CodeDeploy
                Version: 1
              Configuration:
                ApplicationName: CodeDeployApplication
                DeploymentGroupName: CodeDeployDeploymentGroup
              InputArtifacts:
                - Name: BuildOutput
```
### CodePipeline用IAM Role作成
・CodeBuildとCodeDeployで使用する実行許可を付与する
```yaml
  CodePipelineRole: # CodePipeline用ロール
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: CodePipelinePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow # 許可設定
              Action: # 実行許可アクション
                - 's3:PutObject' # S3へのアップロード
                - 's3:GetObject' # S3からのダウンロード
              Resource: !Sub 'arn:aws:s3:::${WebAppS3Bucket}/*' # 特定のS3バケットのオブジェクトへのアクセス
            - Effect: Allow # 許可設定
              Action: # 実行許可アクション
                - 'logs:CreateLogGroup' # ロググループの作成
                - 'logs:CreateLogStream' # ログストリームの作成
                - 'logs:PutLogEvents' # ログの記録
              Resource: '*' # すべにアクセス
        - PolicyName: PiplineCodeBuildDeployPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'codebuild:StartBuild'
                  - 'codebuild:BatchGetBuilds'
                Resource: !GetAtt CodeBuildProject.Arn
              - Effect: Allow
                Action:
                  - 'codedeploy:CreateDeployment'
                  - 'codedeploy:GetDeployment'
                  - 'codedeploy:GetDeploymentConfig'
                  - 'codedeploy:GetApplicationRevision'
                  - 'codedeploy:RegisterApplicationRevision'
                Resource: '*'
```

最後に全体のコードを載せておきます。
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: |
  Example CloudFormation template for creating an S3 bucket and a CodeBuild project for a web application pushed to GitHub.

Parameters:
  WebAppS3BucketName:
    Type: String
    Description: Enter the name for the S3 bucket to host the web application static files

  GitHubRepoURL:
    Type: String
    Description: Enter the URL of your GitHub repository

  EC2InstanceName:
    Type: String
    Default: WebApplicationPipelineEC2
    Description: Enter the name for the EC2 instance

Resources:
  # VPC 作成
  WebAppVPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: WebApplicationPipelineVPC

  # Subnet 作成
  WebAppSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref WebAppVPC
      CidrBlock: '10.0.0.0/24'
      AvailabilityZone: 'ap-northeast-1a'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: WebApplicationPipelineSubnet


  # EC2用SecurityGroup 作成
  MyEC2SecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: EC2 Security Group for MyEC2Instance
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0  # SSHアクセスを許可するCIDR
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0  # HTTPアクセスを許可するCIDR
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          CidrIp: 0.0.0.0/0  # その他のポートにアクセスを許可するCIDR

  MyEC2Instance: # EC2インスタンス 作成
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-04e0b6d6cfa432943  # EC2インスタンスに使用するAMIのIDを指定してください
      InstanceType: t2.micro  # EC2インスタンスタイプ
      SecurityGroupIds:
        - !Ref MyEC2SecurityGroup  # セキュリティグループ
      KeyName: OrenoSecurityKey  # キーペア名(今回は作成済)
      IamInstanceProfile: !Ref EC2InstanceProfile
      Tags:
        - Key: Name
          Value: !Ref EC2InstanceName
      UserData:
        Fn::Base64: |
          #!/bin/bash
          # モジュールの最新化
          sudo yum update -y

          # Javaのインストール
          sudo yum install -y java-17

          # CodeDeploy エージェントインストール
          sudo yum install -y ruby
          wget https://aws-codedeploy-ap-northeast-1.s3.amazonaws.com/latest/install
          chmod +x ./install
          sudo ./install auto

          # Create a sample service script
          cat > /etc/systemd/system/orenoapplication.service << EOF
          [Unit]
          Description=OrenoApplication
          After=syslog.target

          [Service]
          User=ec2-user
          ExecStart=/usr/bin/java -jar /home/ec2-user/app/OrenoApplication-0.0.1-SNAPSHOT.jar
          WorkingDirectory=/home/ec2-user/app

          [Install]
          WantedBy=multi-user.target
          EOF

          # Enable and start the service
          systemctl daemon-reload

  
  EC2InstanceProfile: # EC2用インスタンスプロファイル
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: "/"
      Roles:
        - !Ref EC2Role
  
  EC2Role: # EC2用ロール
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: S3FullAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:*'
                Resource: '*'


  # S3 バケット
  WebAppS3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref WebAppS3BucketName
    # Webアプリケーションの静的ファイルをホストするためのS3バケットを作成します。
    # 空ではないS3 バケットを削除するには Lambdaでカスタムリソースを使用して中身を空にして削除する必要がある。


  # CodeBuildプロジェクト定義
  CodeBuildProject:
    Type: 'AWS::CodeBuild::Project' # リソースタイプ AWS CodeBuild Project
    Properties:
      Name: WebApplicationPipelineCodeBuild # プロジェクト名
      Description: CodeBuild project for building and deploying web application # プロジェクト説明
      ServiceRole: !GetAtt CodeBuildRole.Arn # CodeBuildが使用するIAMロールARN
      Artifacts: # アーティファクト設定
        Type: S3 # 保存先 S3
        Location: !Sub 'arn:aws:s3:::${WebAppS3Bucket}' # S3バケットのARNを指定
        Packaging: ZIP # アーティファクトのパッケージング ZIP
      Environment: # ビルド環境
        ComputeType: BUILD_GENERAL1_SMALL # コンピューティングタイプ
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0 # コンピューティングイメージ
        Type: LINUX_CONTAINER # コンピューティングタイプ
        EnvironmentVariables: # 環境変数
          - Name: S3_BUCKET # 環境変数名 S3バケット
            Type: PLAINTEXT # 環境変数タイプ プレーンテキストで処理
            Value: WebAppS3BucketName # 環境変数値 S3バケット名
      Source: # ソースの設定
        Type: GITHUB # ソースの種類 GitHub
        Location: !Ref GitHubRepoURL # GitHubリポジトリのURL
        GitCloneDepth: 1 # ソースのクローン数
        BuildSpec: buildspec.yml # ソースのビルドスペック

  CodeBuildRole: # CodeBuild IAM ロール定義
    Type: 'AWS::IAM::Role' # リソースタイプ AWS IAM Role
    Properties: # プロパティ設定
      AssumeRolePolicyDocument: # IAMロールのAssumeRolePolicyDocument
        Version: '2012-10-17' # IAMロールポリシーのバージョン
        Statement: # ステートメント
          - Effect: Allow # 許可設定
            Principal: # プリンシパル設定
              Service: codebuild.amazonaws.com # IAMロールのサービス CodeBuild
            Action: 'sts:AssumeRole' # IAMロールのアクション sts:AssumeRole 一時的セキュリティトーン取得
      Policies: # ロールポリシー
        - PolicyName: CodeBuildPolicy # ロールポリシー名
          PolicyDocument: # ロールポリシーのドキュメント
            Version: '2012-10-17' # ロールポリシーのバージョン
            Statement: # ステートメント
              - Effect: Allow # 許可設定
                Action: # 実行許可アクション
                  - 's3:PutObject' # S3へのアップロード
                  - 's3:GetObject' # S3からのダウンロード
                Resource: !Sub 'arn:aws:s3:::${WebAppS3Bucket}/*' # 特定のS3バケットのオブジェクトへのアクセス
              - Effect: Allow # 許可設定
                Action: # 実行許可アクション
                  - 'logs:CreateLogGroup' # ロググループの作成
                  - 'logs:CreateLogStream' # ログストリームの作成
                  - 'logs:PutLogEvents' # ログの記録
                Resource: '*' # すべにアクセス

  CodeDeployApplication: # CodeDeployアプリケーション
    Type: 'AWS::CodeDeploy::Application'
    Properties:
      ApplicationName: CodeDeployApplication

  CodeDeployDeploymentGroup:
    Type: 'AWS::CodeDeploy::DeploymentGroup'
    Properties:
      DeploymentGroupName: CodeDeployDeploymentGroup
      ApplicationName: !Ref CodeDeployApplication
      ServiceRoleArn: !GetAtt CodeDeployRole.Arn  # CodeDeployが使用するIAMロールのARNを指定してください
      DeploymentConfigName: CodeDeployDefault.AllAtOnce  # デプロイメントの構成
      Ec2TagFilters:  # EC2タグを使用してデプロイメントグループをフィルタリング
        - Key: Name
          Value: !Ref EC2InstanceName # EC2インスタンス名(今回は作成済)
          Type: KEY_AND_VALUE # KEY_ONLY: 指定したキーのみ, VALUE_ONLY: 指定した値のみ, KEY_AND_VALUE: 指定したキーと値

  CodeDeployRole: # CodeDeploy用ロール
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codedeploy.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: CodeDeployPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: '*'
                Resource: '*'


  WebAppCodePipeline: # CodePipeline
    Type: 'AWS::CodePipeline::Pipeline'
    Properties:
      ArtifactStore:
        Location: !Ref WebAppS3BucketName
        Type: S3
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Provider: GitHub
                Version: 1
              Configuration:
                Owner: '{{resolve:secretsmanager:GitHubSecret:SecretString:Owner}}' # GitHubのリポジトリのオーナー
                Repo: '{{resolve:secretsmanager:GitHubSecret:SecretString:Repo}}'  # GitHubのリポジトリ名
                Branch: main
                OAuthToken: '{{resolve:secretsmanager:GitHubSecret:SecretString:OAuthToken}}'  # GitHubのOAuthトークン
              OutputArtifacts:
                - Name: SourceOutput
          # ソースステージの設定

        - Name: PiplineBuild
          Actions:
            - Name: BuildAction
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: WebApplicationPipelineCodeBuild
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput
          # ビルドステージの設定

        - Name: PiplineDeploy
          Actions:
            - Name: DeployAction
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CodeDeploy
                Version: 1
              Configuration:
                ApplicationName: CodeDeployApplication
                DeploymentGroupName: CodeDeployDeploymentGroup
              InputArtifacts:
                - Name: BuildOutput
          # デプロイステージの設定

  CodePipelineRole: # CodePipeline用ロール
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: CodePipelinePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow # 許可設定
              Action: # 実行許可アクション
                - 's3:PutObject' # S3へのアップロード
                - 's3:GetObject' # S3からのダウンロード
              Resource: !Sub 'arn:aws:s3:::${WebAppS3Bucket}/*' # 特定のS3バケットのオブジェクトへのアクセス
            - Effect: Allow # 許可設定
              Action: # 実行許可アクション
                - 'logs:CreateLogGroup' # ロググループの作成
                - 'logs:CreateLogStream' # ログストリームの作成
                - 'logs:PutLogEvents' # ログの記録
              Resource: '*' # すべにアクセス
        - PolicyName: PiplineCodeBuildDeployPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 'codebuild:StartBuild'
                  - 'codebuild:BatchGetBuilds'
                Resource: !GetAtt CodeBuildProject.Arn
              - Effect: Allow
                Action:
                  - 'codedeploy:CreateDeployment'
                  - 'codedeploy:GetDeployment'
                  - 'codedeploy:GetDeploymentConfig'
                  - 'codedeploy:GetApplicationRevision'
                  - 'codedeploy:RegisterApplicationRevision'
                Resource: '*'
```
### 課題
・CloudFormationスタックを削除で、構築したすべての環境が削除されることを想定したていたが、S3にオブジェクトが残っていると削除できない。対応として、カスタムリソースでLambdaを使ってオブジェクトを削除することを検討。
・S3にGitHubのコードをコピーしなくてもそのままBuildしたアーティファクトをS3に保存するようにした方が効率的。できるか検討してみる。

以上