一番簡単なCodeBuild

# はじめに

CodeBuildを触ってみたいと思い、ハンズオンなど探してみたのですが、CodePipelineの例ばかりでした。
そのためCodeBuildをメインとして自身で手を動かした内容をまとめておきました。

# 概要
本記事では以下の事をやっています。

- CodeBuildの実行
    - 取り扱うファイルを格納するため、CodeCommitを用意します
- CodeBuildで利用するインスタンスから、ファイルをS3に格納
    - 格納用のS3バケットを用意します

# 参考

https://techtekt.persol-career.co.jp/entry/tech/230302_01

# Level0:動かすだけ

まず、CodeCommitを作ります。以下は作成するCFnテンプレートですが、コンソールから作成してもよいです。
```yaml:createCodeCommit.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MyCodeCommitRepo:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: forCodeBuild
```

作成したCodeCommitに、以下の`buildspec.yml`ファイルをコミットします。

```yaml:buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - echo "Hello, CodeBuild!"
      - echo "This is a sample build."
```

今回は単純なので、コンソールで操作しています。コンソールからファイルをアップしたり、新規で作成することができます。
最初に作成されるブランチ名は`main`になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/c2d5813a-8b5f-21a8-06db-cede61d36fd0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/bb334e58-1ab7-2d01-3c6d-26d6ba5b369b.png)

その後、CodeBuildのプロジェクトを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/b50e1128-44c4-bd78-daf7-7f7d34eea19a.png)

ソースは先ほど作成し、コミット時に作られた`main`ブランチを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/77b23a70-21dd-021f-ea3c-87baf0ee0cbd.png)

環境も適当に指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/28899ad5-5d39-fe53-1941-f2107aae3dc8.png)

ロールもコンソールで作ってもらえます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/3b744e10-c972-47f2-6a6e-b230ae6348b5.png)

ビルド仕様は、さっきコミットしたファイルを使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/07984974-8aa7-01cf-06bf-faf8b9a192bc.png)

バッチ設定、アーティファクトは何も設定しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/8540c0da-dde9-0b2f-b108-f30b4acd4193.png)

ログは、`buildspec.yml`でのechoを見るために、CloudWatchを使います。グループ名、ストリーム名は空で問題ないです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/559f8bbc-4a47-1b5b-afc6-2d7bdabeca29.png)

これで作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/6f0f71a8-e124-31b7-1735-1298b7adc906.png)

作成後、ビルドを開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/559bb5f3-d059-c3d1-fe64-987d66fef5ef.png)


ビルドログに色々出てきます。`buildspec.yml`のechoで指定した文字列が出力されているか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/47018606-1fc3-2200-3e4b-08190646fa35.png)

問題なければ、ステータスが成功になっているはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/a0163c60-7f46-c0f1-eae1-d846f5efffb8.png)

## CFnで作る
（ほぼ）コンソールでやってきたことを、CFnテンプレートで作成しました。
以下のリソースを作成するCFnテンプレートになります。
- CodeCommitのリポジトリ
- CodeBuild用のCloudWatchロググループ
- CodeBuild用のIAMロール
    - ロググループに出力権限付与
    - リポジトリからPULL権限付与
    - コンソールが作成する際に付与される以下の権限はつけない
        - CodePipelineのバケット用のポリシー
        - レポート用のポリシー
- CodeBuildプロジェクト
    - CodeCommitリポジトリの`main`ブランチを見ています

<details><summary>クリックで表示</summary><div>

```yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  ProjectName:
    Type: String
    Default: testProjectByCfn
  RepositoryName:
    Type: String
    Default: forCodeBuildByCfn

Resources:
  MyCodeCommitRepo:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepositoryName

  LogGroupForCB:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/codebuild/${ProjectName}"
      RetentionInDays: 3653   # 未指定時は「失効しない」

  RoleForCB:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: writeLogs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !GetAtt LogGroupForCB.Arn
                  - !Join
                    - ':'
                    - - !GetAtt LogGroupForCB.Arn
                      - '*'
        - PolicyName: pullCodeCommit
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codecommit:GitPull
                Resource:
                  - !GetAtt MyCodeCommitRepo.Arn
        # CodePipelineのバケット用のポリシーは作成しない
        # レポート用のポリシーも作成しない

  CBProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref ProjectName
      ServiceRole: !GetAtt RoleForCB.Arn
      Artifacts:
        Type: no_artifacts
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        # 以下の Image identifier を参照
        ## https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
      Source:
        Type: CODECOMMIT
        Location: !GetAtt MyCodeCommitRepo.CloneUrlHttp
        GitCloneDepth: 1 # 指定しないと、フル
      SourceVersion: refs/heads/main
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
        S3Logs:
          Status: DISABLED
```
</div></details>

作成後はCodeCommitの`main`ブランチに`buildspec.yml`をコミットし、プロジェクトを実行することで、コンソール時と同じ結果が得られるはずです。


# Level1:チェックを入れる

`buildspec.yml`の中身を書き換えて、一緒にコミットするファイルの中身に特定の文字列があるかをチェックするようにします。

チェックするファイルを以下のように追加します。
```text:sample.txt
hogehoge
mogemoge
togetoge
```

チェック処理はgrepを使ってみます。
```diff_yaml:buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - echo "Hello, CodeBuild!"
      - echo "This is a sample build."
+     - grep -q "mogemoge" sample.txt
```

CodeCommitに追加後、プロジェクトを実行します。特に問題なく成功するはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/0428d66a-ccaf-3bc6-c527-04ac0d166f79.png)

わざと失敗するため、`sample.txt`の中身を書き換えてコミットします。
```text:sample.txt
hogehoge
pogepoge
togetoge
```
実行すると失敗と出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/551d18cc-2552-60ee-6f01-39045a0db65c.png)
ログメッセージは以下のようになっています。
```
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: grep -q "mogemoge" sample.txt. Reason: exit status 1
```

エラーの理由をメッセージで出すため、このようにしてみました。
```diff_yaml:buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - echo "Hello, CodeBuild!"
      - echo "This is a sample build."
-     - grep -q "mogemoge" sample.txt
+     - |
+       if ! grep -q "mogemoge" sample.txt; then
+         echo "Test failed: 'mogemoge' not found in sample.txt"
+         exit 1
+       fi
```
以下のように、指定したメッセージを出してエラーにしてくれます。
```
Test failed: 'mogemoge' not found in sample.txt
```

# Level2:S3に出力する
次は、CodeBuildで扱ったファイルを、S3に置いてみます。

## 出力用バケットを作成
以下のCFnテンプレートでバケットを作成します。
```yaml:createS3ForCodebuild.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "out-artifacts-ap-northeast-1-${AWS::AccountId}"
```

## CodeBuildに設定
作成したCodeBuildに、コンソールから出力先を指定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/23f54a20-b441-ecea-703b-e0d1a718eddd.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/0db965c4-72b5-8437-c63e-eb0a584f9773.png)

コンソールで以下のチェックを入れると、使っているIAMロールに出力先バケットへの権限を付与してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/f5120ef0-7c1b-8341-2874-2d117639a89b.png)

ポリシーに以下が追加されていました。
```json
        {
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::out-artifacts-ap-northeast-1-123456789012",
                "arn:aws:s3:::out-artifacts-ap-northeast-1-123456789012/*"
            ],
            "Action": [
                "s3:PutObject",
                "s3:GetBucketAcl",
                "s3:GetBucketLocation"
            ]
        },
```

## buildspec.ymlに記載
出力する旨を`buildspec.yml`に記載します。

```yaml:buildspec.yml(最後に追記)
artifacts:
  files:
    - '**/*'
```

実行すると、指定したバケットに保存されます。再実行の際は上書きされました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/5d31a1be-6ae0-cbb2-7ffa-88d63f400398.png)


## CFnで作る

先のCFnテンプレートに、以下を追加したものになります。
- S3バケット
- バケットへのアクセス権限
    - IAMロールに
- Artifactsセクション

先の環境を作成したCFnスタックを、以下で更新します。

<details><summary>クリックで表示</summary><div>

```yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  ProjectName:
    Type: String
    Default: testProjectByCfn
  RepositoryName:
    Type: String
    Default: forCodeBuildByCfn

Resources:
  MyCodeCommitRepo:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepositoryName

  MyS3Bucket:
    Type: AWS::S3::Bucket

  LogGroupForCB:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/codebuild/${ProjectName}"
      RetentionInDays: 3653   # 未指定時は「失効しない」

  RoleForCB:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: writeLogs
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !GetAtt LogGroupForCB.Arn
                  - !Join
                    - ':'
                    - - !GetAtt LogGroupForCB.Arn
                      - '*'
        - PolicyName: pullCodeCommit
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codecommit:GitPull
                Resource:
                  - !GetAtt MyCodeCommitRepo.Arn
        - PolicyName: putBucketArtifacts
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                Resource:
                  - !GetAtt MyS3Bucket.Arn
                  - !Join
                    - '/'
                    - - !GetAtt MyS3Bucket.Arn
                      - '*'

  CBProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref ProjectName
      ServiceRole: !GetAtt RoleForCB.Arn
      Artifacts:
        Type: S3
        Location: !Ref MyS3Bucket
        EncryptionDisabled: true
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        # 以下の Image identifier を参照
        ## https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
      Source:
        Type: CODECOMMIT
        Location: !GetAtt MyCodeCommitRepo.CloneUrlHttp
        GitCloneDepth: 1 # 指定しないと、フル
      SourceVersion: refs/heads/main
      LogsConfig:
        CloudWatchLogs:
          Status: ENABLED
        S3Logs:
          Status: DISABLED
```

</div></details>


# Level3:作成したファイルだけ置く
先ほどは、CodeCommitからPULLしたファイルをすべてS3に置きました。今度はチェック後、そのファイルを元に新しくファイルを作成しS3に置く、というのをやってみます。
sample.txt内の文字列をすべて大文字にしてsample2.txtを作り、そのファイルだけをS3に置くようにします。
```diff_yaml:buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - echo "Hello, CodeBuild!"
      - echo "This is a sample build."
      - |
        if ! grep -q "mogemoge" sample.txt; then
          echo "Test failed: 'mogemoge' not found in sample.txt"
          exit 1
        fi
+     - cat sample.txt | tr [:lower:] [:upper:] > sample2.txt

artifacts:
  files:
-   - '**/*'
+   - sample2.txt
```

先ほどのファイルが残っている場合は、S3を空にしておいてから実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/eb335ed7-84c0-269a-c519-4710932a07b9.png)
中身もちゃんと大文字になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/96cdd593-ac78-08a0-df7f-1421ab6e72a3.png)

buildセクションで失敗するようにしていた場合、ファイルは何も変更ありませんでした。ちゃんと止まるようです。



# 片付け
以下のリソースが作成されていますので、不要であれば削除してください。CFnテンプレートで作成した場合も、S3バケットを空にした後にスタックの削除を行ってください。
- S3
    - バケットを空にしてから
- CodeCommit
- CodeBuild
- IAMロール・ポリシー
    - CodeBuildプロジェクト作成時に自動作成されたもの
- Cloudwatchロググループ
 
# おわりに
今回はCodeBuildの初歩の初歩を自分なりにまとめて記事にしました。
私のように「CodeBuildだけ試しに使ってみたい」という方のお役に立てれば幸いです。