🪄

GitHub Actions: Reusable workflowsのススメ

2023/12/02に公開

概要

GitHubでは、各Workflowをコピー&ペーストして同じようなコードを何度も記述するのではなく、何度も使えるように設定できます。この機能は、同じ作業の繰り返しを減らし、ワークフローの管理を簡単にします。
平たく言えば、保守性、再利用性の向上を目的に行います。
https://docs.github.com/en/actions/using-workflows/reusing-workflows

お題目はいいので具体的な話をすると、下記のような感じのものを作れます。

画像を見てもらうとわかるのですが、

  • image-build
  • image-push

で上下ともに同じ工程を踏みます。
GitHubのワークフロー再利用機能を用いると、image-buildimage-pushのような作業を、別のファイルに切り出して再利用できるようになります。
今回はこちらのリポジトリで作業していました
https://github.com/na2na-p/training-gha-reusable

実践

今回はReact + TypeScriptなプロジェクトを例に、簡易なCIを作ってみましょう

プロジェクト作成

今回はtraining-gha-reusableという名前で作ります
先にGithub上に同名のリポジトリを作成しておいてください。
作成後に見えるリモートのURLを控えておいてください

pnpm create vite
cd training-gha-reusable
pnpm install

git init
git commit --allow-empty -m "Initial commit"
git add .
git commit -m "Project init"
git remote add origin (remoteのurl)
git push --set-upstream origin topic/project-init

履歴の先頭として空コミットをします(任意)
その後でプロジェクトのファイルを追加します。
次から実際にワークフローの構築をしていきます

ワークフロー構築

どういったときに何をするか検討する

  • main宛のPRが提出されたらCIとして以下を行う
    • pnpm lint
    • pnpm build
  • mainにPRがマージされたら(pushされたら)下記を行う
    • pnpm lint
    • pnpm build
    • (上記のテストに通過したら)deploy

今回は上記のようにしたいと思います。
デプロイについては今回は実際には行わず、適当なスクリプトでお茶を濁すことにします

まずは書いていく

ファイル作成

まず下記のようにしてブランチ作成&切り替えを済ませておいてください

git switch -C topic/ci


上記のような構造で2ファイル作成してください

  • cd-for-main.yml
    • mainにマージされた(pushされた)時に発火するワークフロー
  • ci-for-pr.yml
    • main向けのPRで発火するワークフロー
main向け
.github/workflows/cd-for-main.yml
name: CD for main push
on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  lint:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm lint
  build:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm build
  deploy:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Deploy
        run: |
          echo "Deploying to production"
          echo "Deployed to production"

PR向け
.github/workflows/ci-for-pr.yml
name: CD for main push
on:
  pull_request:
    branches:
      - main
    types:
      - opened
      - reopened
      - synchronize
      - ready_for_review
      - labeled
      - unlabeled

jobs:
  lint:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm lint
  build:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm build

書けたらcommit&pushしておいてください

動作確認

今作ったブランチをmain向けにPR出します。
下記画像のように、四角で囲んだワークフローがpassしていれば問題ないです

参考: https://github.com/na2na-p/training-gha-reusable/pull/1

コードを確認する

下記の部分が2つのワークフローで完全に同一になっていると思います
この部分を共通化してしまおう、というのが本記事で紹介するReusable workflowsになります

  lint:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm lint
  build:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm build

どう変わったか把握しやすくするために一旦このPRはマージしてしまいましょう
コードレビューなんてなかった
マージしたら下記のようにしてmainにcheckoutして最新化してしまいましょう

git checkout main
git pull --rebase

Reusable workflowsとして切り出す

まずはブランチを切りましょう

git switch -C topic/reusable

さて、早速ですがbuildとlintを切り出します
build.ymlとlint.ymlを作成してください
下記のようになればokです

.github
└── workflows
    ├── build.yml
    ├── cd-for-main.yml
    ├── ci-for-pr.yml
    └── lint.yml

build

.github/workflows/build.yml
name: Build
on:
  workflow_call:

jobs:
  build:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm build

lint

.github/workflows/lint.yml
name: Lint
on:
  workflow_call:

jobs:
  lint:
    timeout-minutes: 60
    runs-on:
      - ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
      - run: pnpm i
      - name: Lint
        run: pnpm lint

乱暴に言えばworkflow_callを書けばReusable workflowとして扱うことができます。
他には特別なことはしません。

できたら次は、それを組み込んでいく作業をしましょう
まずはci-for-pr.ymlから
差分だけを表示します

.github/workflows/ci-for-pr.yml
  lint:
-    timeout-minutes: 60
-    runs-on:
-      - ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-      - uses: pnpm/action-setup@v2
-        with:
-          version: 8
-          run_install: false
-      - run: pnpm i
-      - name: Lint
-        run: pnpm lint
+     uses: ./.github/workflows/lint.yml
  build:
-    timeout-minutes: 60
-    runs-on:
-      - ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-      - uses: pnpm/action-setup@v2
-        with:
-          version: 8
-          run_install: false
-      - run: pnpm i
-      - name: Lint
-        run: pnpm build
+    uses: ./.github/workflows/build.yml

以上で対応は完了です
差分のcommit&pushをして、先ほどと同じようにPRを出してみましょう
また同じようにすべてpassしていれば問題ありません、マージしてしまいましょう

https://github.com/na2na-p/training-gha-reusable/pull/2

以上です!
実際にはシークレットの受け渡しやら、引数(のようなもの)のやり取りなどやりますが、今回は省略します。

興味ある方は下記を見てみるといいかもしれません。
私が組んだものなので質問等あれば https://x.com/na2na_chang までお願いします
https://github.com/miso-memoReal/memorial-backend/tree/main/.github/workflows

Discussion