CI/CDサービスとしてのGitHub Actionsが増え、スケジュールで定期的に実行しているリポジトリを多く観測します。そんな中気になる記事があります。
GitHub Actionsで定期実行(cron)のワークフローを組んだユーザーが退職すると、ワークフローは無効化される - shmokmt's blog
無効化されて「定期実行されなくなって困る!」となる前に対処するため、スケジュールトリガーのワークフローの一覧取得する方法を考えてみます。
どのような問題なの?
スケジュール実行のGitHub ActionsはコミットしたユーザーをTrigger Actorとして実行します。このためリポジトリへのユーザーアクセスができなくなると権限がなくなり、ワークフローも実行ユーザーを取れないので無効化されると推測されます。しかもcronの値を変えないと再アクティブされないというのが厄介です。
最後にワークフローの Cron スケジュールにコミットしたユーザーが組織から削除されると、スケジュールされたワークフローは無効になります。 リポジトリへの write アクセス許可を持つユーザーが Cron スケジュールを変更するコミットする場合、スケジュールされたワークフローが再アクティブ化されます。 この状況では、ワークフロー ファイルの変更によってワークフローが再アクティブ化されることはないことに注意してください。cron 値を変更し、この変更をコミットする必要があります。
on: schedule: - cron: "15 4,5 * * *" # <=== Change this value
これを対処するには、スケジュールトリガーで動作するGitHub Actionsワークフローの一覧をサクッと確認できればよさそうです。また、最終コミットユーザーが重要なので一覧にこれも必要です。
ワークフローを取得する方法
GitHub Actionsのワークフロー一覧を取得する方法はいくつかあります。
- クローンして.github/workflowsディレクトリから走査
- GitHub APIを使って取得
- GitHub CLI(gh)コマンドを使って取得
APIを叩くのもいいですが、手元でサクッと実行したいのでGitHub CLI(gh)を使いましょう。GitHub CLIはGitHub Actionsのホストランナーにデフォルトでインストールされているので、手元で動作確認できれば間違いなく動作すると期待できるのもメリットです。1
せっかくなので、それぞれの方法がどういったイメージなのか紹介します。
.github/workflowsディレクトリから走査
GitHub Actionsワークフローは.github/workflowsに配置する必要があります。これを利用して、リポジトリをクローンしてディレクトリを走査すればワークフローを取得できます。
# Bash git clone find .github/workflows -type f -name "*.yaml" # あとはよしなに
# PowerShell git clone Get-ChildItem -Path .github/workflows/*.yaml -File # あとはよしなに
GitHub Actions API
GitHub Actions APIを使うと、ワークフローの実行状況やワークフローの定義を取得できます。これを使ってスケジュールトリガーで動作するGitHub Actionsワークフローの一覧と最終コミットユーザーを取得できます。
// C#ならOctokit + VYamlを使う using Octokit; using System.IO; var dir = Directory.GetCurrentDirectory(); var client = new Octokit.GitHubClient(new ProductHeaderValue("foobar")); var workflows = await client.Actions.Workflows.List("owner", "repo"); foreach (var workflow in workflows.Workflows) { var yaml = File.ReadAllBytes(Path.Combine(dir, workflow.Path)); // あとはよしなに }
GitHub CLI(gh)コマンド
ghコマンドはGitHub CLIの略で、GitHubの操作をCLIから行うためのツールです。このツールを使うと、コマンドから一発でGitHub Actionsのワークフロー一覧を取得したり、GitHub APIを叩くことができます。簡単なことはコマンドで、ちょっとAPIをいじりたくなってもいい感じにできるので便利です。
gh workflow list --json name,path,state # あとはよしなに
スケジュールトリガーで動作するGitHub Actionsワークフローの一覧取得する
先に紹介したGitHub CLI(gh)コマンドを使って、スケジュールトリガーで動作するGitHub Actionsワークフローの一覧を取得する方法を紹介します。ghコマンドの認証)は事前に済ませておいてください。
gh auth login
git clone
しているリポジトリのルートパスで、次のコマンドを実行してください。マークダウンのテーブル形式で、スケジュール実行しているワークフロー名、ファイル名、スケジュール、最終コミットユーザーを表示します。マークダウンで出力されるので、README.mdに貼ってもいいし、社内Wikiにそのまま貼り付けることができます。これで退職時に影響を受けるか事前判断できます。
echo "| Workflow | File Name | Schedule (UTC) | Last Commit by | Last TriggerActor |" echo "| ---- | ---- | ---- | ---- | ---- |" repo=$(gh repo view --json owner,name -q ".owner.login + \"/\" + .name") json=$(gh workflow list --json name,path,state --limit 300) echo "$json" | jq -c '.[] | select(.state == "active") | {name: .name, path: .path}' | sort | while read -r item; do name=$(echo "$item" | jq -r '.name') path=$(echo "$item" | jq -r '.path') url="https://github.com/$repo/actions/workflows/$path" if [[ ! -f "$path" ]]; then continue; fi schedule=$(cat "$path" | yq -o=json | jq -r 'select(.on.schedule != null) | [.on.schedule[].cron] | join("<br/>")') if [[ -z "$schedule" ]]; then continue; fi commiter=$(gh api -X GET "repos/${repo}/commits" -f path="$path" -F per_page=1 | jq -r ".[].committer.login") run_id=$(gh run list --workflow="$path" --event=schedule --limit 1 --json databaseId --jq '.[0].databaseId') if [[ -n "$run_id" ]]; then trigger=$(gh api "repos/${repo}/actions/runs/$run_id" --jq '.triggering_actor.login') else trigger="-" fi echo "| [$name]($url) | $path | $schedule | $commiter | $trigger |" done
例えばguitarrapc/githubactions-labで実行すると次のように表示されます。
実行結果
| Workflow | File Name | Schedule (UTC) | Last Commit by | Last TriggerActor | | ---- | ---- | ---- | ---- | ---- | | [action runner info](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/actionrunner-info.yaml) | .github/workflows/actionrunner-info.yaml | 0 0 * * * | guitarrapc | guitarrapc | | [actionlint](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/actionlint.yaml) | .github/workflows/actionlint.yaml | 0 0 * * * | guitarrapc | guitarrapc | | [auto dump context](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/auto-dump-context.yaml) | .github/workflows/auto-dump-context.yaml | 0 0 * * * | guitarrapc | guitarrapc | | [context github](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/context-github.yaml) | .github/workflows/context-github.yaml | 0 0 * * * | guitarrapc | guitarrapc | | [dotnet lint](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/dotnet-lint.yaml) | .github/workflows/dotnet-lint.yaml | 0 1 * * 1 | guitarrapc | guitarrapc | | [dump context](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/dump-context.yaml) | .github/workflows/dump-context.yaml | 0 0 * * * | guitarrapc | guitarrapc | | [schedule job](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/schedule-job.yaml) | .github/workflows/schedule-job.yaml | 0 0 * * * | guitarrapc | guitarrapc | | [stale](https://github.com/guitarrapc/githubactions-lab/actions/workflows/.github/workflows/stale.yaml) | .github/workflows/stale.yaml | 0 0 * * * | guitarrapc | guitarrapc |
Workflow | File Name | Schedule (UTC) | Last Commit by | Last TriggerActor |
---|---|---|---|---|
action runner info | .github/workflows/actionrunner-info.yaml | 0 0 * * * | guitarrapc | guitarrapc |
actionlint | .github/workflows/actionlint.yaml | 0 0 * * * | guitarrapc | guitarrapc |
auto dump context | .github/workflows/auto-dump-context.yaml | 0 0 * * * | guitarrapc | guitarrapc |
context github | .github/workflows/context-github.yaml | 0 0 * * * | guitarrapc | guitarrapc |
dotnet lint | .github/workflows/dotnet-lint.yaml | 0 1 * * 1 | guitarrapc | guitarrapc |
dump context | .github/workflows/dump-context.yaml | 0 0 * * * | guitarrapc | guitarrapc |
schedule job | .github/workflows/schedule-job.yaml | 0 0 * * * | guitarrapc | guitarrapc |
stale | .github/workflows/stale.yaml | 0 0 * * * | guitarrapc | guitarrapc |
無効化される問題にどう対応するのがいいのか
退職などユーザーをリポジトリ/Organizationから外すとこの問題がおこることを理解しておけば大きな問題にはなりにくいはずです。先のブログにも書いてある通り、退職フロー(コミッターの除外フロー)にGitHub Actionsで定期実行している最終コミッターかチェックすることを追加するだけで未然に防ぐことができます。
利用しているサービスの特性を理解して対処することが重要なので、これを機にGitHub Actionsで定期実行している理由を振り返ってみるのもいいですね。
- ワークフロー一覧を取得して、そのファイルの最終コミットを取得して...と異なる操作が必要になるのでAPIアクセスできるツールを使いたくなる↩