背景
背景としては、元々命名規則が曖昧だったり、クラウドサービスのリソース名などの設定文字数の制限などに抵触して
一定のルールで書くことができなかったので、ほとんど同じ処理内容ながら環境別にワークフロー定義を作成していました。
中身がほとんど同じなので、何かの修正をするときに複数のファイルを修正するのが面倒だなぁと思っていたのですが、比較的スッキリとした対応方法を思いついたのでメモしておきます。
やりたいこと・ゴール
- 環境別に分かれていたワークフロー定義を1つのファイルに集約する
- 環境差異(リソース名)などの定義はワークフロー定義には含めず外部ファイルで定義する
前提・事前準備
環境毎にブランチを作成して、ブランチ名をキーにした以下のような環境定義ファイルを用意します。
以下、YAMLファイルで定義していますが、JSONでも大丈夫です。
(個人的に定義ファイル系は見やすい・書きやすい・コメントが書けるという観点からYAMLの方が適していると考えています)
# 環境定義(キーはブランチ名とすること)
develop:
RESOURCE_GROUP: devgrp
DB_HOSTNAME: dev01.xxx.yyy.zzz
DB_NAME: hogehoge_system_dev
DB_USERNAME: dev_user
ENCRYPT_SERVICE: xxxxx-secrets-dev
testing:
RESOURCE_GROUP: test-group
DB_HOSTNAME: test01.xxx.yyy.zzz
DB_NAME: testdb
DB_USERNAME: test_user
ENCRYPT_SERVICE: yyyyy-secrets-test
production:
RESOURCE_GROUP: production-group
DB_HOSTNAME: prod.xxx.yyy.zzz
DB_NAME: production_db
DB_USERNAME: prod_user
ENCRYPT_SERVICE: zzzzz-secrets-prod
上の定義はサンプルですが、実施にはもっと環境別に異なる命名なってました。。。
※DBパスワードはクラウドの暗号化系サービス(AWSのKMSやAzureのKey Vault)に格納してあるので実際にはワークフローのジョブで取得するようにしているため、取得するためのサービスのリソース名を定義しています。
ワークフロー定義
先に結論。
name: Set environment variable from environments.yml
on:
push:
branches:
- deveop
- testing
- production
workflow_dispatch:
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
# チェックアウト
- name: checkout
uses: actions/checkout@main
# イベントの対象ブランチ名を取得して環境変数TARGET_BRANCH_NAMEに設定する
- name: Extract branch name
shell: bash
run: echo "TARGET_BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
# environments.ymlをyqでロード。yqだけだとループがややこしいのでjqにパイプさせてbash変数展開で name=valueに変換する
- name: Set environment variables
run: |
for s in $(yq e ".${{ env.TARGET_BRANCH_NAME }}" -o=j environments.yml | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]"); do
echo $s >> $GITHUB_ENV
done
# 設定されているかを確認
- name: Print environment variable
run: printenv | grep -E '^DB|^ENC|^RESOURCE' | sort
実行すると"Print environment variable"ジョブのログには以下のように出力されます。(productionの場合の抜粋)
Run printenv | grep -E '^DB|^ENC|^RESOURCE' | sort
printenv | grep -E '^DB|^ENC|^RESOURCE' | sort
shell: /usr/bin/bash -e {0}
env:
TARGET_BRANCH_NAME: production
RESOURCE_GROUP: production-group
DB_HOSTNAME: prod.xxx.yyy.zzz
DB_NAME: production_db
DB_USERNAME: prod_user
ENCRYPT_SERVICE: zzzzz-secrets-prod
DB_HOSTNAME=prod.xxx.yyy.zzz
DB_NAME=production_db
DB_USERNAME=prod_user
ENCRYPT_SERVICE=zzzzz-secrets-prod
RESOURCE_GROUP=production-group
あとは、 ${{env.xxxxx}}
で他の環境変数定義と同じように参照することができるようになります。
解説
ブランチ名の取得
ブランチ名を取得するのはトリガーされるイベント毎に異なります。
全部を把握しているわけではないので、知っていることだけを書きます。
pushとworkflow_dispath(手動実行)の場合
pushとworkflow_dispathの場合は GITHUB_REF
という環境変数に refs/heads/xxxxx
形式で格納されているため、xxxxxの部分をbashの変数展開を利用して抽出します。
前述の"Extract branch name"のジョブでは取得したブランチ名を環境変数 TARGET_BRANCH_NAME に設定していますが、以下のようにoutputで定義して後述のジョブで参照させることも可能です。
steps:
- name: Extract branch name
shell: bash
run: echo "::set-output name=branch::${GITHUB_REF#refs/heads/}"
id: extract_branch
- name: Print branch name
run: echo ${{ steps.extract_branch.outputs.branch }}
※workflow_dispathにおけるブランチは実行時に選択できる以下のリストから選択したブランチ名となります。
pull_requestの場合
私のプロジェクトではpull_requestをトリガーする運用にしていないので上のワークフロー定義には含まれていませんが、以下の変数からマージ元、マージ先のブランチ名をそれぞれ取得できます。
- ターゲット:
${{github.base_ref}}"
- ソース:
${{github.head_ref}}"
環境定義ファイルをロードして環境変数に設定する
ジョブ"Set environment variables"の部分の処理です。
環境定義ファイルはリポジトリの管理対象としてしておき、checkoutジョブでチェックアウトしたものをyqコマンドでロードさせます。
GitHubActionsのUbuntu-latestに標準でインストールされているため、特殊なことをせずに利用できます。
あとは、bashの変数展開などを駆使して、 name=value
形式にしたものを $GIHUB_ENV
という変数(実態はファイル)にリダイレクトさせることで、後続のジョブにおいて環境変数として ${{ env.DB_HOSTNAME }}
のように参照して利用することができるようになります。
余談?
前述の通り、$GITHUB_ENV
というところにリダイレクトすることで環境変数を設定してくれるわけなのですが、
ではこの環境変数のスコープ?はというと、そのジョブの中だけみたいです。
なので、以下のように事前ジョブで環境変数を設定して、needsでこの事前ジョブを指定した後続ジョブで設定した環境変数は参照できませんでした。
例)
name: confirm-scope
on:
workflow_dispatch:
jobs:
job1:
runs-on: ubuntu-latest
steps:
- name: Set environment variable
run: echo "NEW_ENV_VAR=HOGEHOGE" >> $GITHUB_ENV
- name: Show NEW_ENV_VAR
run: printenv NEW_ENV_VAR
job2:
needs:
- job1
runs-on: ubuntu-latest
steps:
- name: Show NEW_ENV_VAR
run: printenv NEW_ENV_VAR || echo "Not defined"
結果は、以下の通りです。残念。。。
Run printenv NEW_ENV_VAR || echo "Not defined"
printenv NEW_ENV_VAR || echo "Not defined"
shell: /usr/bin/bash -e {0}
Not defined
ちょっと調べてみたら、ドキュメントで記述が曖昧?矛盾?しているって指摘受けて修正されていました。