Amplify Hostingのプレビュー環境をGitHub Actionsでデプロイする

こんにちは、ROUTE06でソフトウェアエンジニアをしている @MH4GF です。
Amplify Hosting を利用してホスティングしている Web アプリケーションで、プレビュー環境を GitHub Actions でデプロイする方法を紹介します。

背景

Amplify Hosting では、実はプルリクエストベースのプレビューを機能として提供しています。

docs.aws.amazon.com

プルリクエストの作成と同時に環境にアクセスする URL が払い出され、Gitのコミットのプッシュと同時に再ビルドし、プルリクエストのマージと共に環境を削除します。
ビルドの進捗状況もプルリクエストの Checks として確認できるため、概ね期待する機能は揃っています。

ただ、運用する上でどうしても気になる点がありました。

モノレポで構築されているリポジトリで、ビルドの発火するディレクトリパスを指定できない

今回プレビュー環境を構築したいリポジトリはモノレポで構築しており、Amplify へのデプロイを行いたいソースコードが含まれるフロントエンドのディレクトリ以外にもバックエンドやインフラのコードが含まれています。ビルドに関係ない変更のプルリクエストに対してもビルドが行われ、またプレビュー環境の URL がプルリクエストのコメントとして追加されるためかなりのノイズとなっていました。

解決策を探してみると、Amplify Hosting では差分ベースのデプロイという機能が提供されており、コード差分があるときのみビルドを実行することができます。環境変数に AMPLIFY_DIFF_DEPLOY=true を設定するとフロントエンドの差分ビルドが有効化され、 AMPLIFY_DIFF_BACKEND=true を設定するとバックエンドの差分ビルドが有効化されます。

docs.aws.amazon.com

これらの設定により指定したディレクトリパスの変更がなければスキップすることはできます。ただビルド自体は実行されるため、プルリクエストのコメントにはプレビュー環境の URL のコメントは追加され、 Checks にはビルドの進捗状況が表示されます(ビルドするものがないためすぐ完了します)。この機能ではノイズになることは変わりませんでした。

別の案として、プルリクエストのコメントと Checks の進捗状況を追加する機能をオプトアウトすることができないか調べました。結論としてこれらを抑制することはできず、 Amplify Hosting の issue には「Amplify Hosting からのコメントが追加された場合、GitHub Actions でコメントを hide する」方法がワークアラウンドとして紹介されていました。

github.com

このように、Amplify Hosting が提供するプレビュー機能は、モノレポで構築されているリポジトリで利用するには少し不便な点があり、別の方法を模索していました。調べてみると AWS CLI で Amplify Hosting の環境を操作することができることがわかり、これを利用して GitHub Actions でプレビュー環境を構築することができそうだと考えました。

GitHub Actions でプレビュー環境の構築を実現する

ようやく本題です。まず先に、GitHub Actions の最終的なワークフローファイルを掲載します。

name: frontend-deploy-amplify-preview

on:
  pull_request:
    types: [opened, reopened, synchronize, closed]
    paths:
      - frontend/**/* # モノレポのうち、フロントエンドのディレクトリに変更があったらビルドを実行する
      - .github/workflows/frontend-deploy-amplify-preview.yml # このワークフローファイルの変更でも実行

env:
  AWS_REGION: ap-northeast-1
  AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
  BRANCH_NAME: ${{ github.head_ref }}

jobs:
  deploy-preview:
    runs-on: ubuntu-latest
    permissions:
      id-token: write # AWS IAMロールへのアクセスに必要
      contents: read # リポジトリのチェックアウトに必要
      pull-requests: write # プルリクエストへのコメントに必要
    timeout-minutes: 10
    strategy:
      matrix:
        # matrixでアプリの情報を定義する
        apps:
          - name: App
            amplify-app-id: ${{ secrets.AMPLIFY_APP_ID_APP }}
          - name: Storybook
            amplify-app-id: ${{ secrets.AMPLIFY_APP_ID_STORYBOOK }}

    steps:
      - name: Checkout 🛎
        uses: actions/checkout@v3

      # GitHub の OIDC プロバイダーを利用して AWS IAM ロールを AssumeRole する。(今回は本題ではないため省略)
      - name: Configure AWS credentials 🔓
        uses: aws-actions/configure-aws-credentials@master
        with:
          aws-region: ${{ env.AWS_REGION }}
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
          role-duration-seconds: 1800

      # 今回の主題。AWS CLIを利用してAmplify Hostingの環境を操作する
      - name: Deploy to Amplify Console 🚀
        run: |
          if [ "$EVENT_ACTION" == "opened" ] || [ "$EVENT_ACTION" == "reopened" ]; then
            echo "ACTION=CREATE" >> "$GITHUB_ENV"
            aws amplify create-branch --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --region=${AWS_REGION} --no-enable-auto-build
            sleep 10
            aws amplify start-job --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --job-type RELEASE --region=${AWS_REGION}
          elif [ "$EVENT_ACTION" == "synchronize" ]; then
            echo "ACTION=UPDATE" >> "$GITHUB_ENV"
            aws amplify start-job --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --job-type RELEASE --region=${AWS_REGION}
          elif [ "$EVENT_ACTION" == "closed" ]; then
            echo "ACTION=REMOVE" >> "$GITHUB_ENV"
            aws amplify delete-branch --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --region=${AWS_REGION}
          fi
        env:
          EVENT_ACTION: ${{ github.event.action }}

      # プレビュー環境とビルドの進捗状況のURLを組み立てる
      - name: Get Preview URL
        if: env.ACTION == 'CREATE' || env.ACTION == 'UPDATE'
        run: |
          DISPLAY_NAME=$(aws amplify get-branch --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME | jq -r '.branch.displayName')
          PREVIEW_URL="https://${DISPLAY_NAME}.${{ matrix.apps.amplify-app-id }}.amplifyapp.com"
          echo "PREVIEW_URL=$PREVIEW_URL" >> "$GITHUB_ENV"
          DEPLOYMENT_URL="https://${AWS_REGION}.console.aws.amazon.com/amplify/home?region=${AWS_REGION}#/${{ matrix.apps.amplify-app-id }}/${DISPLAY_NAME}"
          echo "DEPLOYMENT_URL=$DEPLOYMENT_URL" >> "$GITHUB_ENV"
        # AWS CLIの実行に失敗した場合にexit 1にしたく、GitHub Actionsの場合bashを設定すると `set -eo pipefail` がつけられる
        shell: bash

      # 前ステップで組み立てたURLをプルリクエストのコメントとして追加する
      - name: Leave a comment after deployment
        if: env.ACTION == 'CREATE' || env.ACTION == 'UPDATE'
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          header: "${{ matrix.apps.name }}"
          message: "\
            ${{ matrix.apps.name }} のプレビューURLはこちらです :eyes:\n
            ${{ env.PREVIEW_URL }}\n
            \n
            開けない場合、まだデプロイが完了していない場合があります。数分待ってからアクセスしてください。\n
            デプロイ状況: ${{ env.DEPLOYMENT_URL }}\n
            "

いくつかポイントがあるため、それぞれ説明します。

  • matrix でアプリケーションの情報を定義することで、複数のアプリケーションを同時にデプロイできるようにする
  • AWS CLI を利用して Amplify Hosting の環境を操作する
  • プレビュー環境とビルドの進捗状況の URL を組み立てる
  • 前ステップで組み立てた URL をプルリクエストのコメントとして追加する

matrix でアプリケーションの情報を定義することで、複数のアプリケーションを同時にデプロイできるようにする

このワークフローファイルでは matrix を以下のように定義しています。

strategy:
  matrix:
    apps:
      - name: App
        amplify-app-id: ${{ secrets.AMPLIFY_APP_ID_APP }}
      - name: Storybook
        amplify-app-id: ${{ secrets.AMPLIFY_APP_ID_STORYBOOK }}

GitHub Actions の matrix を使うと、設定値を変えて複数のジョブを並列実行できます。上記の例では nameamplify-app-id の 2 つのプロパティを持つオブジェクトの配列として定義していて、ワークフローを実行すると 2 つのアプリケーションのプレビュー環境が構築されます。

AWS CLI を利用して Amplify Hosting の環境を操作する

AWS CLI を利用するステップは以下のようになっています。

- name: Deploy to Amplify Console 🚀
  run: |
    if [ "$EVENT_ACTION" == "opened" ] || [ "$EVENT_ACTION" == "reopened" ]; then
      echo "ACTION=CREATE" >> "$GITHUB_ENV"
      aws amplify create-branch --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --region=${AWS_REGION} --no-enable-auto-build
      sleep 10
      aws amplify start-job --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --job-type RELEASE --region=${AWS_REGION}
    elif [ "$EVENT_ACTION" == "synchronize" ]; then
      echo "ACTION=UPDATE" >> "$GITHUB_ENV"
      aws amplify start-job --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --job-type RELEASE --region=${AWS_REGION}
    elif [ "$EVENT_ACTION" == "closed" ]; then
      echo "ACTION=REMOVE" >> "$GITHUB_ENV"
      aws amplify delete-branch --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME --region=${AWS_REGION}
    fi
  env:
    EVENT_ACTION: ${{ github.event.action }}

github.event.action にはワークフローが発火したイベントのトリガー情報が含まれており、今回のワークフローのトリガーは pull_request イベントかつ opened, reopened, synchronize, closed のいずれかに絞っています。この値を見てプレビュー環境の作成、更新、削除を分岐します。

Amplify Hosting の環境を作成する際は create-branch, 削除する際は delete-branch コマンドを利用します。Amplify Hosting ではバージョン管理システムである Git のブランチと連携するように設計されており、各ブランチを環境として扱います。
create-branch に渡しているオプション --no-enable-auto-build が何をしているかというと、ブランチに push があった際に自動でビルドを実行する機能をオフにしています。一見不要そうですが、これを指定しないと元々の課題であるビルドを発火するディレクトリを指定できない問題が発生します。Amplify 側の自動ビルドはオフにし、この GitHub Actions でビルドの実行を制御させるようにします。

ビルドの実行は start-job コマンドを利用します。このコマンドはブランチの作成後と、プルリクエストに push があり環境を更新したい時に利用しています。 --job-typeRELEASE にすることで、最新のコミットを利用してビルドを実行します。

プレビュー環境とビルドの進捗状況の URL を組み立てる

- name: Get Preview URL
  if: env.ACTION == 'CREATE' || env.ACTION == 'UPDATE'
  run: |
    DISPLAY_NAME=$(aws amplify get-branch --app-id=${{ matrix.apps.amplify-app-id }} --branch-name=$BRANCH_NAME | jq -r '.branch.displayName')
    PREVIEW_URL="https://${DISPLAY_NAME}.${{ matrix.apps.amplify-app-id }}.amplifyapp.com"
    echo "PREVIEW_URL=$PREVIEW_URL" >> "$GITHUB_ENV"
    DEPLOYMENT_URL="https://${AWS_REGION}.console.aws.amazon.com/amplify/home?region=${AWS_REGION}#/${{ matrix.apps.amplify-app-id }}/${DISPLAY_NAME}"
    echo "DEPLOYMENT_URL=$DEPLOYMENT_URL" >> "$GITHUB_ENV"
  shell: bash

このステップでは、デプロイしたプレビュー環境にアクセスしやすくするためにプレビュー環境の URL と AWS コンソールのデプロイ状況の URL を GITHUB_ENV に追加しています。Amplify の API としてはそれらの URL が取得可能なフィールドはないため、情報を元に組み立てています。
Amplify では各環境(ブランチ)の識別は git のブランチ名を利用しますが、 /_ などは URL セーフではないためエスケープしているようです。実際にサブドメインなどで利用される値は get-branch コマンドで返ってくる情報の中の displayName が利用できます。

前ステップで組み立てた URL をプルリクエストのコメントとして追加する

- name: Leave a comment after deployment
  if: env.ACTION == 'CREATE' || env.ACTION == 'UPDATE'
  uses: marocchino/sticky-pull-request-comment@v2
  with:
    header: "${{ matrix.apps.name }}"
    message: "\
      ${{ matrix.apps.name }} のプレビューURLはこちらです :eyes:\n
      ${{ env.PREVIEW_URL }}\n
      \n
      開けない場合、まだデプロイが完了していない場合があります。数分待ってからアクセスしてください。\n
      デプロイ状況: ${{ env.DEPLOYMENT_URL }}\n
      "

https://github.com/marocchino/sticky-pull-request-comment を利用し、コメントを追加します。このカスタムアクションは、コメントがすでに存在する場合は更新し、存在しない場合は新規作成してくれるため便利です。matrix で複数ジョブを動作させている場合は競合してしまうため、header でコメントの識別子を指定する必要があります。

現状できていないこと

現状できていないこととして、 start-job コマンドで Amplify 側のジョブを起動した後、GitHub 側のプルリクエスト上でそのジョブの成功・失敗の認識ができていません。理想としてはプルリクエストの Checks にジョブの進捗状況を表示したいですが、現状は実現できていません。
ひとまずデプロイ状況を確認できるように、プルリクエストのコメントにデプロイ状況の URL を追加しています。

まとめ

本記事では、GitHub Actions で Amplify Hosting のプレビュー環境を構築する方法を紹介しました。Amplify Hosting のようなマネージドサービスを利用するモチベーションとしてはこのような CD 環境の構築が簡単にできることにも期待していたのですが、モノレポでの構築には不便な点があり今回は自身で構築することにしました。できればマネージドサービス側から提供してくれる方が望ましいため、今後の改善に期待したいです。