GitHub Actionをローカル実行する - actのメモ

2022年4月15日
GitHub Actionをローカル実行する - actのメモ

GitHub Actionを新しく作るとき、GitHub上でテストしては失敗を繰り返してしまう。actを使って、ある程度ローカルで動作検証した上で、GitHubで動かせるようにする。

インストール

実行にはDocker環境が必要。

macOSの場合、以下でインストールできる。

$ brew install act

mac以外の環境で、goが入ってる場合は、以下でもインストールできる。

$ go install github.com/nektos/act@latest

M1 Macで起動する場合は、「--container-architecture linux/amd64」オプションをつける。

また、以下の問題があり、yarnコマンドが実行できないことがある。

https://github.com/nektos/act/issues/280

「-P」または「--platform」でプラットフォームを指定でき、上のissueに記載されている通り、「-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:js-latest」をつけると、yarnコマンドを実行できるようになった。

初回実行後に作成される「~/.actrc」にこれらのオプションを記載しておくと、毎回の実行時にこれらのオプションをつけて実行できる。

--container-architecture linux/amd64
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:js-latest

基本的な実行方法

引数に何もつけないとpushイベントをトリガーとしたワークフローが実行される。

$ act

pull_requestイベントをトリガーとしたい場合は以下のように引数を与える。

$ act pull_request

イベントでトリガーされるワークフロー一覧

イベント名の後に「-l」または「--list」オプションをつける。

$ act pull_request -l
Stage  Job ID      Job name    Workflow name  Workflow file    Events      
0      helloworld  helloworld  Hello World    helloworld.yaml  pull_request

ワークフローを直接指定して実行

「-W」または「--workflows」で指定したワークフローを実行することができる。

$ act -W .github/workflows/helloworld.yaml pull_request

Pull Requestのマージをローカル実行

マージ時にhello worldと表示するテスト用のworkflowを作成する。 (.github/workflows/helloworld.yaml)

name: Hello World

on:
  pull_request:
    types: [closed]

jobs:
  hello-world:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello World!"

これをローカルで実行してみると、イベントのプロパティが存在しないため、エラーが発生する。

$ act -W .github/workflows/helloworld.yaml pull_request
Error:   ❌  Error in if-expression: "if: github.event.pull_request.merged == true" (Unable to dereference 'merged' on non-struct 'invalid')

これを回避するには、以下のようなイベント情報を書き込んだ、jsonファイルを用意し、「-e」または「--eventpath」で指定する。

参考: https://github.com/nektos/act/wiki/Beginner's-guide#using-event-file-to-provide-complete-event-payload

$ cat pull_request.json
{
  "pull_request": {
    "merged": true
  }
}
$ act -W .github/workflows/helloworld.yaml pull_request -e pull_request.json
[Hello World/hello-world] 🚀  Start image=ghcr.io/catthehacker/ubuntu:js-latest
[Hello World/hello-world]   🐳  docker pull image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 username= forcePull=false
[Hello World/hello-world]   🐳  docker create image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Hello World/hello-world]   🐳  docker run image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Hello World/hello-world]   🐳  docker exec cmd=[mkdir -m 0777 -p /var/run/act] user=root workdir=
[Hello World/hello-world] ⭐  Run echo "Hello World!"
[Hello World/hello-world]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/0] user= workdir=
| Hello World!
[Hello World/hello-world]   ✅  Success - echo "Hello World!"

pull_request.jsonのmergedをfalseに書き換えると、実行されないことが確認できる。

$ cat pull_request.json
{
  "pull_request": {
    "merged": false
  }
}
$ act -W .github/workflows/helloworld.yaml pull_request -e pull_request.json

シークレットがある場合

オプション「-s」または「--secret」を使って、シークレットキーを指定できる。複数ある場合は、複数つける。

$ act -s SECRET1=value1 -s SECRET2=value2

実行例(.github/workflows/helloworld.json)

name: Hello World

on:
  push:

jobs:
  hello-world:
    runs-on: ubuntu-latest
    steps:
      - run: echo "secret1=${{ secrets.SECRET1 }}, secret2=${{ secrets.SECRET2 }}"

実行結果

 % act -W .github/workflows/helloworld.yaml -s SECRET1=s1 -s SECRET2=s2
[Hello World/hello-world] 🚀  Start image=ghcr.io/catthehacker/ubuntu:js-latest
[Hello World/hello-world]   🐳  docker pull image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 username= forcePull=false
[Hello World/hello-world]   🐳  docker create image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Hello World/hello-world]   🐳  docker run image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Hello World/hello-world]   🐳  docker exec cmd=[mkdir -m 0777 -p /var/run/act] user=root workdir=
[Hello World/hello-world] ⭐  Run echo "secret1=${{ secrets.SECRET1 }}, secret2=${{ secrets.SECRET2 }}"
[Hello World/hello-world]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/0] user= workdir=
| secret1=***, secret2=***
[Hello World/hello-world]   ✅  Success - echo "secret1=${{ secrets.SECRET1 }}, secret2=${{ secrets.SECRET2 }}"

「--secret-file」でシークレットをファイルに書いて、指定してもよい。

$ cat .secret
SECRET1=s1
SECRET2=s2

$ act -W .github/workflows/helloworld.yaml --secret-file .secret
[Hello World/hello-world] 🚀  Start image=ghcr.io/catthehacker/ubuntu:js-latest
[Hello World/hello-world]   🐳  docker pull image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 username= forcePull=false
[Hello World/hello-world]   🐳  docker create image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Hello World/hello-world]   🐳  docker run image=ghcr.io/catthehacker/ubuntu:js-latest platform=linux/amd64 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Hello World/hello-world]   🐳  docker exec cmd=[mkdir -m 0777 -p /var/run/act] user=root workdir=
[Hello World/hello-world] ⭐  Run echo "secret1=${{ secrets.SECRET1 }}, secret2=${{ secrets.SECRET2 }}"
[Hello World/hello-world]   🐳  docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/0] user= workdir=
| secret1=***, secret2=***
[Hello World/hello-world]   ✅  Success - echo "secret1=${{ secrets.SECRET1 }}, secret2=${{ secrets.SECRET2 }}"