AWS CodeCommitへのプルリクエストでCodePipelineを起動させてみた

### はじめに
CodeCommitへのpushイベントはCodePipelineがいい感じにトリガー作ってくれるのですが、プルリクエストでも起動させてレビュー前に静的解析ツールなど実行できないかと思い調べてみました。

### 環境

■操作環境
EC2(Amazon Linux)

■使用サービス
・CodePipeline(CodeCommit,CodeBuild)
  　　CodeCommitからソースを取得して、コンテナを起動しLarastanを実行します。

・EventBridge
  　　プルリクエスト作成を検知してLambdaを実行させます。

・Lambda
  　　CodePipelineに設定されているブランチをプルリクエスト元ブランチに変更してCodePipelineを実行させます。

・ECR
  　　CodePipelineで使用するコンテナイメージを保存します。

### 本記事でやったこと

CodeCommitに保存したLaravelのソースへのプルリクエストをトリガーに、CodePipelineを起動してコンテナ立ち上げてプルリクエスト元のブランチのソースにLarastanを実行するまでを自動化します。一連の流れを通すのがメインになるので各サービスについての解説はしていません。


### 手順

###１.実行環境用意
まず、EC2にコンテナイメージ作成とLaravelインストール用の環境を構築します。

GitとDockerをインストール

```
sudo yum install -y git docker
```

docker-composeインストール

```
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```

```
sudo chmod +x /usr/local/bin/docker-compose
```
サービス起動

```
sudo service docker start
```

dockerをec2-userで実行できるようにユーザーグループに加える

```
sudo usermod -a -G docker ec2-user
```

ec2-userでコマンド使用できるか確認する、permission deniedとか出る場合はSSH閉じて接続し直してください。

```
docker info
```

### Dokerコンテナの実行環境構築
作業ディレクトリ作成

```
mkdir /home/ec2-user/docker
```

ここにDockerfileなどのコンテナ作成用のファイルを格納します。

```
mkdir /home/ec2-user/docker/web
```

Laravelのソース格納用、ここをCodeCommitに紐づけます

```
mkdir -p /home/ec2-user/docker/sample-laravel/src
```

Dockerfile作成

```
vi /home/ec2-user/docker/web/Dockerfile
```

```
FROM php:8.0-apache
WORKDIR /var/www
COPY 000-default.conf /etc/apache2/conf-enabled/000-default.conf

RUN apt-get update \
&& apt-get install -y \
git \
zip \
unzip \
vim \
libpng-dev \
libpq-dev

# Composerのインストール
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer

# Laravelで必要になるmodRewriteを有効化する
RUN ln -s /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled/rewrite.load \
&& a2enmod rewrite
```

000-default.conf作成

```
vi /home/ec2-user/docker/web/000-default.conf
```

```
<VirtualHost *:80>
       ServerAdmin webmaster@localhost
       DocumentRoot /var/www/sample-laravel/public
       ErrorLog ${APACHE_LOG_DIR}/error.log
       CustomLog ${APACHE_LOG_DIR}/access.log combined
       <Directory /var/www/sample-laravel/public>
         AllowOverride All
         Require all granted
       </Directory>
</VirtualHost>
```

docker-compose.yml作成

```
vi /home/ec2-user/docker/docker-compose.yml
```

```
services:
  web:
    container_name: web
    build:
      context: ./web
    ports:
        - '8080:80'
    volumes:
      - ./sample-laravel/src:/var/www/sample-laravel
```

コンテナ起動

```
cd /home/ec2-user/docker
```

```
docker-compose up -d
```

起動確認

```
docker ps
```
上手く起動していれば以下のようにwebって名前のコンテナが表示されます。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/864b6795-e8b3-522a-55de-6d64d976329e.png)

コンテナにログイン

```
 docker exec -it web bash
```

コンテナ内でLaravelをインストールします。

```
composer create-project laravel/laravel sample-laravel
```

ログディレクトリなどのパーミッション変更
本当はちゃんと適切に設定してあげないといけないのですが。。。今回は割愛します。

```
cd /var/www/sample-laravel/storage
```

```
chmod -R 777 app logs framework
```

ここでセキュリティグループで8080ポートをアクセスできる状態にすると
http://EC2のパプリックIP:8080
でLaravelの初期画面が表示されると思います。

![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/da07c5a3-fb2b-f576-8ad1-81a83914e0b7.png)

Larastanインストール

```
cd /var/www/sample-laravel
```

```
composer require --dev nunomaduro/larastan
```

一番緩いレベル0で実行

```
./vendor/bin/phpstan analyse -l 0 app
```

エラーなしですね。
![image (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/c8f360fb-8618-5d70-6fee-1ddec720714b.png)

レベル7で実行

```
./vendor/bin/phpstan analyse -l 7 app
```

Laravel導入しただけの状態でも怒られるんですね。。。
![image (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/1d98ab6e-1690-573a-0b61-0f80c2c2aea9.png)

###2.LaravelのコードをCodeCommitリポジトリと紐づける
さてと、準備だけでかなり長くなりましたがここからが本題です。
まずCodeCommitでリポジトリを作成します。

AWSコンソールを開いてデベロッパー用ツール > CodeCommit >リポジトリ から
リポジトリを作成します。

今回はリポジトリ名を「sample-laravel」として他デフォルトで作成します。

![image (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/3889dcb5-f328-6e5b-8c86-188b2513f834.png)

リポジトリが作成できたら以下のような画面が表示されますので
Git認証を作成していない場合は案内に従って認証を作成してください。

![image (45).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/23c24731-dda9-00cb-1116-8a7418e73123.png)

EC2に戻ってsample-laravel配下をCodeCommitに紐づけます。
※コンテナからは出てください。

```
cd /home/ec2-user/docker/sample-laravel
```

既存ソースをCodeCommitに紐づける手順はAWSのドキュメント参考にしました。
https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/how-to-migrate-repository-local.html#how-to-migrate-local-version

```
git init
```

```
git config --local init.defaultBranch master
```

```
git add .
```

```
git commit -m "Initial commit"
```

```
git push https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/sample-laravel --all
```

pushまで済むと先程作成したリポジトリが更新されます。
![image (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/563ef018-48f4-fab8-170a-6d67677d6596.png)

###3.コンテナイメージをECRにアップロード
AWSコンソールを開きAmazon ECR > リポジトリからリポジトリを作成を押下。
リポジトリ名を「sample-laravel-image」とし他はデフォルトとしました。

![image (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/dff2540c-9e8b-06e9-c9af-c93523d9eb1c.png)

リポジトリが作成できたらEC2のDockerfileから作成したイメージをアップします。
まず、実行環境のEC2にI AMロールでECRの権限を付与します。
EC2のコンソールでアクション > セキュリティ > IAMロールを変更から「新しい IAM ロールを作成」で
作成したロールに「AmazonEC2ContainerRegistryFullAccess」を割り当てました。

![image (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/d1311c0f-03ca-36da-9f6b-00a1416ec6bc.png)

EC2に戻ってDockerflieのある階層に移動します。

```
cd /home/ec2-user/docker/web
```

ここからECRへのアップロードまではコンソールの「プッシュコマンドの表示」の手順そのままなので、そちらを見たほうがよいかもしれないです。

![image (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/e1b18adb-3c16-9b6c-dbc3-def085104567.png)

ECRへのログイン、ここで弾かれる場合はロールの割当がうまくいっていない可能性が高いです。

```
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com
```

```
docker build -t sample-laravel-image .
```

```
docker tag sample-laravel-image:latest アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/sample-laravel-image:latest
```

```
docker push アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/sample-laravel-image:latest
```

ECRへのpushまで終わるとECR側でもアップロードしたイメージが反映されます。
後ほど使用するので「URIのコピー」からURIをメモしておいてください。

![image (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/73668192-49c3-9838-90cd-703854524212.png)

CodeCommitとECRの準備はできたので、次はCodePipelineと連携させていきます。

###4.CodePipeline構築

まず、EC2に戻ってCodePipelineで使うbuildspec.ymlとDockerfileを作成します。

```
mkdir /home/ec2-user/docker/sample-laravel/pipeline
```

```
vi /home/ec2-user/docker/sample-laravel/pipeline/buildspec.yml
```

```
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region ap-northeast-1)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t web -f pipeline/Dockerfile .

  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker run web php vendor/bin/phpstan analyse -l 0 app
```

```
vi /home/ec2-user/docker/sample-laravel/pipeline/Dockerfile
```

```
FROM 先程メモしたイメージのURI

COPY ./src /var/www/src
WORKDIR /var/www/src
RUN cp -p .env.example .env
RUN composer install
```

追加したファイルはリポジトリにpushしておいてください。

AWSコンソールを開きデベロッパー用ツール > CodePipeline > パイプラインから「パイプラインを作成する」でパイプラインを新規作成します。

パイプライン名は「sample-laravel-pipeline」としました。

![image (13).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/15dd4144-6246-ad58-5f0d-4ab85207617f.png)

ソースプロバイダーをCodeCommitとしsample-laravelのmasterブランチを選択します。

![image (14).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/263a6327-4577-d4c2-98d5-d0c57c73f19f.png)

ビルドステージのプロバイダーをCodeBuildに設定し「プロジェクトを作成する」を押下します。

![image (15).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/41766f7d-488c-520a-93d9-138a39c151c6.png)

buildプロジェクト名を入力します。
プロジェクト名は「sample-laravel-build」としました。
![image (16).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/1162925b-c8ef-a688-2673-bf7ee3c2ac06.png)

![image (17).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/12c831ef-6706-21e7-529b-f7d616339d5d.png)

特権付与にチェック入れます

![image (18).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/c9f9adfb-ec14-e453-6dd7-4ae18fe77437.png)

buildspec.ymlのパス「pipeline/buildspec.yml」を入力しバッチ設定、ログはデフォルトのままにします。
![image (19).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/0200ffd6-ed2e-1302-8d0d-aeb76e8f2d0f.png)


![image (20).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/f48294c2-41dc-4604-d3e8-ad520d3c48aa.png)

今回はデプロイは行いませんので「導入段階をスキップ」を選択します。
![image (21).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/e7e0830b-da01-fa2e-b420-ba56dd60a2e5.png)

パイプラインを作成すると自動的に実行されますが、パイプラインに設定したロールにECRの権限設定をしていないため一度失敗します。
![image (22).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/cece052c-9c50-b472-3254-847454604880.png)

CodeBuild > ビルドプロジェクト > sample-laravel-buildからビルドの詳細タブで設定されたサービスロールをクリックします。
![image (23).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/bc4bb78a-cffe-430f-610b-5af5a36ea23a.png)

![image (24).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/a4ebef4e-27ce-3989-9cd6-5faf1e1f70a6.png)

ロールの許可タブから「ポリシーをアタッチ」を選択し「AmazonEC2ContainerRegistryReadOnly」をアタッチします。
![image (25).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/460371a9-d95a-8a5a-38df-2b9ff26063f3.png)

CodePiplineに戻り「変更をリリースする」から再実行すると、ロールに権限割り当てたので成功します。
![image (26).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/3f4f7901-592f-0b85-897a-4de4ce23a361.png)

Buildステージの詳細からログを確認すると、Larastanもちゃんと実行されています。
![image (27).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/05fa1d1e-8b92-21ec-87aa-121b351d32a6.png)

###5.CodePipelineのブランチをプルリクエスト元のブランチに変更して、パイプラインを起動するLambda関数を作成する

Lambda > 関数 > 関数の作成で作成します。
関数名を「sample-laravel-pipline-trigger」としてランタイムは「Python3.9」を設定します。他はデフォルトにしました。

![image (30).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/20e1abb4-89f4-38d6-c0eb-9698eb407c85.png)


コードタブに以下のスクリプトを貼り付けてDeployします。

```python:Lambda&#x20;Function
import json
import boto3

# パイプライン名
PIPELINE_NAME = 'sample-laravel-pipeline'
codepipeline = boto3.client('codepipeline')

def lambda_handler(event, context):
    # 現在のcodepipelineの設定値を取得
    response = codepipeline.get_pipeline(
        name=PIPELINE_NAME
    )
    
    #EventBridgeから引き渡されるプルリク元のブランチ名を取得
    targetBranch = event['detail']['sourceReference'].replace('refs/heads/', '')
    #pipelineに設定するためプルリク元のブランチで更新パラメータを作成
    response['pipeline']['stages'][0]['actions'][0]['configuration']['BranchName'] = targetBranch
    #更新に不要な値削除
    del response['metadata'],response['ResponseMetadata']
    
    #pipelineの設定を更新
    updateResponse = codepipeline.update_pipeline(pipeline=response['pipeline'])

    #codepipelineを開始
    startResponse = codepipeline.start_pipeline_execution(
        name=PIPELINE_NAME
    )

    return
```

スクリプトにbotoによるCodePipelineの更新が入っているので、関数のロールにCodePipelineの編集権限を付与します。

設定タブからアクセス権限を選択してロールをクリックします。

![image (31).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/c28244ee-0140-3066-5936-26e6a44731d3.png)

ロールの編集画面でアクセス許可を追加から「AWSCodePipelineFullAccess」のポリシーをアタッチします。
![image (32).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/d2e0a23f-96b5-e837-965d-322cf1b471b5.png)

###6.プルリクエスト時に起動するようにEventBridgeのトリガーを変更する

まず、CodePipelineを作った最に自動で作成されたルールを削除してリポジトリへのpushではパイプラインを起動しないようにします。

※こちらはやらなくてもpushで余計にパイプラインが走るくらいで後の工程に支障はないので飛ばしても問題ないです。

Amazon EventBridge > ルール > codepipeline-sample-master-xxxxx-rule

ルール作成時の命名規則がいまいちよくわからないですがイベントパターンの
resourcesにsample-laravelが指定されているルールを削除します。

![image (28).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/72abb2b8-5ffd-d63d-4df4-2f5a2a0c8500.png)

そのままルールの新規作成で、sample-laravelへのプルリクエストをトリガーに
Lambdaを起動させるルールを追加します。
ルール名は「sample-laravel-lambda-trigger」としました。

![image (33).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/dc1468f3-1bb5-c3f8-9f10-c27926b5652d.png)

イベントをカスタムパターンにしてsample-laravelへのプルリクエスト作成のイベントで発火するよう定義する。
![image (34).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/2509e0ad-a889-b818-cd74-2dd262f5981a.png)

アカウントIDは利用しているAWSアカウントのIDに書き換えてください。

```
{
  "source": ["aws.codecommit"],
  "detail-type": ["CodeCommit Pull Request State Change"],
  "resources": ["arn:aws:codecommit:ap-northeast-1:アカウントID:sample-laravel"],
  "detail": {
    "event": ["pullRequestCreated"]
  }
}
```

ターゲットに先程作成したLambda関数の「sample-laravel-pipline-trigger」を選択してルールを作成
![image (35).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/ae8a62f4-9525-bb91-c7e8-e9804dcf0f32.png)

###7.プルリクエストをしてみる
環境が整ったのでいよいよプルリクエストを作成してみます。

CodeCommitのブランチからブランチの作成。

![image (36).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/771934ef-f3fb-60bd-8d17-134c6077c5a2.png)

ブランチ名はなんでもいいですが、「develop」にしました。
![image (37).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/bdb47988-8459-a246-1098-a6bbb2795691.png)

試しにbuildspec.ymlを編集してLarastanをレベル7に編集します。
Larastanインストール時にレベル７だと怒られていたのでビルドエラーを返すはず
![image (38).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/662be623-c750-498a-b93b-b9b6bc72063f.png)

![image (40).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/41e4c185-d575-124f-1663-8b76aa173d17.png)


プルリクエストの作成から[develop]->[master]にプルリクエストを作成します。
差分は先程編集したbuildspec.ymlです。
![image (39).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/857ee336-6574-32f6-04aa-79509837c5af.png)

無事起動しました。
![image (41).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/e25f0cf0-931b-0cb9-6231-135ce6dc4f27.png)

Sourceの「AWS CodeCommit」をクリックすると遷移する先がパイプライン構築時には存在していなかった「develop」になっています。
![image (42).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/5d74077a-09e8-b150-e3e1-0c674a846f84.png)

buildステージの詳細から遷移してログを確認するとやっぱりLarastanに怒られてBuild失敗のステータスとなっていました。
![image (43).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/71e30dc0-0162-dc57-3215-da0f64b15c19.png)

怒られて終わりはモヤモヤするので、Larastanの実行レベルを0に戻してプルリクエストを作り直しました。
無事成功ステータスで終了してますね。

![image (44).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524156/270d2017-2106-6712-7f83-a74b6e4e1f55.png)

今回は触れませんが実行結果をプルリクのコメントに書き込むなど、通知も自動化しておくとレビュアーは楽できそうですね。

### おわりに

今回は簡単に導入できるLarastanで試しましたが、PHPUnitなんかも実行できますし
そもそも実行環境に自作コンテナイメージ使ってるので大抵のことはできるかと思います。

EventBridgeやCodeシリーズを使いこなせると自動化はかどりそうだなーと思いました。
精進せねば！