GitHub Actionsでworkflowを組んでいると、外部ツールと組合わせるために、JSONを組み立てなければならないことがある。Actionに直接渡すだけだとJSONの出る幕はあまりないが、外部サービスになんかするといった時にはすぐJSONが必要になりがち。
二重の苦労
従来は頑張ってJSON="{\"foo\": \"$BAR\"}"
みたいに素朴に文字列を組み立てていたけれど、これがすんなりいく訳もなく、大抵3回は変数の展開に失敗する。bashでは変数展開をしたければdouble quotationでくくる必要があるけれど、JSONのオブジェクト表記におけるフィールド名もdouble quotationでくくる必要があるので、ネスト地獄が発生する。また、これを渡すときも大変で、ファイルで渡したければ<(cat $JSON)
と書いてfile substitutionを使う必要がある。
- 二重の苦労
- 組み立てるのに苦労
- 変数展開が大変
- double quotationがネストする
- 渡すときに苦労
- ファイルで渡したいときどうする
- ちなみにnode.jsはfile substitutionに非対応なので一旦ファイルに置く必要がある
- 組み立てるのに苦労
ちなみにcat <<EOF > data.json
などのようにして一旦ファイルに置くとnode.jsのようなfile substitutionに非対応のコマンドなどでも使えるけれど、けっこう不毛な感じになってしまう。
渡すときの苦労はファイルに置くことで(なんか不毛だけど)大抵の場合はなんとかなるのでこれで納得している。
actions/github-script
で組み立てを簡単にする
JSONを組み立てるときは、色々試した結果、actions/github-script@v6
でオブジェクトを組み立てて、JSON.stringify()
を用いて文字列をゲットし、それをそのステップのoutputとして出力すると便利。
例えば、次に示すのはCIの実行時間をMackerelにポストするといった例。
- uses: actions/github-script@v6 id: create-body with: result-encoding: string // 文字列で受け取れる script: | const time = Date.now() / 1000 // ${{}} による変数展開も可能 const start = ${{ needs.recordstarttime.outputs.ci_start_epoch }} const end = ${{ needs.recordendtime.outputs.ci_end_epoch }} const body = [ { name: 'ci.ciyaml.elapsed_sec.sec', time: time, value: end - start } ] return JSON.stringify(body) - name: Post Mackerel uses: yutailang0119/action-mackerel-api@v3 with: api-key: ${{ secrets.MACKEREL_API_KEY }} http-method: POST path: services/うんたらかんたら/tsdb body: ${{ steps.create-body.outputs.result }}
苦行から解放された。便利。