【AWS】 CodeBuild/CodePipelineをCloudFormationコードで深堀りする

## はじめに
業務でAWSにおいてCI/CD環境を構築する必要があるため、そもそもCICDに関する理解が浅いと感じたため本記事にてアウトプットをいたします。

今回はAWSのCodePipelineとCodeBuildに絞っていきたいと思います。

## そもそもCI/CDとは
CICDとは`Continuous Integration`(継続的インテグレーション), `Continous Deliversy`(継続的デリバリー)の略称です。

`Continuous Integration`とはAWS公式の言葉を引用すると
> ソフトウェアのリリースプロセスにおけるビルド段階と単体テスト段階のことを指します。リビジョンがコミットされるたびに、自動化されたビルドとテストが開始されます。

https://aws.amazon.com/jp/devops/continuous-integration/

つまりどういうことか？
EC2上でDockerのイメージを作っていく工程を例に解説します。

1. Dockerfileを作る
2. Dockerfileを基にDocker Hubからイメージを取得し、buildを実行する
3. build実行後、Dockerコンテナを起動し、アプリケーションのテストを実行する
4. Dockerイメージが出来上がり、テストも完了後、そのイメージをECRにPushする (正確にはECRにPushできるようにタグを編集後、Pushを実行する)

図にすると以下のような流れになるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/0f8e8de5-704f-963f-8243-95965c7cb460.png)


この作業、手作業で一つ一つの工程を実施することはもちろんできます。
が、より効率的にやろうと思った場合、この一連の流れをあることをトリガーにその後の作業を自動でやってくれると便利だと思いませんか？

はい、これらの作業をAWSのCodeBuildを使うことで(厳密にはCodeBuildだけではありませんが…)GitHubへの`SourcePush`をトリガーに、一連の流れを自動実行できるように仕組み化することが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/fcd24337-7f4f-0ed0-6f35-952c77b04934.png)



## CodeBuildとは
CloudFormationテンプレートの内容を抜粋しながら説明します。
全体のコードについてはGitHubにありますのでそちらを参照ください。
実際にデプロイしたいという方は`s3-cicd/s3-pipeline-codestar.yml`を先にデプロイし、その後に`codebuild/codebuild.yml`をデプロイすることで動作確認することが可能かと思います。

`s3-cicd/s3-pipeline-codestar.yml`の方は資材バックアップ用のものです。
GitHubにPushを実行するとPipeliineが起動し、`CodeBuild`が起動し、GitHubにPushした資材がS3にバックアップとして保存されます。
イメージは以下です。
GitHubに必要な資材をPushすると、`Pipeline`が動き出し、`CodeBuild`で`aws S3 sync`コマンドが実行され、GitHubにある資材がS3に同期されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/d9c8bb3b-feb1-b10f-440b-eeaf0b185739.png)


https://github.com/Kyrieee-ops/s3-cicd/blob/main/s3-pipeline-codestar.yml

https://github.com/Kyrieee-ops/codebuild/blob/master/codebuild.yml

<details>
<summary>s3-pipeline-codestar.yml</summary>

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to build and push Docker images to S3 from GitHub.

Parameters:
  ConnectionArn:
    Description: The CodeConnections ARN for your source repository.
    Type: String
  FullRepositoryId:
    Description: The full repository ID to use with your CodeConnections connection.
    Type: String
    Default: "Kyrieee-ops/s3-cicd"
  BranchName:
    Description: The branch name to use with your CodeConnections connection.
    Type: String
    Default: main
  CodePipelineName:
    Description: The CodePipeline pipeline name that will deploy to your CloudFormation stack.
    Type: String
    Default: s3-codepipeline
  BucketNameBackup:
    Type: String
    Default: s3-cicd-backup
    
Resources:
# ------------------------------------------------------------#
# CodeBuild
# buildspecを実行するプロジェクトを定義する
# GitHubに保存されているソースコードのビルドを行いArtifact
# ------------------------------------------------------------#
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: github-to-s3-build
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Sub |
          version: 0.2
          phases:
            build:
              commands:
              - echo aws s3 sync....
              - aws s3 sync . s3://${BucketNameBackup}-${AWS::AccountId}/template --delete

      Environment:
        ComputeType: BUILD_GENERAL1_SMALL # ビルドする際のリソースを指定する(AWS公式に環境環境タイプの容量一覧があるので参考にする)
        Image: aws/codebuild/standard:5.0
        Type: LINUX_CONTAINER # ビルドの実行タイプをLinuxベースのコンテナを使用する
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: CODEPIPELINE # build成果物が不要な場合はNO_ARTIFACTSを指定する
# ------------------------------------------------------------#
# CodeBuildが使用するロールの定義
# ------------------------------------------------------------#
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: codebuild-role-for-s3
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 
                - codebuild.amazonaws.com
            Action: sts:AssumeRole

# ------------------------------------------------------------#
# CodeBuildRoleにアタッチするポリシー
# カスタマーインラインポリシー
# ------------------------------------------------------------#
  CodeBuildPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${CodeBuildProject}:*"
          - Action:
              - codebuild:StartBuild
              - codebuild:BatchGetBuilds
              - s3:PutObject
              - s3:GetObject
              - s3:ListBucket
              - s3:DeleteObject
            Effect: Allow
            Resource: 
              - !GetAtt CodeBuildProject.Arn
              # - !ImportValue s3bucketbackupArn 
              - !GetAtt s3bucketbackup.Arn # s3バケットリソースの論理名でs3バケットのARNを取得する
              - !Join
                - '/'
                # - - !ImportValue s3bucketbackupArn
                - - !GetAtt s3bucketbackup.Arn
                  - '*'
      PolicyName: CodeBuildPolicy
      Roles:
        - !Ref CodeBuildRole

# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------#
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref s3bucketbackup
        Type: S3
      # ExecutionMode: PARALLEL # Code Pipelineが実行中であっても並列実行を制御するためにキューに入る設定
      Name: !Ref CodePipelineName
      # PipelineType: V2
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId: # Sourceコードの取得を行うフェーズを定義
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection # CodeStarSourceConnectionを使用してGitHubと連携をする
                Version: '1'
              Configuration:
                ConnectionArn: !Ref ConnectionArn # 事前にCodeConnectionArnを作成しておく必要があるSource
                FullRepositoryId: !Ref FullRepositoryId
                BranchName: !Ref BranchName
                # PollForSourceChanges: false  # 自動トリガーを無効化 -> ここをfalseに設定しておかないとCloudFormation deploy時にCodePipelineが起動してしまうため
              OutputArtifacts:
                - Name: SourceOutput # ソースステージの出力 -> 指定されたリポジトリからコードを取得しそれをSourceOutputという名前で出力アーティファクトとして生成する
              RoleArn: !GetAtt CodePipelineSourceActionRole.Arn
              RunOrder: 1 
        - Name: Build
          Actions:
            - Name: CodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              InputArtifacts:
                - Name: SourceOutput
              Configuration:
                ProjectName: !Ref CodeBuildProject
              OutputArtifacts:
                - Name: BuildOutput        
    DependsOn:
      - CodePipelineRoleDefaultPolicy
# ------------------------------------------------------------#
# CodePipelineが使用するロールを指定する
# codepipelineに対してAssumeRole -> 要するにCodePipelineが権限を引き受けることができる
# CloudFormationでロール名を指定しない場合、スタック名 + 論理ID + ランダムな文字列で生成する
# ------------------------------------------------------------#  
  CodePipelineRole: 
    Type: AWS::IAM::Role
    Properties:
      RoleName: codepipeline-role-for-s3
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
        Version: '2012-10-17'

# ------------------------------------------------------------#
# CodePipelineRoleにアタッチするポリシー
# CodePipelineが指定のS3バケットに対する操作を行うことができる
# ------------------------------------------------------------#
  CodePipelineRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action: # CodePipelineが指定のS3バケットに対して操作を行えるようにしている
              - s3:Abort*
              - s3:DeleteObject*
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
              - s3:PutObject
              - s3:PutObjectLegalHold
              - s3:PutObjectRetention
              - s3:PutObjectTagging
              - s3:PutObjectVersionTagging
              - s3:PutObjectAcl
              - s3:PutObjectVersionAcl
            Effect: Allow
            # 以下ではS3バケットレベルの操作とオブジェクトレベルの操作を許可するために指定している
            # `Join`でS3バケットに`/*`のように結合し、`arn:aws:s3:::my-bucket/*`を指定
            # つまり対象のS3バケットの全てのオブジェクトに対して許可をする、ということを示している
            Resource:
              # - !ImportValue s3bucketbackupArn
              - !GetAtt s3bucketbackup.Arn # s3バケットリソースの論理名でs3バケットのARNを取得する
              - !Join
                - '/'
                # - - !ImportValue s3bucketbackupArn
                - - !GetAtt s3bucketbackup.Arn
                  - '*'

          - Action: # CodePipelineに対してCodeBuildの操作権限を付与する
            - codebuild:StartBuild
            - codebuild:BatchGetBuilds
            - codebuild:BatchGetProjects
            Effect: Allow
            Resource: !GetAtt CodeBuildProject.Arn

          - Action: sts:AssumeRole # あくまでCodePipelineがCodeBuildプロジェクトのロールを引き受ける許可
            Effect: Allow
            Resource: 
            - !GetAtt CodeBuildRole.Arn
            - !GetAtt CodePipelineSourceActionRole.Arn
        Version: '2012-10-17'
      PolicyName: CodePipelineRoleDefaultPolicy
      Roles:
        - !Ref CodePipelineRole

# ------------------------------------------------------------#
# CodePipelineのソースアクションに必要なロールを定義
# GitHubからコードを取得するアクション
# ------------------------------------------------------------#
  CodePipelineSourceActionRole: 
    Type: AWS::IAM::Role
    Properties:
      RoleName: codepipeline-source-from-github-role
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root # 使用しているAWSアカウントに対して権限を引き受けることができる
        Version: '2012-10-17'
# ------------------------------------------------------------#
# ソースアクション(コードを取得するアクション)が必要とする権限を定義する
# ------------------------------------------------------------#
  CodePipelineSourceActionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action: codestar-connections:UseConnection # CodePipelineがGitHubのソースプロバイダーに接続し、GitHubからソースを取得する
            Effect: Allow
            Resource: !Ref ConnectionArn # AWS ⇔ GitHub接続名のARNを参照し、GitHubとの接続を使用してコードを取得が可能となる
          # CodePipelineがGitHubから取得したファイルをS3へアップロードするために必要なActionの操作権限を定義
          - Action:
              - s3:Abort*
              - s3:DeleteObject*
              - s3:PutObject
              - s3:PutObjectLegalHold # 必要に応じてオブジェクトの保持ポリシーを設定する場合に必要
              - s3:PutObjectRetention
              - s3:PutObjectTagging
              - s3:PutObjectVersionTagging # アップロードしたファイルにタグを付与する場合に必要
            Effect: Allow
            Resource:
              # - !ImportValue s3bucketbackupArn
              - !GetAtt s3bucketbackup.Arn
              - !Join
                - '/'
                # - - !ImportValue s3bucketbackupArn
                - - !GetAtt s3bucketbackup.Arn
                  - '*'
          - Action:
              - s3:PutObjectAcl
              - s3:PutObjectVersionAcl
            Effect: Allow
            Resource: !Join
              - /
              # - - !ImportValue s3bucketbackupArn
              - - !GetAtt s3bucketbackup.Arn
                - '*'
        Version: '2012-10-17'
      PolicyName: CodePipelineSourceActionRoleDefaultPolicy
      Roles:
        - !Ref CodePipelineSourceActionRole
# ------------------------------------------------------------#
# S3　backup
# ------------------------------------------------------------# 
  s3bucketbackup:
    Type: AWS::S3::Bucket
    Properties: 
      AccessControl: Private
      BucketName: !Sub ${BucketNameBackup}-${AWS::AccountId}
      PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
      VersioningConfiguration: 
        Status: Suspended
      BucketEncryption:
          ServerSideEncryptionConfiguration:
            - ServerSideEncryptionByDefault:
                SSEAlgorithm: AES256
              BucketKeyEnabled: true
      Tags:
        - Key: Name
          Value: !Sub ${BucketNameBackup}-${AWS::AccountId}

  s3BucketPolicyBackup:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref s3bucketbackup
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Action:
            - "s3:GetObject"
            Effect: Allow
            Resource: !Sub "arn:${AWS::Partition}:s3:::${BucketNameBackup}-${AWS::AccountId}/*"
            Principal: 
              AWS: !Sub arn:${AWS::Partition}:iam::674078804300:user/Administrator

Outputs:
     s3bucketbackup:
      Value: !Ref s3bucketbackup
      Export:
        Name: s3bucketbackup  
     s3BucketArn:
      Value: !Sub arn:aws:s3:::${s3bucketbackup}
      Export: 
        Name: s3bucketbackupArn
```
</details>

<details>
<summary>codebuild.yml</summary>

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to build and push Docker images to ECR from GitHub.

Parameters:
  ECRRepositoryName:
    Description: The name of the ECR repository to push the Docker image.
    Type: String
    Default: amazonlinux-repository
  CodeBuildRoleName:
    Description: The CodeBuild role name
    Type: String
    Default: codebuild-role-for-ecr
  CodeBuildProjectName:
    Description: The CodeBuild name
    Type: String
    Default: github-to-ecr-build   
  GitHubConnectionArn:
    Description: The ARN of the GitHub connection for CodePipeline.
    Type: String
  FullRepositoryId:
    Description: The full repository ID to use with your CodeConnections connection.
    Type: String
    Default: "Kyrieee-ops/codebuild"
  BranchName:
    Description: The branch name to use with your GitHub repository.
    Type: String
    Default: master
  CodePipelineName:
    Description: The CodePipeline name that will deploy to your CloudFormation stack.
    Type: String
    Default: codebuild-codepipeline
  CodePipelineSourceActionRoleName:
    Description: The CodePipeline role name for ecr
    Type: String
    Default: codepipeline-source-from-github-role-for-ecr
  CodePipelineRoleName: 
    Description: The CodePipeline role name for codebuild
    Type: String
    Default: codepipeline-role-for-codebuild

Resources:
  # ECRリポジトリの作成
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Sub ${ECRRepositoryName}
      Tags: 
        - Key: Name
          Value: !Sub ${ECRRepositoryName}

# ------------------------------------------------------------#
# CodeBuildがECRにDockerImageをPushするために必要なロール
# codebuild-role-for-ecr
# ------------------------------------------------------------#
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${CodeBuildRoleName}
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 
                - codebuild.amazonaws.com
            Action: 
              - sts:AssumeRole
      Policies:
        - PolicyName: CodeBuildPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                Resource: !Join
                  - /
                  - - !ImportValue s3bucketbackupArn
                    - '*'
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*"
              - Effect: Allow
                Action:
                  - ecr:PutImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                  - ecr:BatchCheckLayerAvailability
                  - ecr:DescribeRepositories
                  - ecr:GetAuthorizationToken
                Resource: 
                  - "*"
# ------------------------------------------------------------#
# CodeBuild
# buildspecを実行するプロジェクトを定義する
# GitHubに保存されているソースコードのビルドを行いArtifact
# envはphasesの外で書かないとエラーになる
# ------------------------------------------------------------#
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub ${CodeBuildProjectName}
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Sub |
          version: 0.2
          env:
            variables:
              IMAGE_REPO_NAME: ${ECRRepositoryName}
              IMAGE_TAG: dev
              AWS_ACCOUNT_ID: ${AWS::AccountId}
              AWS_DEFAULT_REGION: ${AWS::Region}              
          phases:
            pre_build:
              commands:
                - echo Logging in to $AWS_DEFAULT_REGION/$AWS_ACCOUNT_ID Amazon ECR...
                - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...          
                - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
                - docker image ls
                - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
                - docker image ls
            post_build:
              commands:
                - echo Build completed on `date`
                - echo Pushing the Docker image to ECR...
                - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:5.0
        Type: LINUX_CONTAINER
        PrivilegedMode: true # Dockerビルドのために必要
      ServiceRole: !Ref CodeBuildRole
      Artifacts:
        Type: CODEPIPELINE
# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------#
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub ${CodePipelineName}
      RoleArn: !GetAtt CodePipelineRole.Arn
      ArtifactStore:
        Type: S3 # アーティファクトの保存場所をS3に指定
        Location: !ImportValue s3bucketbackup # 事前に作成したS3バケットのインポート
      Stages:
        - Name: Source
          Actions:
            - Name: GitHubSource
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection
                Version: '1'
              Configuration:
                ConnectionArn: !Sub ${GitHubConnectionArn}
                FullRepositoryId: !Sub ${FullRepositoryId}
                BranchName: !Sub ${BranchName}
              OutputArtifacts:
                - Name: SourceOutput
              RoleArn: !GetAtt CodePipelineRole.Arn   
              RunOrder: 1 # 実行順序
        - Name: Build
          Actions:
            - Name: CodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              InputArtifacts:
                - Name: SourceOutput
              Configuration:
                ProjectName: !Sub ${CodeBuildProject}
              # OutputArtifacts:
              #   - Name: BuildOutput
# ------------------------------------------------------------#
# CodePipelineRole
# CodeBuildを実行する用途
# ECRに保存されたイメージを使用する
# ------------------------------------------------------------#
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${CodePipelineRoleName}
      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:
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuilds
                  - ecr:GetAuthorizationToken
                  - codestar-connections:UseConnection # GitHub接続を使用するための権限を追加
                Resource: "*"
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:DeleteObject
                  - s3:ListBucket
                # `arn:aws:s3:::${s3bucketbackup}`と`arn:aws:s3:::${s3bucketbackup}/*`イコール
                Resource: 
                - !ImportValue s3bucketbackupArn
                - !Join
                  - '/'
                  - - !ImportValue s3bucketbackupArn
                    - '*'
# ------------------------------------------------------------#
# CodePipelineのソースアクションに必要なロールを定義
# GitHubからコードを取得するアクション
# ECRにイメージをPushする用途
#------------------------------------------------------------#
  CodePipelineSourceActionRole: 
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${CodePipelineSourceActionRoleName}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root # 使用しているAWSアカウントに対して権限を引き受けることができる
        Version: '2012-10-17'      
      Policies:
      - PolicyName: CodePipelineSourceActionRoleDefaultPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - codestar-connections:UseConnection
              Resource: !Sub ${GitHubConnectionArn}
            - Effect: Allow
              Action:
                - s3:Abort*
                - s3:DeleteObject*
                - s3:PutObject
                - s3:PutObjectLegalHold
                - s3:PutObjectRetention
                - s3:PutObjectTagging
                - s3:PutObjectVersionTagging # アップロードしたファイルにタグを付与する場合に必要
              Resource:
                - !ImportValue s3bucketbackupArn
                - !Join
                  - '/'
                  - - !ImportValue s3bucketbackupArn
                    - '*'
            - Effect: Allow
              Action:
                - s3:PutObjectAcl
                - s3:PutObjectVersionAcl
              Resource: !Join
                - /
                - - !ImportValue s3bucketbackupArn
                  - '*'
Outputs:
  ECRRepositoryUri:
    Description: "URI of the ECR repository"
    Value: !GetAtt ECRRepository.RepositoryUri
```
</details>



## GitHubとの接続
GitHubのPushをトリガーに`CodePipeline`を動かすためには、前提としてGitHubとAWSを連携させる必要があります。

```yaml
  ConnectionArn:
    Description: The CodeConnections ARN for your source repository.
    Type: String
  FullRepositoryId:
    Description: The full repository ID to use with your CodeConnections connection.
    Type: String
    Default: "Kyrieee-ops/s3-cicd"
  BranchName:
    Description: The branch name to use with your CodeConnections connection.
    Type: String
    Default: main
```

yamlの中では`Parameter`セクションで定義している以下の部分です。
- `ConnectionArn`: GitHubと接続する際に使用するプロバイダーARNです。
ConnectionARNを発行するにはPipelineの画面から左ペインの[接続]から[接続を作成]ボタンから簡単に作成することが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/24350531-9a3e-ca0d-b387-7c6a4234c53f.png)

この値は外部に公開したくない値となるため、`.gitignore`でシークレットにしておくことをおすすめします。

`FullRepositoryId`: GitHubのリポジトリの名前です。

`BranchName`: GitHubのブランチです。今回は`main`としています。

## CodeBuild (S3 Sync)
CodeBuildでは、ソースの定義とビルドする環境を定義しています。
コードの一部を再掲します。

```yaml
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Sub |
          version: 0.2
          phases:
            build:
              commands:
              - echo aws s3 sync....
              - aws s3 sync . s3://${BucketNameBackup}-${AWS::AccountId}/template --delete

      Environment:
        ComputeType: BUILD_GENERAL1_SMALL # ビルドする際のリソースを指定する(AWS公式に環境環境タイプの容量一覧があるので参考にする)
        Image: aws/codebuild/standard:5.0
        Type: LINUX_CONTAINER # ビルドの実行タイプをLinuxベースのコンテナを使用する
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: CODEPIPELINE # build成果物が不要な場合はNO_ARTIFACTSを指定する
```

### Source:
GitHubへのPushをトリガーにCodePipelineが起動し、`Source:` 以下に記述されているBuildSepcが実行されます。
`phases.build`から以下コマンドが実行されます。
`Source`今回のコードは`aws cli`コマンドを使用して、GitHubにPushされたymlをS3バケットに同期させています。

### Environment:
`Environment`ではBuildを実行する環境を定義しています。
今回のコードの場合、AWS公式の情報から

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-compute-types.html

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/ec2-compute-images.html

実行するコンテナ環境はLinuxであり、
メモリ: 3GB, vCPU: 2vCPUのスペックであることがわかります。
イメージは`Ubuntu 20.04`のディストリビューションを使用したイメージであることがわかります。
コンソール上からはわからないですが、実際に内部ではLinuxが建てられ、Dockerイメージを使用して実行環境が作られているというのがわかりますね。

```yaml
        ComputeType: BUILD_GENERAL1_SMALL 
        Image: aws/codebuild/standard:5.0
        Type: LINUX_CONTAINER 
```

:::note 
今回の`phases`は`build`が指定されていますが、この`phases`にはいくつか種類があります。
- install: 必要なソフトウェアのインストールを実行するフェーズ。
- pre_build: ビルド準備に必要な処理を実行するフェーズ(例: 環境変数設定や認証) -> ECRにPushするにはECRのプライベートリポジトリに対して、Dockerクライアントからプライベートリポジトリに対して認証を得る必要がありますが、その認証を実行するフェーズです。
- build: ソースコードのコンパイルやテストの実行など、ビルドプロセスの主要な部分を担当するフェーズ -> もう少し具体的に言うとDockerイメージをbuildし、ECRにPushする前までのプロセスをここで実行するフェーズです。
- post_build: ビルド後の処理（例: 成果物のアップロードや通知）を実行するフェーズ -> buildフェーズでできあがったDockerイメージをECRにPushするフェーズです。

`s3-pipeline-codestar.yml`ではGitHubにymlなどのファイルをPushするとS3に格納されるymlのため、特にビルドに必要なものやビルド後の処理などが必要ないため、`build`のみとしています。

:::


## CodePipeline
CodePipelineでは、「何を起因にしてトリガーを引くか？」を定義する必要があります。
今回の場合はGitHubのPushがトリガーですね。


コードの一部を再掲します。
```yaml
      Stages:
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId: # Sourceコードの取得を行うフェーズを定義
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection # CodeStarSourceConnectionを使用してGitHubと連携をする
                Version: '1'
              Configuration:
                ConnectionArn: !Sub ${ConnectionArn} # 事前にCodeConnectionArnを作成しておく必要があるSource
                FullRepositoryId: !Sub ${FullRepositoryId}
                BranchName: !Sub ${BranchName}
                # PollForSourceChanges: false  # 自動トリガーを無効化 -> ここをfalseに設定しておかないとCloudFormation deploy時にCodePipelineが起動してしまうため
              OutputArtifacts:
                - Name: SourceOutput # ソースステージの出力 -> 指定されたリポジトリからコードを取得しそれをSourceOutputという名前で出力アーティファクトとして生成する
              RoleArn: !GetAtt CodePipelineSourceActionRole.Arn
              RunOrder: 1 
        - Name: Build
          Actions:
            - Name: CodeBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              InputArtifacts:
                - Name: SourceOutput
              Configuration:
                ProjectName: !Ref CodeBuildProject
              OutputArtifacts:
                - Name: BuildOutput  
```

### Stages:
`Stages`: セクションとはソースコードを取得するための内容を定義しています。

`ActionTypeId`以下では
- `Category`: このアクションのカテゴリを定義します (`Source`のためソースコード取得のフェーズを意味する)
- `Owner`: アクションプロバイダーの所有者は`AWS`です。
- `Provider`: `CodeStarSourceConnection`プロバイダーに`CodeStarSourceConnection`を使用しますが、`CodeStarSourceConnection`とは接続先プロバイダーをGitHubにしたい場合に選択します。


```yaml
      Stages:
        - Name: Source
          Actions:
            - Name: CodeConnections
              ActionTypeId: # Sourceコードの取得を行うフェーズを定義
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection # CodeStarSourceConnectionを使用してGitHubと連携をする
                Version: '1'
```

### Configuration
ソースコードを取得する内容を`Stages`で定義した後は、`Configuration`で具体的な値を定義します。
- AWSと連携しているGitHubは？
- GitHubのリポジトリ名は？
- ブランチ名は？
を定義しています。
```yaml
      Configuration:
        ConnectionArn: !Sub ${ConnectionArn}
        FullRepositoryId: !Sub ${FullRepositoryId}
        BranchName: !Sub ${BranchName}
```

### OutputArtifacts
`OutputArtifacts`で`Stages`で取得した成果物を`SourceOutput`として格納します。
```yaml
              OutputArtifacts:
                - Name: SourceOutput
```

ではその成果物はどこに保存されるのか？は
`ArtifactStore`に記載されているS3ということになります。
```yaml
ArtifactStore:
  Location: !Ref s3bucketbackup
  Type: S3
```

## 検証
では実際に検証してみましょう。

実際に適当な`test.txt`をGitHubに`Commit`し、Pushしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/21c5c0eb-8731-16cd-08b2-fe6b288c50a9.png)

1.git commitします。
```GitBash
$ git commit -m 'test commit'
[main xxxxx] test commit
 2 files changed, 26 insertions(+), a12 deletions(-)
 create mode 100644 test.txt
```

2.git pushします
```GitBash
$ git push
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 748 bytes | 748.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0), pack-reused 0        
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:Kyrieee-ops/s3-cicd.git
   xxxxxx..xxxxxx  main -> main
```
GitPushをトリガーにCodePipelineが起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/e4a1c707-1807-6cf0-dc0d-5b0251d25be7.png)

ほぼ同時にCodeBuildが起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/a977f10b-f9d8-cf5e-8717-f33192def15b.png)

CodeBuildのログを確認すると、CloudFormationのBuildSpecで記述した`aws cli`の`s3 sync`コマンドが実行され、ローカルのGitリポジトリの内容がS3に同期されているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/274ab28c-b7a2-ee42-aac8-e522ffac5690.png)


最終的にS3にtest.txtが追加されているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/e721c0fb-7636-92c0-bb14-eb2b61bd12da.png)

`s3-pipeline-codestar.yml`の流れはこれで理解できたかと思います。

次に`codebuild.yml`の方ですが、こちらはDockerイメージをBuildを実行し、Pythonのユニットテストを実行しECRにPushされるまでの流れです。

## CodeBuild (ECR Push)

DockerイメージをECRにPushする流れを今一度確認してみましょう。
構成図を再掲します。
GitHubにPushを実行すると、CodePipelineが起動し、その後CodeBuildが起動されます。
CodeBuildで実行されている内容は
1. `Dockerfile`に基づいて、`docker image build`が実行され`image`が`Pull`される
2. `Pull`した`image`を基に`Dockerfile`の内容に基づいて`build`を実行
3. `build`が完了し、コンテナが起動
4. 最終的に、`build`が完了したDockerイメージを`ECR`に`Push`する

上記の流れです。
この内容を整理しておくと、CodeBuildの`BuildSpec`に何を書けば良いかが明確になるかと思います。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/fcd24337-7f4f-0ed0-6f35-952c77b04934.png)

以下コードを掲載します。
```yaml
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub ${CodeBuildProjectName}
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Sub |
          version: 0.2
          env:
            variables:
              IMAGE_REPO_NAME: ${ECRRepositoryName}
              IMAGE_TAG: dev
              AWS_ACCOUNT_ID: ${AWS::AccountId}
              AWS_DEFAULT_REGION: ${AWS::Region}              
          phases:
            pre_build:
              commands:
                - echo Logging in to $AWS_DEFAULT_REGION/$AWS_ACCOUNT_ID Amazon ECR...
                - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...          
                - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
                - docker image ls
                - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
                - docker image ls
            post_build:
              commands:
                - echo Build completed on `date`
                - echo Pushing the Docker image to ECR...
                - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

```

### variables
BuildSpec以下で先ほど定義した`docker image build`から`ECR Push`の内容を定義しています。
valiablesでは環境変数を定義します。

- `IMAGE_REPO_NAME: ${ECRRepositoryName}`: ECRのリポジトリ名
- `IMAGE_TAG: dev`: ECRにPushするDockerイメージのタグ名
- `AWS_ACCOUNT_ID: ${AWS::AccountId}`と`AWS_DEFAULT_REGION: ${AWS::Region}`はCloudFormationの組み込み関数

```yaml
        BuildSpec: !Sub |
          version: 0.2
          env:
            variables:
              IMAGE_REPO_NAME: ${ECRRepositoryName}
              IMAGE_TAG: dev
              AWS_ACCOUNT_ID: ${AWS::AccountId}
              AWS_DEFAULT_REGION: ${AWS::Region}
```

### pre_build
`phases`以下からbuildの内容が実行されます。
ここではECRリポジトリにdockerコマンドを使用してログインするコマンドを実行しています。

```yaml
          phases:
            pre_build:
              commands:
                - echo Logging in to $AWS_DEFAULT_REGION/$AWS_ACCOUNT_ID Amazon ECR...
                - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 
```

### build
Dockerfileを基に`build`を実行しています。
以下のDockerfileのbuildを実行をします。
このDockerfileではPythonをインストールし、build実行の中で`Unit Test`を実行後にECRにPushする一連の流れが実行されます。


```Dockerfile
# Amazon Linux 2023をベースにする
FROM amazonlinux:2023.6.20241111.0

# 必要なパッケージをインストール
RUN dnf install -y python3 python3-pip && \
    dnf clean all

# 作業ディレクトリを設定
WORKDIR /app

# アプリケーションコードとテストコードをコピー
COPY app/ /app

# テスト用のPythonライブラリをインストール
RUN pip3 install --no-cache-dir pytest \
 && chmod +x /app/entrypoint.sh

CMD ["/bin/bash", "/app/entrypoint.sh"]
```

CMDで`entrypoint.sh`が実行され、

```sh
#!/bin/bash
echo "Running tests..."
pytest /app --maxfail=5 --disable-warnings --verbose
if [ $? -ne 0 ]; then
    echo "Tests failed. Exiting."
    exit 1
fi
echo "Tests passed. Starting application..."
python3 app.py
```
`pytest`の部分で`test_app.py`が実行され、

```py
import pytest
import app

def test_add():
    assert app.add(1, 2) == 3
    assert app.add(-1, 1) == 0
    assert app.add(0, 0) == 0
```


その後、`python3 app.py`が実行され、アプリケーションファイル`app.py`が動作し、アプリケーションが実行されるという流れになります。
```python
def add(a, b):
    return a + b
```



yamlの話に戻り、タグをECRにPushできる状態のタグに編集をします。

```yaml
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...          
                - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
                - docker image ls
                - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
                - docker image ls
```

### post_build
`post_build`でECRリポジトリにPushを実行しています。

```yaml

            post_build:
              commands:
                - echo Build completed on `date`
                - echo Pushing the Docker image to ECR...
                - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
```

## 検証
では実際に動きを確認してみましょう。
CodePipelineの[変更をリリースする]からリリースをしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/5d844678-92ff-697a-a189-7c8b40cd3b71.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/763f3d68-b1d1-b74d-c43f-b915b5219c40.png)

CodeBuildが実行されるとCloudFormationで定義していたBuildSpecの内容が実行されていることがCodeBuildのログから確認することができます。

```sh
[Container] 2025/01/26 06:16:49.113511 Running on CodeBuild On-demand
[Container] 2025/01/26 06:16:49.113527 Waiting for agent ping
[Container] 2025/01/26 06:16:49.315256 Waiting for DOWNLOAD_SOURCE
[Container] 2025/01/26 06:16:50.758677 Phase is DOWNLOAD_SOURCE
[Container] 2025/01/26 06:16:50.799082 CODEBUILD_SRC_DIR=/codebuild/output/src2468269984/src
[Container] 2025/01/26 06:16:50.799650 YAML location is /codebuild/readonly/buildspec.yml
[Container] 2025/01/26 06:16:50.802038 Setting HTTP client timeout to higher timeout for S3 source
[Container] 2025/01/26 06:16:50.802443 Processing environment variables
[Container] 2025/01/26 06:16:50.851902 No runtime version selected in buildspec.
[Container] 2025/01/26 06:16:50.869552 Moving to directory /codebuild/output/src2468269984/src
[Container] 2025/01/26 06:16:50.871222 Unable to initialize cache download: no paths specified to be cached
[Container] 2025/01/26 06:16:50.900340 Configuring ssm agent with target id: codebuild:70d347c0-7e64-41c1-a132-bb5a9d0e3fae
[Container] 2025/01/26 06:16:50.901217 Successfully updated ssm agent configuration
[Container] 2025/01/26 06:16:50.901738 Registering with agent
[Container] 2025/01/26 06:16:50.940839 Phases found in YAML: 3
[Container] 2025/01/26 06:16:50.940863  BUILD: 6 commands
[Container] 2025/01/26 06:16:50.940868  POST_BUILD: 3 commands
[Container] 2025/01/26 06:16:50.940872  PRE_BUILD: 2 commands
[Container] 2025/01/26 06:16:50.941301 Phase complete: DOWNLOAD_SOURCE State: SUCCEEDED
[Container] 2025/01/26 06:16:50.941391 Phase context status code:  Message: 
[Container] 2025/01/26 06:16:51.011590 Entering phase INSTALL
[Container] 2025/01/26 06:16:51.052717 Phase complete: INSTALL State: SUCCEEDED
[Container] 2025/01/26 06:16:51.052740 Phase context status code:  Message: 
[Container] 2025/01/26 06:16:51.086807 Entering phase PRE_BUILD
[Container] 2025/01/26 06:16:51.123739 Running command echo Logging in to $AWS_DEFAULT_REGION/$AWS_ACCOUNT_ID Amazon ECR...
Logging in to ap-northeast-1/アカウントID Amazon ECR...

[Container] 2025/01/26 06:16:51.127508 Running command aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

[Container] 2025/01/26 06:17:06.298072 Phase complete: PRE_BUILD State: SUCCEEDED
[Container] 2025/01/26 06:17:06.298089 Phase context status code:  Message: 
[Container] 2025/01/26 06:17:06.347421 Entering phase BUILD
[Container] 2025/01/26 06:17:06.348824 Running command echo Build started on `date`
Build started on Sun Jan 26 06:17:06 UTC 2025

[Container] 2025/01/26 06:17:06.393993 Running command echo Building the Docker image...
Building the Docker image...

[Container] 2025/01/26 06:17:06.397646 Running command docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
```
問題なく実行されると、CodePipelineとCodeBuildが成功ステータスが表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/17940614-8ad8-50cf-0aed-283aeb5cf34d.png)


## 最後に
今回CodePipelineとCodeBuildについてまとめてみました。
ECRへのPushまでの流れについてまとめてみました。
EKSやECSに実際にデプロイする部分についても今後はまとめて良ければとおもっておｒ

## 参考
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-docker.html

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-env-vars.html