はじめに
最近、CircleCIのワークフローの一部をGitHub Actionsに書き換えました。
同様の作業をこれから行う人に向けて、本記事では
「最初にこのあたりを押さえておけば割とスムースに書き換えができるのでは?」
と思われるポイントをまとめました。
本記事では、以下の観点でCircleCIとGitHub Actionsを比較しながら、GitHub Actionsのワークフローファイルの書き方を簡単に解説していきます。
- ワークフローファイルのパス
- ジョブの構成
- ソースコードのチェックアウト
- 任意のコマンドの実行
- キャッシュ
- 環境変数
本記事で触れられていない点に関しては、公式のリファレンスを見るか、Boothで販売されている
が非常におすすめですので、そちらを読んでいただければと思います。
(私個人の力量ではActionsを理解するのに公式リファレンスだけでは辛かったので、上記の本には非常にお世話になりました。CircleCIの方は、公式リファレンスだけでとても分かりやすいんですけどね。。。)
1. ワークフローファイルのパス
CircleCI, GitHub Actionsそれぞれのワークフローファイルのパスは以下になります。
CircleCI | GitHub Actions |
---|---|
.circleci/config.yml | .github/workflows/test.yml .github/workflows/deploy.yml など任意の名前 |
2. ジョブの構成
2.1. CircleCIの場合
CircleCIでは、jobs
でジョブを定義し、workflows
でジョブの実行順や条件を定義しています。
version: 2.1
jobs:
build:
# 略
test:
# 略
deploy:
# 略
workflows:
version: 2
build-test-deploy:
jobs:
- build
- test:
requires:
- build
- deploy:
requires:
- test
filters:
branches:
only:
- master
requires
で、先行ジョブを定義します。
その他、今回のサンプルではfilters
, branches
, only
を使って、master
ブランチでのみdeploy
ジョブが動くようにしています。
2.2. GitHub Actionsの場合 その1
Actionsでは、CircleCIでいうところのworkflows
にあたるものは無いようなのでjobs
だけでジョブの実行順などを定義していきます。
name: build-test
on: [pull_request] # on: pull_request でも良い(指定したいイベントがひとつだけなら)
jobs:
build:
# 略
test:
needs: [build] # needs: build でも良い(指定したいジョブがひとつだけなら)
# 略
name: build-test-deploy
on:
push: # イベントに対して更に条件を追加する場合は、[]を使った指定はできない
branches:
- master
jobs:
build:
# 略
test:
needs: [build]
# 略
deploy:
needs: [test]
# 略
2.2.1. on
on
では、Actionsを動かすトリガーとなるイベントを定義できるので、今回のサンプルでは
PRを立てた時に
build
,test
ジョブを動かす(このPRのブランチにpushした時にも動きます)master
ブランチにpushした時に、build
,test
,deploy
ジョブを動かす(PRをmaster
ブランチにマージした時も動きます)
としました。
2.2.2. needs
needs
で先行ジョブを定義しています。
2.3. GitHub Actionsの場合 その2
先ほどの例ではワークフローを2つに分けていますが、build
, test
ジョブの内容が同じであれば記述として冗長になってしまいます。
ワークフローを分けないのであれば、以下の構成が考えられるかと思います。
name: build-test-deploy
on:
pull_request:
push:
branches:
- master # masterにマージした時にもワークフローを動かすため
jobs:
build:
# 略
test:
needs: [build]
# 略
deploy:
needs: [test]
if: github.ref == 'refs/heads/master' # masterにマージした時のみdeployジョブを動かすため
# 略
2.3.1. if
GitHub Actionsでは、ジョブやステップに対し、if
で実行条件を定義できます。
このif
を使って、deploy
ジョブが動くのを、masterにマージした時のみとしています。
2.3.2. github.ref / github.head_ref / github.base_ref
今回は、github.ref
というコンテキストを利用して、masterマージ時のみdeploy
ジョブが動くようにしました。
似たようなコンテキストとして、github.head_ref
, github.base_ref
があります。
その値についてはrefs/...
が先頭に付いたり付かなかったりするので、状況別にまとめました。参考にしてください。
プロパティ | プルリクエスト時の値 | masterへマージした時の値 |
---|---|---|
github.ref | refs/pull/PR番号/merge | refs/heads/master |
github.head_ref | PRのブランチ名 (feature/hoge など。refs/...は付かない。) |
無し |
github.base_ref | PRでマージする予定のブランチ名 (master など。refs/...は付かない。) |
無し |
3. ソースコードのチェックアウト
3.1. CircleCIの場合
CircleCIでは、checkout
コマンドでGitHubのソースコードをチェックアウトします。
version: 2.1
executors:
default:
docker: # 略
jobs:
build:
executor: default
steps:
- checkout
# 略
3.2. GitHub Actionsの場合
GitHub Actionsでは、actions/checkout
を使用してGitHubのソースコードをチェックアウトします。
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
3.3. git branchおよびgit logの結果の違い
CircleCIのcheckout
コマンドと、GitHub Actionsのactions/checkout
では、チェックアウトのされ方に違いがあります。
CircleCIのチェックアウト後に、git branch
とgit log
を実施した結果の例は以下になります。
$ git branch
* feature/hogeなどPRのブランチ名
master
$ git log --oneline -5
xxxxxxx (HEAD -> feature/hoge, origin/feature/hoge, master) コミットメッセージ
xxxxxxx コミットメッセージ
xxxxxxx コミットメッセージ
xxxxxxx コミットメッセージ
xxxxxxx コミットメッセージ
対して、GitHub Actionsでのチェックアウト後に、git branch
とgit log
を実施した結果の例は以下になります。
$ git branch
* (HEAD detached at pull/PR番号/merge)
$ git log --oneline -5
xxxxxxx Merge プルリクエストのHEADのコミットハッシュ into masterブランチのコミットハッシュ
GitHub Actionsでのチェックアウトではデフォルトで--depth=1
が指定されているので、CircleCIのチェックアウトのように過去のコミット履歴は取得できません。
そのため、例えば
$ git diff --name-only origin/master...HEAD
で、masterとプルリクエスト間で差分のあるファイル名を出力しようとしても、以下のエラーになります。
fatal: ambiguous argument 'origin/master...HEAD': unknown revision or path not in the working tree.
もし、GitHub Actionsでも過去のコミット履歴を利用して何らかの処理をしたい場合は、チェックアウト後に追加のステップでgit fetch --prune --unshallow
を行えば良いかと思います。
steps:
- uses: actions/checkout@v2
- run: git fetch --prune --unshallow
4. 任意のコマンドの実行
4.1. CircleCIの場合
run
でコマンドを実行します。
steps:
- run: echo "Hello World"
name
などを定義する場合は、以下の記述になります。
steps:
- run:
name: Greeting # CircleCIの実行結果の画面にステップ名として表示される
command: echo "Hello World"
複数コマンドを実行する場合は、以下の記述になります。
steps:
- run: |
echo "One"
echo "Two"
steps:
- run:
name: Count
command: |
echo "One"
echo "Two"
4.2. GitHub Actionsの場合
GitHub Actionsもrun
でコマンドを実行します。
steps:
- run: echo "Hello World"
name
などを定義する場合は、以下の記述になります。
steps:
- name: Greeting # GitHub Actionsの実行結果の画面にステップ名として表示される
run : echo "Hello World"
複数コマンドを実行する場合は、以下の記述になります。
steps:
- name: Count
run: |
echo "One"
echo "Two"
5. キャッシュ
5.1. CircleCIの場合
CircleCIでは、restore_cache
とsave_cache
コマンドでキャッシュのリストアと保存を行います。
# 略
steps:
# 略
- restore_cache:
key: npm-v1-{{ checksum "package-lock.json" }}
- run:
name: if there is no node_modules, npm ci
command: |
if [ ! -d node_modules ]; then
npm ci
fi
- save_cache:
key: npm-v1-{{ checksum "package-lock.json" }}
paths:
- node_modules
5.2. GitHub Actionsの場合
GitHub Actionsでは、actions/cache
でキャッシュのリストアと保存を行います。
# 略
steps:
# 略
- name: cache npm
uses: actions/cache@v1.1.2
with:
path: node_modules
key: npm-v1-${{ hashFiles('package-lock.json') }}
- name: if there is no node_modules, npm ci
run: |
if [ ! -d node_modules ]; then
npm ci
fi
なお、上記のサンプルでは、キャッシュからnode_modules
を復元できたかどうかをnode_modules
の有無で判定し、復元できている場合はnpm ci
を実行しないようにしています。
actions/cache
ではキャッシュヒットした場合に、cache-hit
に'true'
を出力するので、これを利用する場合は以下のように書き直せます。
# 略
steps:
# 略
- name: cache npm
id: cache-npm # ステップを特定するためのidを定義
uses: actions/cache@v1.1.2
with:
path: node_modules
key: npm-v1-${{ hashFiles('package-lock.json') }}
- name: npm ci
if: steps.cache-npm.outputs.cache-hit != 'true' # キャッシュヒットした時のみnpm ciを実行する
run: npm ci
5.2.1. キャッシュの保存のタイミングについて
CircleCIではキャッシュのリストア/保存のステップをそれぞれ記述しましたが、GitHub Actionsではキャッシュをリストアしたいタイミングにactions/cache
を使用するステップを記述するのみとなり、保存のステップは記述しません。
キャッシュの保存は、actions/cache
ステップが存在するジョブの最後にPost + actions/cacheのステップ名
というステップ名で実行されます。
ただし、ジョブが正常終了しなかった場合は、キャッシュを保存するステップは実行されません。
そのため、ビルドのためのジョブを存在させず、テストジョブ内で
-
actions/cache
の利用とビルド - テスト
を行なっていると、テストが通らない限り、キャッシュが保存されません。
# 略
jobs:
# build: # ビルドジョブ無し
test:
steps:
# 略
- name: cache npm # テストが通らないと、ジョブの最後のキャッシュ保存を行なってくれない
# 略
- name: npm ci
# 略
- name: npm test
run: npm test
もし、これを防ぎたい場合は、ビルドのためのジョブを定義し、ビルドジョブとテストジョブそれぞれで、キャッシュのkey
は同じにした上で、actions/cache
の利用とビルドを行えば良いかと思います。
# 略
jobs:
build:
steps:
# 略
- name: cache npm
id: cache-npm
uses: actions/cache@v1.1.2
with:
path: node_modules
key: npm-v1-${{ hashFiles('package-lock.json') }}
- name: npm ci
# 略
test:
needs: [build]
steps:
# 略
- name: cache npm
id: cache-npm
uses: actions/cache@v1.1.2
with:
path: node_modules
key: npm-v1-${{ hashFiles('package-lock.json') }}
- name: npm ci
# 略
- name: npm test
run: npm test
なお、キャッシュの保存のステップは以下のような処理結果になります。
Cache saved successfully
Post job cleanup.
/bin/tar -cz -f /home/runner/work/_temp/9fa1f088-a867-4efe-80eb-
132944ceba50/cache.tgz -C /home/runner/work/リポジトリ名/リポジトリ名/node_modules .
Cache saved successfully
リストア時にキャッシュヒットした場合は、キャッシュは上書き保存されません。
Post job cleanup.
Cache hit occurred on the primary key npm-v1-433b1cb5f6b93173ee6214d276272b0b7fc0c4c8b3e0e3770d29c98035416d4e, not saving cache.
5.2.2. 単一ファイルのキャッシュ
CircleCIでは、paths
にディレクトリだけでなくファイル名を指定し、それをキャッシュすることができますが、actions/cache
ではディレクトリしかキャッシュできません。
GitHub Actionsで単一のファイルをキャッシュする場合は、退避用のディレクトリを作って、そこにファイルをコピーした上でディレクトリごとキャッシュし、リストアされた時にはそのディレクトリから本来のパスにコピーする、といった作業が必要になるかと思います。
6. 環境変数
6.1. CircleCIの場合
CircleCIでは、Project Settings
のEnvironment Variables
画面で環境変数を設定します。
ワークフローファイル上で、環境変数を新規に設定する場合は、environment
に記述します。
version: 2.1
jobs:
# 略
deploy:
environment:
AWS_DEFAULT_REGION: ap-northeast-1
# 略
前述の画面で設定した環境変数の値を上書きする場合も同様の方法となります。
6.2. GitHub Actionsの場合
GitHub Actionsでは、GitHubのSettings
のSecrets
画面で、いったん秘密情報として設定します。
その上で、ワークフローファイルで、env
に改めて環境変数として設定します。
秘密情報は、${{ secrets.秘密情報のキー }}
と記述することで取り出せます。
# 略
jobs:
# 略
deploy:
needs: [test]
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ap-northeast-1
なお、env
はワークフロー全体やステップ単位に適用することもできます。
# 略
env:
# 略
jobs:
# 略
# 略
jobs:
steps:
- name: s3 upload
env:
# 略
run: # 略
おまけ(SSH)
GitHub Actionsの実行環境にSSHで入って調査したりデバッグをしたい場合は、以下の記事を参考にしてください。