🕹️

[Github Actions] Release tagでバージョン管理に連動させた環境設定ごとのデプロイ

2023/05/30に公開

tldr

  • mainなどへのmerge/commit単位では自動デプロイせず、まとまった時に手動でデプロイ処理が走るようにしたい
  • 個別のリリース用ブランチは作りたくない
  • "まとまった時"はリリースタグを利用して、v1.x.xなどの形式で管理する
  • secretsなどはEnterpriseアカウントなので Github Environmentを使って管理したい
  • secretsは各環境ごとに異なる。なので各環境ごとにworkflow runの承認できるメンバーを変えたい
  • Actionsに出てくるworkflow runsのタイトルは同じだと分かりにくいので動的にReleaseバージョンなどに合わせたい
  • 公式以外の出どころが分からないAppsはactionsで使いたくない

手順

  1. レポジトリのSettings > Tags > Protected tagsでNew rule > v*で保存
  2. レポジトリのSettings > Environments > New environment
    a. Nameを使いたいブランチ名にする (main,staging)
    b. (以下Configure画面で)Deployment protection rules > Required reviewersにチェックしてから承認メンバーを追加
    c. Allow administrators to bypass configured protection rulesのチェックを外す
    d. Save protection rulesで保存
    e. Deployment branchesでAll branchesSelected branchesに切り替える
    f. Add deployment branch ruleでブランチ名をEnvironment Nameと同じブランチ名を入力する
    g. 先人に従いEnvironment secrets > Add secretで任意のキーGCLOUD_SERVICE_KEYなどを設定する。 ただの変数で良いものは Environment variables > Add variableでGOOGLE_APPLICATION_CREDENTIALSなどを設定
  3. レポジトリに以下2ファイルを作って各ブランチに反映
.github/workflows/deploy-trigger.yml
name: AutomatedReleaseTagCheck
run-name: Checking ${{ github.event.release.name }}

on:
  release: 
    types: [prereleased, released]

permissions: write-all

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Fetch all tags
      run: git fetch --tags

    - name: Get second latest tag
      id: get-second-latest-tag
      run: echo ::set-output name=tag::$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)

    - name: Compare tags
      run: |
        SECOND_LATEST_TAG=${{ steps.get-second-latest-tag.outputs.tag }}
        NEW_TAG=${{ github.event.release.tag_name }}
        if [[ "$(printf '%s\n' "$NEW_TAG" "$SECOND_LATEST_TAG" | sort -V | head -n1)" == "$NEW_TAG" ]]; then
          echo "New tag ($NEW_TAG) is not newer than the second latest tag ($SECOND_LATEST_TAG)"
          exit 1
        fi

    - name: Validate tag
      id: validate
      run: |
        TAG_NAME=${{ github.event.release.tag_name }}
        echo "Tag name is $TAG_NAME"
        if [[ $TAG_NAME != v* ]]; then
          echo "Tag does not start with 'v'"
          exit 1
        fi

    # https://github.blog/changelog/2022-09-08-github-actions-use-github_token-with-workflow_dispatch-and-repository_dispatch/
    - name: Trigger deploy workflow
      uses: actions/github-script@v6
      with:
        script: |
          const branch = "${{ github.event.release.target_commitish }}";
          const workflow_file = 'deploy.yml';

          await github.rest.actions.createWorkflowDispatch({
            owner: context.repo.owner,
            repo: context.repo.repo,
            workflow_id: workflow_file,
            ref: branch,
            inputs: {
              environment: branch,
              title: "${{ github.event.release.tag_name }}"
            },
          });

.github/workflows/deploy.yml
name: DeployAll
run-name: Deploy ${{ github.event.inputs.title }}

on:
  workflow_dispatch:
    inputs:
      title:
        description: 'workflow run title to be used'     
        required: true
        default: 'Deploy(default)'
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'


jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.environment }}
    env:
      GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}
      GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }}
    
    steps:
    - name: Checkout
      uses: actions/checkout@v2
      with:
        ref: ${{ github.event.inputs.branch }}

    - name: Make GOOGLE_APPLICATION_CREDENTIALS
      run: |
        echo "$GCLOUD_SERVICE_KEY" | base64 -d > $GOOGLE_APPLICATION_CREDENTIALS

    # 以下各自のデプロイプロセス
    # - name: Set up Node.js
    #   uses: actions/setup-node@v2
    #   with:
    #     node-version: 16
    # ...

  1. レポジトリトップの右の方にあるReleases > Draft a new releaseで
    a. Choose a tagの入力項目にタグバージョンv1.0.1などを入れて新規に作る
    b. Targetでデプロイ処理をしたい上記Environment連動ブランチを選ぶ
    c. Release titleに workflow runsに表示したい文字列を入力する
    d. Set as a pre-releaseSet as the latest releaseを選んでReleaseを公開
  2. Actions > All workflowsにまずChecking {Release titleに使った文字列}のrunができる
  3. 上記5がすぐ通るとタイマーアイコンの付いたDeploy {release tag version}が出てくるので、詳細を開いて"github-actions requested your review to deploy to main" > Review deployments から承認
  4. 環境設定連動したデプロイが回る🚀

調査に時間がかかった点

  • workflow runsに出てくるタイトルはyamlのname:on: push:などに反応するブランチlatestコミットメッセージだが、去年の9月からrun-nameが使えるようになった
  • API経由で半自動にworkflow runを作る workflow_dispatchGITHUB_TOKENでの使用も去年の9月から可能になった
  • workflowでEnvironment Protection Ruleの承認プロセスを使うにはon: push: branch:か"manually created" workflow runしか現在対応してない
  • workflowでonは複数条件設定できるが、ANDではなくORなのでon: releaseon:push:tagsが Environment Protection Ruleと共存できない
  • レポや権限設定にもよるがSettings > Actions でwrite権限がない場合は workflowにpermissions: write-allが必要

今回の内容はCurvegridさんでの業務で関わらせていただきましたので、掲載許可を取った上で記載しております。

Discussion