GitHub Actionsの共通したアクションを切り出してシンプルに保つ
こんにちは。スターフェスティバル株式会社の ikkitang です。
さて、皆様 GitHub Actions 使ってますか? 弊社では大半のプロジェクトで GitHub Actions が活用されていて、 月 1 回の WinSession で知見が共有されたりしています。
GitHub Actions の素敵な所はイベントの柔軟さだと感じています。それによって GUI での操作が想起できて、直感的にイベントをフックさせて Workflow を作っていくことができます。とはいえ、Workflow が増えてくると別の Workflow をコピペで持ってきて必要な Step を書き換えるなんてことをすることがないでしょうか。
そこで、今回は Workflow の保守性を上げるために Step の共通化について調べてみました。
ちなみに今回説明にあたって GitHub にサンプルを用意してみました。参考にしてみてください。
サンプルの前提の説明
- GitHub Pages で静的サイトを管理する
- 技術スタック
- Next.js
- Static HTML Export 機能によって出力した HTML・CSS・JS GitHub Pages でホスティングする
- 以下、2 つの Workflow を作る
- 全ての Push イベントにおいて、linter を掛けて build を試みる (普通なら test も動かす)
- ルール違反があれば NG とする
- 以下、main.yml として説明
- main ブランチへの Push(PR のマージ)において、GitHub Pages へデプロイする
- 以下、deploy.yml として説明
- 全ての Push イベントにおいて、linter を掛けて build を試みる (普通なら test も動かす)
GitHub Actions でよくある構成の一種、Push イベントで test/linter 実行、main ブランチへの Push でデプロイという形式です。
通常時の Workflow 設定
通常の main.yml
すべての Push イベントにおいて、linter を掛ける場合は以下のように書く事が出来ます。
ちなみに、package.json
に予め "lint": "next lint"
、"build": "next build"
という script の設定がある前提です。
npm ci
などで Next.js が動作する環境を整えた後でそれぞれのコマンドを実行する事で期待した動作を得る事が出来ます。
通常の main.yml(クリックで展開)
name: Main
on:
push:
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "16"
- name: Install dependencies
run: npm ci
- name: Check lint
run: npm run lint
- name: Run build
run: npm run build
通常の deploy.yml
main ブランチへの Push イベントにおいて、デプロイ処理を行う場合は以下のように書く事が出来ます。
Static HTML Export 機能で out
ディレクトリに export した後、そのディレクトリをpeaceiris/actions-gh-pages
を使ってデプロイしています。 (前提として、package.json の scripts に"export": "next export"
と定義されているとします。)
通常の deploy.yml(クリックで展開)
name: Deploy to GitHub Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "16"
- name: Install dependencies
run: npm ci
- name: Check lint
run: npm run lint
- name: Run build
run: npm run build
- name: Static HTML Export
run: npm run export
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./out
さて、余談ですが、今回 mainブランチへのpushイベント
をトリガーに Workflow を発火させました。本来の業務に目を向けると、main ブランチへは PR を作ってレビューが通ればマージする、というフローを取られる事が一般的でしょう。
mainブランチへのPRがマージされた時
をトリガーにしたい所ではありますが、実は GitHub Actions では ブランチへのPRがマージされた時
をトリガーにする事は出来ません。それをしたい場合は以下のどちらかをやる事になります。
- パターン1
- GitHub の Branch protection rules で PullRequest 経由以外の main ブランチへの Push を禁止する。
- main ブランチの
push
イベントをトリガーに Workflow を実行する。
- パターン2
- main ブランチへの PR がクローズされた時をトリガーに Workflow を実行する(PR のマージでもイベントとしては[closed]として扱われる)
- 実行時で
if: github.event.pull_request.merged == true
でマージ以外の時は Workflow を終了させる
パターン 2 の例(クリックで展開)
pull_request:
branches:
- main
types: [closed]
jobs:
sample:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: checkout
uses: actions/checkout@v2.4.0
今回はパターン 1 での実装をしました。
共通している部分に目を向ける
上記で示したコードは以下の画像のように、複数の共通な Step があります。
npm ci
・ npm run lint
/ npm run build
この辺の Step をまとめて共通化してしまいましょう。(共通化できる部分は実はもっとありますが、後に共通化の制限・注意事項にて補足します。)
コードの共通化
共通部分の切り出し
コードの共通化は Composite action
などと呼ばれています。公式ドキュメントとしては以下あたりが参考になります。
- https://docs.github.com/ja/actions/creating-actions/about-custom-actions#choosing-a-location-for-your-action
- https://docs.github.com/ja/actions/creating-actions/creating-a-composite-action
- https://docs.github.com/ja/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
yaml ファイルは任意の場所に配置してよい、とドキュメントにありますが、制約として yaml ファイルは action.yml
or action.yaml
というファイル名にする必要があります。
公式ドキュメントでは、 ./.github/actions/
ディレクトリに 各アクションのディレクトリを用意して配置する方法が紹介されています。
./github/actions/{Action名}/action.yml
といった感じでしょうか。
さて、実際の yaml についてですが、ある程度指定された書き方があります。
通常の Workflow では jobs.[*].steps
で各 Step を記述する所をruns.[*].steps
という記法で step を記述します。
また、 using: "composite"
という記述と 各 Step にある shell
の指定は必須です。
shell の指定が必須な理由は見つけられませんでした。 どの OS で動いてるかは呼び出し元の Workflows が知ってる事であって Composite action は知る術が無く、指定が必要なのだと思っています。
runs:
using: "Composite"
steps:
- name: Install dependencies
run: npm ci
shell: bash
- name: Check lint
run: npm run lint
shell: bash
- name: Run build
run: npm run build
shell: bash
共通部分の読み込み
読み込みはレポジトリルートからの相対パス指定で 先程 action.yml を配置したディレクトリの位置を指定することで読み込みがされます。
規定された方法で指定をしないと actions/checkout@v2
とかと同じように扱われてしまって、外部の third party アクションを探しにいくので注意です。
- main.yml
name: Main
on:
push:
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "16"
- name: Initialize
uses: ./.github/actions/build
- deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "16"
- name: Initialize
uses: ./.github/actions/build
- name: Static HTML Export
run: npm run export
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./out
ある程度の共通化の結果が得られました。
共通化の制約
if 構文のサポート
便利な Composite action 機能ですが、制約としては if構文
がサポートされていません。
ぶっちゃけ、自分は if 構文が無くて困る程使いこなせてないですが、サポートがあれば内部のスクリプトの微調整という点で役立つでしょう。
2021-01-18 追記
コメントで補足頂きましたが、現時点で既に if 構文 がサポートされているようです。 Shunsuke Suzuki さんありがとうございました。
Shunsuke Suzuki さんのコメント
共通化出来ない所
- uses: actions/checkout@v2
は共通化として切り出す事は出来ません。
切り出された Composite action は checkout アクションによって取得されるから、という理由だそうです。
use
構文のサポート
去年の 8 月にサポートされました。 今回、 setup-node は共通化に含めませんでしたが、こんな感じで Node.js のセットアップも Composite action に含める事も出来ます。
runs:
using: "composite"
steps:
- uses: actions/setup-node@v2
with:
node-version: "16"
- name: Install dependencies
run: npm ci
shell: bash
- name: Check lint
run: npm run lint
shell: bash
- name: Run build
run: npm run build
shell: bash
引数のサポート
inputs
という形式で Composite action に変数を渡す事も出来ます。例えばこのような感じです。
inputs:
node_version:
description: "setup node-version"
required: true
runs:
using: "composite"
steps:
- uses: actions/setup-node@v2
with:
node-version: ${{inputs.node_version}}
Context のサポート
githubコンテキスト
とか envコンテキスト
はもちろんサポートしてます。
まとめ
- GitHub Actions では共通した step をまとめて Composite action として外部に定義できる。
- uses 構文を使い、レポジトリの任意のディレクトリをレポジトリルートからの相対パスで読み込みが可能。
- ただし、ファイル名は action.yml としないといけない。
- 例)
./github/actions/{Action名}/action.yml
- 基本的に通常の Workflow と同様のコンテキストをサポートする
この記事が読まれた方のより良い GitHub Actions ライフにつながれば幸いです。
以上、スターフェスティバルのエンジニア ikkitang がお送りしました!
[PR] エンジニア募集中
私達スターフェスティバルではエンジニアを絶賛募集しております。 食
という我々の生活と切っては切り離せない関係の領域の問題について技術で問題解決していきませんか?
詳しくはこちら〜!
Discussion
以前はサポートされていませんでしたが、既に(この記事の公開日時 2022.01.17 時点で)サポートされているかと思います。
Shunsuke Suzuki さん
コメントありがとうございます!
めちゃくちゃ、ドキュメントに書いてある〜〜w
本文追記させて頂きました。
補足ありがとうございます。