0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actionsをローカルでデバッグしよう!「act」

Posted at

概要

最近CI/CDまわりをリポジトリ内でちゃんと整備していこうかと考えています。GitHub Actionsをうまく使えばいろいろなことができそうなのですが、そもそもActionsで何ができるかもよくわかっていないうえ、毎度GitHubにpushしなければ試せないので、めんどくさい...
また、mainブランチ(デフォルトブランチ)にActionsのファイルがないと手動のワークフローであればそもそも選択すらできない状態だったりと、気軽に試すことへのハードルが高い状態でした。

そんな中、ローカルで手軽にActionsを試す方法がないかと調べていたところ、「act」というツールがあったので、導入して試してみようと思います。

actとは

紹介したとおり、GitHub Actionsのワークフローをローカルで実行してくれるツール。Docker APIを利用して、ワークフロー内で定義されたイメージを構築し、GitHubが提供するものと一致する環境にしたうえでワークフローを実行してくれるみたいです。

試してみる

環境

  • OS:Windows 11(WSL導入済み)
  • docker導入済み(Docker Desktopは入っていない状態)

導入

DockerコマンドをWindowsから叩けるようにしていないので、WSL上にactはインストールします。WSLに入り、以下コマンドでインストール。

curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash

一応エイリアスも付けておきます

echo "alias act='{actのファイル場所}'" >> ~/.bashrc

サンプルリポジトリで試す

公式にあった以下サンプルリポジトリで試してみます。

テスト用のActionsはこんな感じ。めちゃシンプルですが、checkoutとnpm testを行います。

main.yml
name: CI
on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
    - run: npm install
    - run: npm test

クローンしたら、 -nオプションをつけて実行してみます。このオプションはdry-runができるオプションです。

papa@ENVY:~/workspace/github-actions-demo$ act -n
INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock' 
? Please choose the default image you want to use with act:
  - Large size image: ca. 17GB download + 53.1GB storage, you will need 75GB of free disk space, snapshots of GitHub Hosted Runners without snap and pulled docker images
  - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with most actions
  - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions

Default image and other options can be changed manually in /home/papa/.config/act/actrc (please refer to https://nektosact.com/usage/index.html?highlight=configur#configuration-file for additional information about file structure)  [Use arrows to move, type to filter, ? for more help]
  Large
> Medium
  Micro

なんかデフォルトのイメージサイズを選べるみたいなので、とりあえずデフォルトのMediumにしておきます。その後dry-runでActionsファイルの検証をしてくれました。

*DRYRUN* [CI/test] ⭐ Run Set up job
*DRYRUN* [CI/test] 🚀  Start image=catthehacker/ubuntu:act-latest
*DRYRUN* [CI/test]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
*DRYRUN* [CI/test]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
*DRYRUN* [CI/test]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
*DRYRUN* [CI/test]   ✅  Success - Set up job
*DRYRUN* [CI/test]   ☁  git clone 'https://github.com/actions/setup-node' # ref=v1
*DRYRUN* [CI/test] ⭐ Run Main actions/checkout@v2
*DRYRUN* [CI/test]   ✅  Success - Main actions/checkout@v2 [3.960898ms]
*DRYRUN* [CI/test] ⭐ Run Main actions/setup-node@v1
*DRYRUN* [CI/test]   ✅  Success - Main actions/setup-node@v1 [4.383822ms]
*DRYRUN* [CI/test] ⭐ Run Main npm install
*DRYRUN* [CI/test]   ✅  Success - Main npm install [15.374775ms]
*DRYRUN* [CI/test] ⭐ Run Main npm test
*DRYRUN* [CI/test]   ✅  Success - Main npm test [14.103341ms]
*DRYRUN* [CI/test] ⭐ Run Complete job
*DRYRUN* [CI/test] Cleaning up container for job test
*DRYRUN* [CI/test]   ✅  Success - Complete job
*DRYRUN* [CI/test] 🏁  Job succeeded

満を持して本実行もやってみます。

papa@ENVY:~/workspace/github-actions-demo$ act
INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock' 
[CI/test] ⭐ Run Set up job
[CI/test] 🚀  Start image=catthehacker/ubuntu:act-latest
[CI/test]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[CI/test]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[CI/test]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[CI/test]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=
[CI/test]   ✅  Success - Set up job
[CI/test]   ☁  git clone 'https://github.com/actions/setup-node' # ref=v1
[CI/test] ⭐ Run Main actions/checkout@v2
[CI/test]   🐳  docker cp src=/home/papa/workspace/github-actions-demo/. dst=/home/papa/workspace/github-actions-demo
[CI/test]   ✅  Success - Main actions/checkout@v2 [21.559058ms]
[CI/test] ⭐ Run Main actions/setup-node@v1
[CI/test]   🐳  docker cp src=/home/papa/.cache/act/actions-setup-node@v1/ dst=/var/run/act/actions/actions-setup-node@v1/
[CI/test]   🐳  docker exec cmd=[/opt/acttoolcache/node/18.20.8/x64/bin/node /var/run/act/actions/actions-setup-node@v1/dist/index.js] user= workdir=
| [command]/usr/bin/tar xz --warning=no-unknown-keyword -C /tmp/0b4179be-4517-414e-9994-baff7e8f6edf -f /tmp/b2a498c2-87dc-4bea-af88-b9a5d6166147
| [command]/opt/hostedtoolcache/node/10.24.1/x64/bin/node --version
| v10.24.1
| [command]/opt/hostedtoolcache/node/10.24.1/x64/bin/npm --version
| 6.14.12
[CI/test]   ❓ add-matcher /run/act/actions/actions-setup-node@v1/.github/tsc.json
[CI/test]   ❓ add-matcher /run/act/actions/actions-setup-node@v1/.github/eslint-stylish.json
[CI/test]   ❓ add-matcher /run/act/actions/actions-setup-node@v1/.github/eslint-compact.json
[CI/test]   ✅  Success - Main actions/setup-node@v1 [3.93772134s]
[CI/test]   ⚙  ::add-path:: /opt/hostedtoolcache/node/10.24.1/x64/bin
[CI/test] ⭐ Run Main npm install
[CI/test]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/2] user= workdir=
| added 280 packages from 643 contributors and audited 280 packages in 3s
| 
| 24 packages are looking for funding
|   run `npm fund` for details
| 
| found 56 vulnerabilities (12 low, 17 moderate, 23 high, 4 critical)
|   run `npm audit fix` to fix them, or `npm audit` for details
[CI/test]   ✅  Success - Main npm install [3.333388341s]
[CI/test] ⭐ Run Main npm test
[CI/test]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/3] user= workdir=
| 
| > github-actions-demo@1.0.0 test /home/papa/workspace/github-actions-demo
| > mocha ./tests --recursive
| 
| 
| 
|   GET /
|     ✓ should respond with hello world
| 
| 
|   1 passing (14ms)
| 
[CI/test]   ✅  Success - Main npm test [349.237214ms]
[CI/test] ⭐ Run Complete job
[CI/test] Cleaning up container for job test
[CI/test]   ✅  Success - Complete job
[CI/test] 🏁  Job succeeded

ActionsでのJSのテストまで正常に実行してくれました。

上の実験では勝手にpushイベントを実行してくれました。デフォルトで、何もイベント名を指定せずにactを実行すると、pushイベントが呼ばれるらしいです。

特定のイベントを実行したい場合は、act pull_requestact scheduleなどでコマンドを実行すればOK!

オプションなど

特定のワークフローのみ実行(-W

デフォルトではすべてのワークフローファイルを実行しようとするみたいです。特定のワークフローファイルのみを実行させたい場合は、-Wオプションでファイルを指定します。

act -W '.github/workflows/checks.yml'

特定のジョブのみ実行(-j

ジョブも同じように、デフォルトではすべてのジョブを実行します。特定のジョブのみを実行させたい場合は、-jオプションでジョブ名を指定します。

act -j 'test'

この例であれば、そのリポジトリ内のすべてのワークフローのうち、pushイベントに紐づいているtestという名前のジョブがすべて起動する、という形になります。

GitHub variablesの置き換え(--var, --var-file

GitHub variablesで置き換わるはずの値をローカルで再現することも可能です。

# 直接コマンドから指定
act --var VARIABLE=some-value

# ファイルから読み込み
act --var-file my.variables

試しにこんな感じのワークフローと、variableが記述されたファイルを用意してみました。

test-variables.yml
name: Test Variables
on: push

jobs:
  test-variables:
    runs-on: ubuntu-latest
    steps:
    - name: Check GitHub Variables
      run: |
        echo "=== GitHub Variables ==="
        echo "MY_VARIABLE: ${{ vars.MY_VARIABLE }}"
        echo "API_ENDPOINT: ${{ vars.API_ENDPOINT }}"
        echo "ENVIRONMENT: ${{ vars.ENVIRONMENT }}"

    - name: Use Variables
      env:
        MY_VAR: ${{ vars.MY_VARIABLE }}
        MY_ENV: ${{ vars.ENVIRONMENT }}
      run: |
        echo "=== Combined Usage ==="
        echo "Variable MY_VARIABLE is: $MY_VAR"
        echo "Environment is: $MY_ENV"
my.variables
MY_VARIABLE=test-value-123
API_ENDPOINT=https://api.example.com
ENVIRONMENT=development

実行結果がこちら。vars.***の部分が正常に置き換わっていることが確認できます

papa@ENVY:~/workspace/github-actions-demo$ act -W .github/workflows/test-variables.yml --var-file my.variables
INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock' 
[Test Variables/test-variables] ⭐ Run Set up job
[Test Variables/test-variables] 🚀  Start image=catthehacker/ubuntu:act-latest
[Test Variables/test-variables]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[Test Variables/test-variables]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[Test Variables/test-variables]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[Test Variables/test-variables]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=
[Test Variables/test-variables]   ✅  Success - Set up job
[Test Variables/test-variables] ⭐ Run Main Check GitHub Variables
[Test Variables/test-variables]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/0] user= workdir=
| === GitHub Variables ===
| MY_VARIABLE: test-value-123
| API_ENDPOINT: https://api.example.com
| ENVIRONMENT: development
[Test Variables/test-variables]   ✅  Success - Main Check GitHub Variables [57.765277ms]
[Test Variables/test-variables] ⭐ Run Main Use Variables
[Test Variables/test-variables]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=
| === Combined Usage ===
| Variable MY_VARIABLE is: test-value-123
| Environment is: development
[Test Variables/test-variables]   ✅  Success - Main Use Variables [69.503679ms]
[Test Variables/test-variables] ⭐ Run Complete job
[Test Variables/test-variables] Cleaning up container for job test-variables
[Test Variables/test-variables]   ✅  Success - Complete job
[Test Variables/test-variables] 🏁  Job succeeded

同様に、シークレット情報も同じような形で実行できます。これにより、ワークフロー内のsecrets.***の値が置き換わります。

  • コマンドから:act -s MY_SECRET=some-value
  • ファイルから:act --secret-file my.secrets

さらに実験:GitHubの機能も実行できる?

issueへの自動コメント返信やProjectのステータス変更など、GitHubの操作回りなどもローカルで試せるみたい?探り探りやってみます。Issueへの自動コメント返信を試してみましょう。

ワークフローを作る

こんな感じで、Issueがopenになったときに固定のコメントを返却するようなワークフローを試してみます。

issue-commnent.yml
name: Auto Comment on Issue

on:
  issues:
    types: [opened]

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - name: Comment on new issue
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '👋 こんにちは!Issueを作成していただきありがとうございます。\n\n担当者が確認次第、対応させていただきます。'
            })

いったん実行

何も考えずにact issuesで実行してみます。

| Error: Input required and not supplied: github-token
|     at Object.getInput (/run/act/actions/actions-github-script@v7/dist/index.js:212:15)
|     at main (/run/act/actions/actions-github-script@v7/dist/index.js:36262:24)
|     at /run/act/actions/actions-github-script@v7/dist/index.js:36260:1
|     at /run/act/actions/actions-github-script@v7/dist/index.js:36317:3
|     at Object.<anonymous> (/run/act/actions/actions-github-script@v7/dist/index.js:36320:12)
|     at Module._compile (node:internal/modules/cjs/loader:1364:14)
|     at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
|     at Module.load (node:internal/modules/cjs/loader:1203:32)
|     at Module._load (node:internal/modules/cjs/loader:1019:12)
|     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12)

なるほど、ローカルからの実行なのでGitHub tokenが必要みたいですね。これはSecretと同じように登録できるみたいなので、別ファイルとして保存してコマンドから読み込めるようにしましょう。

my.token
GITHUB_TOKEN=ghp_*************************

以下コマンドで再実行してみます。そもそも紐づけるべきIssueもないはずなのでどうなるんでしょう...

act issues --secret-file my.token
| RequestError [HttpError]: Not Found
|     at /run/act/actions/actions-github-script@v7/dist/index.js:9537:21
|     at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
|   status: 404,
|   response: {
|     url: 'https://api.github.com/repos/papaHasuo/act-github-test/issues//comments',
|     status: 404,
|     headers: {
|       'access-control-allow-origin': '*',
|       'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset',
|       connection: 'close',
|       'content-security-policy': "default-src 'none'",
|       'content-type': 'application/json; charset=utf-8',
|       date: 'Sun, 01 Feb 2026 09:01:56 GMT',
|       'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin',
|       server: 'github.com',
|       'strict-transport-security': 'max-age=31536000; includeSubdomains; preload',
|       vary: 'Accept-Encoding, Accept, X-Requested-With',
|       'x-content-type-options': 'nosniff',
|       'x-frame-options': 'deny',
|       'x-github-media-type': 'github.v3; format=json',
|       'x-github-request-id': 'EC4D:20C716:3B5415:5010A4:697F1683',
|       'x-xss-protection': '0'
|     },
|     data: {
|       message: 'Not Found',
|       documentation_url: 'https://docs.github.com/rest',
|       status: '404'
|     }
|   },
|   request: {
|     method: 'POST',
|     url: 'https://api.github.com/repos/papaHasuo/act-github-test/issues//comments',
|     headers: {
|       accept: 'application/vnd.github.v3+json',
|       'user-agent': 'actions/github-script octokit-core.js/5.0.1 Node.js/18.20.8 (linux; x64)',
|       authorization: 'token [REDACTED]',
|       'content-type': 'application/json; charset=utf-8'
|     },
|     body: '{"body":"👋 こんにちは!Issueを作成していただきありがとうございます。\\n\\n担当者が確認次第、対応させていただきます。"}',
|     request: {
|       agent: [Agent],
|       fetch: [Function: proxyFetch],
|       hook: [Function: bound bound register]
|     }
|   }
| }

そりゃそう、404エラーが出ちゃいました。ただしよく見ると

request: {
|     method: 'POST',
|     url: 'https://api.github.com/repos/papaHasuo/act-github-test/issues//comments',

URLが/issue//commentsになっており、Issue番号が何も指定されていないみたいですね。おそらくですが、Actionsファイル内のcontext.issue.number部分がうまく取得できていないのかと思います。逆に言えば、ownerやrepo自体の取得はちゃんとできてそうですね。

issue-comment.yml
  script: |
    github.rest.issues.createComment({
      issue_number: context.issue.number,       ← **ここが取得できない**
      owner: context.repo.owner,
      repo: context.repo.repo,
      body: '👋 こんにちは!Issueを作成していただきありがとうございます。\n\n担当者が確認次第、対応させていただきます。'
    })

イベントペイロードの指定(-eオプション)

-eをつけることで、イベントの詳細データをJSON形式で読み込んでくれるみたいです。今回で行くと、openになったIssueの情報を前もってJSONの形でローカルに持たせておけば、その情報を読み込めるらしい...今回はIssue番号さえわかればいいので、以下のファイルを用意します。

issue-opend.json
{
  "issue": {
    "number": 1
  }
}

そのうえで、コマンドを変えて実行してみました。

act issues -e .github/workflows/issue-opened.json --secret-file my.token
結果抜粋
|   request: {
|     method: 'POST',
|     url: 'https://api.github.com/repos/papaHasuo/act-github-test/issues/1/comments',
|     headers: {
|       accept: 'application/vnd.github.v3+json',
|       'user-agent': 'actions/github-script octokit-core.js/5.0.1 Node.js/18.20.8 (linux; x64)',
|       authorization: 'token [REDACTED]',
|       'content-type': 'application/json; charset=utf-8'
|     },
|     body: '{"body":"👋 こんにちは!Issueを作成していただきありがとうございます。\\n\\n担当者が確認次第、対応させていただきます。"}',
|     request: {
|       agent: [Agent],
|       fetch: [Function: proxyFetch],
|       hook: [Function: bound bound register]
|     }
|   }
| }

さっきと違いURL情報がちゃんと作られていそうですね!
もしかしてこれって、該当リポジトリにちゃんとIssueがあれば作ってくれる...?

雑にIssueを作ってみて...
image.png

再度コマンドを叩くと...
image.png

できました!下準備さえあればGitHubの操作もある程度できそうですね!

まとめのリポジトリはこちら

感想

思ったよりもいろんなことができそうなツールでした。チェックアウトしてビルド、あたりは何となく想像ついていましたが、Issue周りの自動化もローカルで試せるとは...
ただ流石にデプロイ回りなども考え出すと、権限周りとかも含めてローカルで考えるほうが面倒になるので、それに関してはもうGitHubにコミットして直接実験するほうが良いのかな、と思いました。dry-runで構文周りのチェックだけはやってもいいかも。

個人的には、GitHubとの紐づけでどんなことができるのかちょっと試したいときに、毎回試行錯誤しながらのコミットが大量に残るのが嫌だったので、そこをローカルで試せるのは大変ありがたいです。いろんなちまちました作業を自動化できそう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?