2022年8月31日水曜日

nektos/act で GitHub Actions のワークフローをローカル実行

GitHub Actions で困る、というか面倒なのがワークフローのデバッグをするために都度 push してトライアンドエラーなところじゃないでしょうか?
単純に直列に step 並べるだけならそんなに困らないですが、steps.*.if 使ったり output 使って後続 step で利用しているような場合など、ちょっとしたミスで動かなかったりするのでできれば push するまえに確認できると嬉しいですよね。

あとはイベントによっては PR のときに確認できないことがあるので、マージしてからミスに気づくこともあります。
Release してみたらワークフロー失敗したってのは私も何回もやってます。そういう系は workflow_dispatch に対応しておくのがオススメ。

また、CI と同じことをローカルで実行したいということはよくあると思います。
CI サービスによってはローカル実行サポートしてることもありますが、GitHub Actions 公式には今の所ないので act を使います。

https://github.com/nektos/act

act pull_request とすると、 pull_request で trigger されるジョブが実行されます。
細かい使い方はドキュメント見ていただくとして、ここでは使ってみてわかった注意点などを書いておこうと思います。

最初に結論

最初にもっとも重要な注意点を書いておこうと思います。

act オススメしません!

補足すると「GitHub Actions と同じことをローカルでやりたい」のであれば act 使わずに Dagger とか Makefile とか shellscript とかなんでもいいので GitHub Actions のワークフローと処理を分離しておくことをおすすめします。
Jenkins のときと同じですね。

というわけでそのような目的がある場合は、この記事を読んでいないで処理の分離を始めましょう。

そうではなく、GitHub Actions のワークフローの方をローカルで検証したいときは act を使ってみるのはアリかもしれません。
記事の最後で私なりの利用ケースを考えてみたので興味のある方は最後までお付き合いいただけると幸いです。

ポイント
act の runner は hosted runner と同一ではない

act の runner は hosted runner よりも最小の環境で作られています。
なので hosted runner にはあるけど act の runner にはないものがあったりします。

より hosted runner に近いイメージも用意されていますが、とても大きいイメージなので注意してね、とのことです。
https://github.com/nektos/act#runners

↑にも書かれていますが、 -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04 のように runs-on 指定の名前を置換してあげることができます。
このオプションを利用すると self-hosted runner 使っている場合の対応が簡単です。

act のときは〜をする・〜をしない

https://github.com/nektos/act#skipping-steps

やはりどうしても環境が異なっていたり、act でできないこともあり、act でそのまんまワークフローを実行するのは無理だったりします。
なので、そういう場合は if: ${{ !env.ACT }} のように ACT 環境変数で分岐させると良いです。

コンテキストの内容がすべて定義されない

act pull_request としても github context にはほとんどの情報が設定されません。
特に event は空っぽなので、 github.event.pull_request.head.sha とかを workflow 中に参照していると失敗します。
イベント情報などある状態で act 実行したい場合は、以下に書かれてるようにイベントの json ファイルをオプションで指定してください。

https://github.com/nektos/act#events

GITHUB_TOKEN は自動発行されない

https://github.com/nektos/act#github_token

はい。当たり前ですが自動では発行されないです。必要な場合は -s オプション使ってシークレットをセットしてください。

サービスコンテナは未対応

services の内容は無視されます。

設定ファイル

act で実行するにはそれなりにオプションで動くようにカスタマイズが必要だと思います。
お試し程度なら良いかもしれませんが、開発ワークフローで act を常用するようであれば、それらの設定をファイル(.actrc)に書き出しておくと良いと思います。
こうしておけば誰でも実行できるようになるはず。

https://github.com/nektos/act#configuration

ドキュメントに書かれてないこと
本家とは微妙に挙動が異なることがある

これはほんの一例ですが、公式ツールではないのでどうしても本家と違う挙動をすることがあります。
※下記の例は旧バージョンの話なのですでに修正されてます。

setup-ruby が ImageOS が想定外のために失敗する

act の提供してる image の ImageOS 環境変数が small ってなってて ruby/setup-ruby が失敗しました。どうやら setup-ruby は ImageOS 環境変数で実行環境を判断してるっぽいです。
step.*.env で ImageOS 環境変数を与えて上げること回避することはできますが、act 向けの変数になってしまうので微妙ではあります。

こんなときに便利かも
シークレットを設定する権限をもっていないとき

そういうケースってあんまりない気もしますが、なくもないのかなと思います。
シークレットが必要なワークフローの PR をしたけど、そのリポジトリのシークレットを設定する権限がない。 fork したとか、リーダーにしか権限ないとか?

シークレット追加してもらって検証というのもしづらいかなと思いますので、act -s で想定シークレットを渡してデバッグしておくといいかもしれません。

レアなイベントをトリガーにしている場合

pull_request イベントなら PR 出せば済む話ですが、workflow_dispatch とか release とか branch_protection_rule とかのイベントトリガーのワークフローをデバッグするのってめんどくさいです。
というのもイベントによってはデフォルトブランチにワークフローがあることが条件のものがあるからです。
ワークフローをトリガーするイベント - GitHub Docs

こういうやつ。

ですが、act 使えばこれらのイベントでトリガーされたときのデバッグが簡単になると思います。(env.ACT をみて dryrun な状態にしておくこと!)
最後に

act どうなんでしょうね?
本当に必要なケースってあんまりないような気もしてしまいます。

GitHub Actions のワークフローで書くことって大概が pull_request イベントだと思うし、それなら実際に動かしたらいいってなるし。

時間かかるからローカルがいい、処理をデバッグしたいって話だとワークフローとは別の話な気がするので act でなくても解決できるはず。

デフォルトブランチに入れなきゃ動作確認できないイベントはたしかに act があると便利そうではありますが・・果たして本当に楽になるのだろうか?


「デフォルトブランチに入れなきゃ確認できない」って部分は GitHub 公式でなんらか解決策を出してくれると嬉しいですね。

以上。

2022年8月15日月曜日

Git で今のブランチ名を取得するコマンドの違いを確認してみた

Git で今いるブランチ名を確認する方法はいくつかあります。
そういう情報は検索すればいっぱい出てきますが、今回は以下の 4 つを比較しました。
git コマンドの出力を加工する方法も検索すると出てきますが、加工なんかしなくても取れるのでそういう系は今回は除外。

* git branch --show-current
* git rev-parse --abbrev-ref HEAD
* git symbolic-ref --short HEAD
* git name-rev --name-only HEAD

とりあえず、試すとこうです。
ただ単にコマンドを実行しただけではどれも同じに見えます。
以下で細かく確認をしていきます。
(本記事執筆時の筆者環境の git version は 2.37.1)
内部処理
まずは、GIT_TRACE で内部的に何をしているのかの違いを確認してみます。
GIT_TRACE は git コマンドのデバッグ用変数です。git コマンドが遅いとか挙動調べるのに使えるので覚えておくと便利です。
こちらもどれも built-in の呼び出しのみで差分なし。
ちなみにこれだと GIT_TRACE の挙動が分かりづらいので git fetch のトレースを貼っておきます。(実は git fetch が中どんな処理をしているのかが伺えます)
パフォーマンス
次は、パフォーマンスの比較をします。GIT_TRACE_PERFORMANCE で内部処理の各処理のパフォーマンスを出力できますが、今回はコマンド自体のパフォーマンスを比較したいのでベンチマーク用のツールを使用します。
今回は hyperfine を使いました。また計測環境は alpine コンテナ内にコピーしたリポジトリで行いました。(コンテナ内の git version は 2.36.2)
結果はこちら
name-rev を使ったものだけ少し遅い結果となりました。他は大体同じくらい。
バージョン
最後に各コマンドが使える Git のバージョンを確認しておきます。
比較的新しいサブコマンドやオプションだと古い環境では使えない可能性もあるので、選択する際の参考になると思います。

commandversion
git branch --show-current2.20
git rev-parse --abbrev-ref HEAD1.6.3
git symbolic-ref --short HEAD1.7.10
git name-rev --name-only HEAD1.5.3

--show-current が比較的新しめの機能でした。
他は今の最新バージョンが 2.37 なのを考えると誤差と言えると思います。
Detached
もう1個だけ確認して置きたいことがありました。 CI などでは Detached 状態で checkout されることがあるので、その場合の挙動を確認しておきましょう。


ここで大きな違いがでてきましたね。
表にまとめるとこうです。
command出力exit code
git branch --show-current0
git rev-parse --abbrev-ref HEADHEAD0
git symbolic-ref --short HEADfatal: ref HEAD is not a symbolic ref128
git name-rev --name-only HEADブランチ名0

symbolic-ref だとコマンドが失敗します。他はそれぞれ出力結果が違いますが、ブランチ名が取れるのは name-rev のみでした。name-rev がちょっと他より遅い理由がなんとなくわかりますね。
CI とかでも fetch はされてるはずなので name-rev ならブランチ名を取得できそうです。
(pull_request の場合は merge ブランチになってるので注意)

また、例えば1つ前のコミットを指してた場合は以下のように ~ 使った表現で出力される模様。
あと、Detached 状態からコミットしてどこのブランチにもいないような場合は「undefined」が出力されます。
まとめ
Detached 状態のときにどうするかで使うコマンドを選ぶと良さそうです。
ブランチ名を必ず取りたいなら name-rev を使うことになります。
Detached が稀で速度を気にするのであれば↓のように symbolic-ref を最初に試みるのがいいかもしれません。

git symbolic-ref --short HEAD 2>/dev/null || git name-rev --name-only HEAD

なんらかの文字列が入っていればよいのであれば rev-parse 、逆に空文字が良ければ branch --show-current ですね。--show-current が git version 2.20 以降でしか使えないのでそこは注意してください。(あとは --show-current の Detached 状態の挙動はバグの可能性もあるので、今後挙動が変わるかもしれません)

調べ始めたときは、対して違いはありませんでしたーってオチを予想してたのですが、割と調べてみてよかったなと思いました。
では。


2022年8月9日火曜日

GitHub Actions の Composite Action で post 処理を実現する方法

GitHub Actions での処理の共有方法として action がありますが、現在この action は javascript と docker そして composite の  3  つの方法で作成することができます。
筆者は今から action 作るなら composite をおすすめします。
composite action は登場した当初は runs.steps[*].if や runs.steps[*].uses が使えなかったので、ちょっと力不足でしたが最新のバージョンであればこれらが使えるようになっており、action を作る上で困ることはほとんどなくなったと思います。

じゃあ、タイトルはなんなんだということになりますが、ちょっと凝ったことしようと思うと(他ではできるけど) composite ではできないこともあります。

それがタイトルに書いた post 処理です。
右のスクリーンショットは action に書ける Metadata syntax なのですが、composite だけ post に対応してません。(詳細はリンク先へ)


でも、大丈夫です。というのがこの記事。
いずれ公式で composite action にも post が対応されると思いますが、今の状態でも post 処理できます。


その前に。
post 処理とはどんなものかと言うと、ジョブのステップが全部終わった後に行う処理のことです。みなさんがよく使うであろう actions/checkout でも post 処理が行われています。

こんな感じ。
基本的には action で設定したものをもとに戻したり、docker コンテナを停止したりするのに利用されています。


では本題です。
composite action で post 処理を行うには・・・
post 処理ができる action を uses で呼び出す」ことで可能です!

つまり、「composite action は uses で他の action を呼び出せるので、javascript/docker action を呼べば良い」のです。

リポジトリが別になっちゃう?
いえ、大丈夫。 uses は相対パスで action を呼び出せます。

つまりこういうことです。

composite action の repository のサブディレクトリに javascript/docker action を配置して、それを composite action から呼び出し、呼び出した action の post 処理を composite action の post 処理の代わりにします。
「uses: "./resources/post-action"」 がその部分で、この action のメイン処理は空で、post 処理で起動したコンテナの終了をしています。

実際にこの書き方をした action のリポジトリはこちら。
https://github.com/srz-zumix/setup-service-jenkins


composite ではない action を使うことになるので、それはそれで問題があったりしますがネストされた action なので実はそのへんもなんとかなっちゃうかもしれないなと思ってます。(javascript action の default node version 問題とか)
(docker action は実行できない os があるので、この目的ではあまりオススメしません)


ざつにざーっと書きましたが、今回は以上となります。では。