GitHub Actionsを使用して環境別のCDを構築する

## 概要
仕事でGitHub Actionsを用いて環境別にCDを構築する方法について構築したので、残しておこうと思います。

また、仕事ではCircleCIからGitHub Actionsへの移行を行いましたので、そちらの参考にしたものについてもついても以下に纏めておきます。

### 移行について
使用したツール: `GitHub Actions Importer`

以下の公式リファレンスがわかりやすかったです。

https://docs.github.com/ja/enterprise-cloud@latest/actions/migrating-to-github-actions/automated-migrations/migrating-from-circleci-with-github-actions-importer

`gh actions-importer audit circle-ci --output-dir tmp/audit`のコマンドを使用すると`audit_summary.md`に変換率が吐き出され、変換後ファイルも出力されました。

[CircleCI パイプラインでサポートされている構文](https://docs.github.com/ja/enterprise-cloud@latest/actions/migrating-to-github-actions/automated-migrations/migrating-from-circleci-with-github-actions-importer#supported-syntax-for-circleci-pipelines)や[環境変数のマッピング](https://docs.github.com/ja/enterprise-cloud@latest/actions/migrating-to-github-actions/automated-migrations/migrating-from-circleci-with-github-actions-importer#environment-variable-mapping)を参考に変換しきれていない部分について書き換えを行なえば完了です!

## 環境別での構築について
さて、ここからが本題です。
TWELVE-FACTOR APPでは環境を分ける意味についてこのようにあります。
>アプリケーションは時に設定を定数としてコード内に格納する。これはTwelve-Factorに違反している。Twelve-Factorは 設定をコードから厳密に分離すること を要求する。設定はデプロイごとに大きく異なるが、コードはそうではない。
引用元: [TWELVE-FACTOR APP](https://12factor.net/ja/config)

この考え方を意識すると、同一のワークフロー(ソースコード)を使用して検証環境や本番環境にリリースするのが良いということになります。

上記を踏まえて、GitHub Actionsで環境を選択してデプロイができるようになるワークフローを構築してきます。

### Step:1 Jobの実行トリガーを設定する
GitHub Actionsでは`push`や`PR作成`などをトリガーとしてjobを起動させることができます。
今回は手動でjobを実行させるため、`workflow_dispatch`を使用して設定を行います。このトリガーは`inputs`を設定することで、外部から値を入力することができるようになります。
```yml
on:
  workflow_dispatch:
    inputs:
      selected_environment: #他にはenvironmentを使用することもできる。
        description: 'デプロイ環境を選択してください。'
        required: true
        default: '---'
        type: choice
        options:
          - development
          - stage
          - production
```

### Step2: Jobを2つに分ける
Jobを２つに分けるとはなんぞや?と思われるでしょう。
ここでは、デプロイの準備をするJobとデプロイを実行するJobを作成していきます。
まずはじめに、準備用のJobを作成します。

```yml
env:
  SELECTED_ENVIRONMENT: ${{ github.event.inputs.selected_environment }}

jobs:
  pre_deploy:
    runs-on: ubuntu-latest
    outputs: #環境毎に設定したい値は基本的にここで設定する。
      WORKING_BRANCH: ${{ env.WORKING_BRANCH }} #deployジョブ内でcheckoutする時に使用する
      TOKEN_NAME: ${{ env.TOKEN_NAME }}
    steps:
      - name: Setting development environment
        if: env.SELECTED_ENVIRONMENT == 'development'
        run: |
          echo 'WORKING_BRANCH=develop' >> $GITHUB_ENV
          echo 'TOKEN_NAME=DEVELOP' >> $GITHUB_ENV
      - name: Setting staging environment
        if: env.SELECTED_ENVIRONMENT == 'stage'
        run: |
          echo 'WORKING_BRANCH=stage' >> $GITHUB_ENV
          echo 'TOKEN_NAME=STAGE' >> $GITHUB_ENV
      - name: Setting production environment
        if: env.SELECTED_ENVIRONMENT == 'production'
        run: |
          echo 'WORKING_BRANCH=master' >> $GITHUB_ENV
          echo 'TOKEN_NAME=PRODUCTION' >> $GITHUB_ENV
```

ここで、行なっていることは`workflow_dispatch`で選択した値に一致する場合、環境変数に値を代入し、それをoutputsに設定するということです。
環境変数に設定しているのは、ワークフロー内で重複定義ができるためです。
outputsに設定しているのは後続のデプロイを実行するJobから参照できるようにするためとなります。

次に、デプロイを実行するJobを作成します。
```yml
deploy:
    runs-on: ubuntu-latest
    needs: pre_deploy
    steps:
      - name: Confirm TOKEN_NAME
        run: echo ${{ env[format('DEPLOY_{0}', needs.pre_deploy.outputs.TOKEN_NAME)] }}
        env:
          DEPLOY_DEVELOP: 'development'
          DEPLOY_STAGE: 'stage'
          DEPLOY_PRODUCTION: 'production'
      - name: Checkout branch
        uses: actions/checkout@v3
        with:
          ref: ${{ needs.pre_deploy.outputs.WORKING_BRANCH }}
      #ブランチのcheckoutが出来たので、以下にデプロイ処理を記述していく。
```
ここで鍵となるのは以下の3点となります。
- `needs: pre_deploy`
- `${{ needs.pre_deploy.outputs.WORKING_BRANCH }}`
- `${{ env[format('DEPLOY_{0}', needs.pre_deploy.outputs.TOKEN_NAME)] }}`

１つずつ解説していきます。

<strong>needs: pre_deploy</strong>
これを設定することにより、Jobに依存関係が発生します。
pre_deployが実行されない場合、deployも実行されなくなります。

<strong>${{ needs.pre_deploy.outputs.WORKING_BRANCH }}</strong>
依存関係があるJobでは、`needs.ジョブ名.outputs.変数名`で依存先のoutputsに設定されている変数の値を参照することができるようになります。
これを用いて、pre_deployで設定した値を参照できるようにしています。

<strong>${{ env[format('DEPLOY_{0}', needs.pre_deploy.outputs.TOKEN_NAME)] }}</strong>

これは、値を設定する応用的な使い方となります。
基本的には上記の方法を用いて値を設定していけば問題ないのですが、Secretsに登録された値を環境毎に環境変数に設定する場合`echo`コマンドを使用することはできません。
そのため、悩んでいた所、チームリーダーの方が以下の記事を教えてくださいました。

https://dev.classmethod.jp/articles/github-actions-variable-indirection-reference/

GitHub Actionsでは`${{ env.hoge }}`と`${{ env[hoge] }}`は同じ意味となります。また、format関数を使用することもできます。
format関数は`format('hoge{0}', hoge)`のように記述すると第1引数内の`{0}`に第２引数を代入して返してくれます。
そのため、前述のものは`hogehoge`となります。

これらを踏まえて読み解くと、developの場合は`${{ env[DEPLOY_DEVELOP] }}`となり、`echo`コマンドを使用しないで環境毎に動的に値を変更できるようになるのです。

### Step:3 デプロイ処理を記述していく

ここまでのStep:1, Step:2を参考に値を設定できたらあとはデプロイ処理を記述していくだけとなります。
今回は説明に入れませんでしたが、Cacheを使用するなどして時間短縮を図るのも良いかもしれません。

## 全体像
```yml
name: Deployment

on:
  workflow_dispatch:
    inputs:
      selected_environment: #他にはenvironmentを使用することもできる。
        description: 'デプロイ環境を選択してください。'
        required: true
        default: '---'
        type: choice
        options:
          - development
          - stage
          - production

env:
  SELECTED_ENVIRONMENT: ${{ github.event.inputs.selected_environment }}

jobs:
  pre_deploy:
    runs-on: ubuntu-latest
    outputs: #環境毎に設定したい値は基本的にここで設定する。
      WORKING_BRANCH: ${{ env.WORKING_BRANCH }} #deployジョブ内でcheckoutする時に使用する
      TOKEN_NAME: ${{ env.TOKEN_NAME }}
    steps:
      - name: Setting development environment
        if: env.SELECTED_ENVIRONMENT == 'development'
        run: |
          echo 'WORKING_BRANCH=develop' >> $GITHUB_ENV
          echo 'TOKEN_NAME=DEVELOP' >> $GITHUB_ENV
      - name: Setting staging environment
        if: env.SELECTED_ENVIRONMENT == 'stage'
        run: |
          echo 'WORKING_BRANCH=stage' >> $GITHUB_ENV
          echo 'TOKEN_NAME=STAGE' >> $GITHUB_ENV
      - name: Setting production environment
        if: env.SELECTED_ENVIRONMENT == 'production'
        run: |
          echo 'WORKING_BRANCH=master' >> $GITHUB_ENV
          echo 'TOKEN_NAME=PRODUCTION' >> $GITHUB_ENV

  deploy:
    runs-on: ubuntu-latest
    needs: pre_deploy
    steps:
      - name: Confirm TOKEN_NAME
        #ここではenvですが、secretsで活用する想定
        run: echo ${{ env[format('DEPLOY_{0}', needs.pre_deploy.outputs.TOKEN_NAME)] }}
        env:
          DEPLOY_DEVELOP: 'development'
          DEPLOY_STAGE: 'stage'
          DEPLOY_PRODUCTION: 'production'
      - name: Checkout branch
        uses: actions/checkout@v3
        with:
          ref: ${{ needs.pre_deploy.outputs.WORKING_BRANCH }}
      #ブランチのcheckoutが出来たので、以下にデプロイ処理を記述していく。

```
## まとめ
今回は仕事でGitHub Actionsを活用してCD環境を整備した際に学んだことを備忘録的にまとめました。
Jobの依存関係などを活用すればCI/CDの構築なども簡単に行えるようになると思いますので、チャレンジして見たいと思います。