GitHub Actionsのワークフローをブランチ名を元に環境定義を切り替える方法

# 目的

`ブランチ名＝環境定義名` とした場合にその環境名のブランチへのPRがマージされたときに
デリバリー（デプロイ）フローにおける環境差異をEnvironmentsに定義したVariableやSecretを参照できるようにするフローを検討する。

※環境定義名とは[デプロイに環境を使用する](https://docs.github.com/ja/actions/deployment/targeting-different-environments/using-environments-for-deployment)に記載された環境定義により参照する変数・シークレットなどを切り替えられる機能です。（それだけの機能ではないですが、詳細はリンク先を参照）
なお、この機能は無料アカウントでは、publicなリポジトリしか利用できない機能です。
GitHub Teams/Proなどの有料アカウントではprivateリポジトリでも利用可能。

# TL;DR

PRがマージされたときにベースブランチ
また環境名のブランチを選択して手動でワークフローを実行(workflow_dispatch)する場合にも対応する。

※前提条件として、環境名のブランチにはProtecionにより直接のプッシュができないように設定してあるものとします。

```yaml:.github/workflows/build-deploy.yml
name: Build and Deploy

on:
  # PRのマージCLOSEで実行（マージの条件はここにはかけないのでジョブの起動条件ifで絞る）
  pull_request:
    branches:
      - development
      - staging
      - production
    types:
      - closed
  # 手動実行時（任意のブランチを選択された環境名にデプロイできるようにする）
  workflow_dispatch:
    inputs:
      # デプロイする環境
      deploy_target:
        description: Environment to deploy
        required: true
        type: choice
        options:
          - development
          - staging
          - production
jobs:
  build-deploy:
    # 環境別にシークレット(secrets.*)や変数(vars.xxxx)を切り替える定義
    environment: ${{ (github.event_name == 'pull_request' && github.ref_name) || inputs.deploy_target }}
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged || github.event_name == 'workflow_dispatch'
    steps:
      # チェックアウト
      - name: Checkout resources
        uses: actions/checkout@v3
      # ビルドステップなどをここに記載
      - name: Build
        run: echo "dummy build process"
      # デプロイをステップなどを記載
      - name: Deploy
        run: |
          echo "Deploy to ${{ env.TARGET_RESOURCE_NAME }}"
          # ログ上はマスキングされてしまうが、実際の処理では参照可能
          echo "Access key=${{ env.TARGET_RESOURCE_API_KEY }}"
        # 例としてここに環境定義にしたシークレットや変数（Variable）を環境変数として設定
        env:
          TARGET_RESOURCE_NAME: ${{ vars.TARGET_RESOURCE_NAME }}
          # 対象環境のリソースにデプロイするためのAPIKeyとかシークレットなどを
          TARGET_RESOURCE_API_KEY: ${{ secrets.RESOURCE_API_KEY }}
      # 処理完了通知をSlackに
      - name: slack-send
        uses: slackapi/slack-github-action@v1.23.0
        with:
          payload: |
            {
              "text": "GitHub Action build-deploy result: ${{ job.status }}\ntarget:${{ vars.TARGET_RESOURCE_NAME }}\nkey:${{ secrets.RESOURCE_API_KEY }}"
            }
        env: 
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```

ポイントとしてはこれができるのは `github.ref_name` で環境名に対応するブランチ名が設定されているイベントの場合のみということになります。
イベント毎にブランチ名を分ける必要がある場合は、`(条件 && 合致した場合) || 合致しない場合` みたいな書き方をネストさせればなんとかなるとは思いますが、可読性が悪そうなので別途コメント書いておいたほうがよいでしょう。
ただし、ここで利用できるのはあくまでもGitHubActionsが起動したときに確定している変数(Context)の値のみであるはずです。チェックアウトしたり、Stepで処理した変数や環境変数を設定することはできないと思われます。（未検証）

# 検証作業記録

## イベント別のブランチ名の参照方法

イベント毎に違うことは理解していてもいつも忘れるのでメモしておきます。

ここでは、`feature/hogehoge`ブランチを作成後に以下の操作が行われたこととします。

1. ローカルで開発・テストが完了したfeatureブランチをプッシュする
1. featureブランチからdevelopmentブランチへPRを作成する
1. 作成したPRのレビューの指摘を受けて修正コミット＋プッシュをする
1. ワークフローを手動実行
1. 作成したPRがマージする
    1. PRのマージによるpull_requestのイベント
    1. PRのマージによるベースブランチがへのプッシュイベント

|No|イベント|github.event_name|github.event.action|github.event.pull_request.merged|github.ref_name|github.head_ref|github.base_ref|
| --- | --- | --- | --- | --- | --- | --- | --- |
|1|featureブランチへプッシュ|push|||feature/hogehoge|||
|2|PR作成時|pull_request|opened|false|2/merge|feature/hogehoge|development|
|3|作成済PRに追加コミット(push)時<br>(レビューの指摘結果反映など)|pull_request|opened|false|2/merge|feature/hogehoge|development|
|4|手動実行(feature/hogehogeブランチを選択して実行)|workflow_dispatch|||feature/hogehoge|||
|5-1|PRのマージ(headブランチ側のpull_request)|pull_request|closed|true|development|feature/hogehoge|development|
|5-2|PRのマージ(baseブランチ側のpush)|push|||development|||

pushイベントやworkflow_dispacthイベントは `github.ref_name` で対象のブランチが取得できますが、紛らわしいのがpull_requestイベント。
open/syncronizedの場合は `github.ref_name` にはGitHub内部で自動生成されるマージ作業用？一時的なブランチが設定されますが、マージ後の場合はpushイベントと同じで `github.ref_name` がマージされたブランチ＝ベースブランチなりました。

### ブランチ名検証ワークフロー

```yaml:.github/workflows/confirm-branch-name.yml
name: Confirm branch name
on:
  push:
  pull_request:
    types:
      - opened
      - synchronize
      - closed
  workflow_dispatch:
jobs:
  show-variable:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout resource
        uses: actions/checkout@v3
      - name: Show github context
        run: |
          echo "github.event_name=${{ github.event_name }}"
          echo "github.event.pull_request.merged=${{ github.event.pull_request.merged}}"
          echo "github.ref_name=${{ github.ref_name }}"
          echo "github.head_ref=${{ github.head_ref }}"
          echo "github.base_ref=${{ github.base_ref }}"
```

### ブランチ名検証ワークフロー実行結果

#### push(featureブランチをプッシュしたとき)

```
github.event_name=push
github.event.action=
github.event.pull_request.merged=
github.ref_name=feature/hogehoge
github.head_ref=
github.base_ref=
```

#### pull_request-作成時(action=opened)

```text:log抜粋
github.event_name=pull_request
github.event.action=opened
github.event.pull_request.merged=false
github.ref_name=2/merge
github.head_ref=feature/hogehoge
github.base_ref=development
```

#### pull_request-作成後の追加コミットなど(action=synchronize)

```text:log抜粋
ithub.event_name=pull_request
github.event.action=synchronize
github.event.pull_request.merged=false
github.ref_name=2/merge
github.head_ref=feature/hogehoge
github.base_ref=development
```

#### 手動実行(workflow_dispatch)

```text:log抜粋
github.event_name=workflow_dispatch
github.event.action=
github.event.pull_request.merged=
github.ref_name=feature/hogehoge
github.head_ref=
github.base_ref=
```

#### pull_request-マージ(headブランチ側のpull_requestイベント)

```text:log抜粋
github.event_name=pull_request
github.event.action=closed
github.event.pull_request.merged=true
github.ref_name=development
github.head_ref=feature/hogehoge
github.base_ref=development
```

※ポイントとしてpull_requestのマージで発生するトリガーの場合 `github.ref_name` はbaseブランチと同じになります。

#### pull_request-マージ(baseブランチ側のpushイベント)

```text:log抜粋
github.event_name=push
github.event.action=
github.event.pull_request.merged=
github.ref_name=development
github.head_ref=
github.base_ref=
```

## 環境切り替えワークフロー実行結果

それぞれの環境の変数とシークレットには以下を設定した状態とします

|変数/シークレット名|development|staging|production|
|---|---|---|---|
|TARGET_RESOURCE_NAME|xxx-deplopment|yyy-staging|zzz-production|
|RESOURCE_API_KEY|development-key|staging-key|production-key|

別途：`SLACK_WEBHOOK_URL`はリポジトリ共通としてリポジトリ全体のシークレットに設定

![actions-variables.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205949/bd842082-cf40-2fd9-10c3-d0c2335c08c0.png)
![actions-secrets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205949/0212ce34-480c-1eec-29db-b0721b2450dc.png)

※以下、ログ上のシークレット文字列は `***` でマスキングされてしまうのでログでは確認できませんが、最後のSlack通知のPayloadに含めているkey部分はちゃんと環境別の値が送信されました。

### featureブランチ->developmentブランチへマージ

```text:log抜粋
Run echo "Deploy to xxx-deplopment"
  echo "Deploy to xxx-deplopment"
  # ログ上はマスキングされてしまうが、実際の処理では参照可能
  echo "Access key=***"
  shell: /usr/bin/bash -e {0}
  env:
    TARGET_RESOURCE_NAME: xxx-deplopment
    TARGET_RESOURCE_API_KEY: ***
Deploy to xxx-deplopment
Access key=***
```

### developmentブランチ->stagingブランチへマージ

```text:log抜粋
Run echo "Deploy to yyy-staging"
  echo "Deploy to yyy-staging"
  # ログ上はマスキングされてしまうが、実際の処理では参照可能
  echo "Access key=***"
  shell: /usr/bin/bash -e {0}
  env:
    TARGET_RESOURCE_NAME: yyy-staging
    TARGET_RESOURCE_API_KEY: ***
Deploy to yyy-staging
Access key=***
```

### stagingブランチ->productionブランチへのマージ

```text:log抜粋
Run echo "Deploy to zzz-production"
  echo "Deploy to zzz-production"
  # ログ上はマスキングされてしまうが、実際の処理では参照可能
  echo "Access key=***"
  shell: /usr/bin/bash -e {0}
  env:
    TARGET_RESOURCE_NAME: zzz-production
    TARGET_RESOURCE_API_KEY: ***
Deploy to zzz-production
Access key=***
```

### 手動実行時にdevelopmentブランチを選択して、ターゲットにstagingを選択して実行

```text:log抜粋
Run echo "Deploy to yyy-staging"
  echo "Deploy to yyy-staging"
  # ログ上はマスキングされてしまうが、実際の処理では参照可能
  echo "Access key=***"
  shell: /usr/bin/bash -e {0}
  env:
    TARGET_RESOURCE_NAME: yyy-staging
    TARGET_RESOURCE_API_KEY: ***
Deploy to yyy-staging
Access key=***
```

ちゃんとenvironmentsの三項演算子的な記述の部分がハンドリングされて、inputs.deploy_targetの選択した環境が選択されていることが確認できた。