GitHub Actionsにおけるジョブ間のデータの受け渡しに関するすごく細かい疑問

2024/07/05に公開
3

事前情報

恐縮ですが私はCI/CDの素人です。データサイエンスに興味があり、そちらに軸足を置いているのですが、最近は「どれだけ高尚な技術であってもマス層が使ってくれないと意味ないんじゃない?」という考えに至り、そのインタフェースであるアプリに興味を持っています。そんな背景から個人開発を始めた関係でCI/CDを勉強し始めました。
ちなみに勉強には以下の書籍を使っています。
https://gihyo.jp/book/2024/978-4-297-14173-8
こちらの書籍は特定の言語に縛られないCI/CDの考え方そのものや、言語に関係ないGitHub Actionsの共通構文を扱っており、ある意味抽象化された内容になっています。特定の言語にしぼったネイティブな内容ではないので、どんな言語を使う人にも役立つように設計されていてとてもわかりやすく、おすすめです。

本題

上述した書籍を読んでいる途中なのですが、「5. 複数ジョブの実行制御」において大きく分けて2つ疑問が生じました。ただとても細かいことで、「そんなこと気にしてどうすんの?」って内容ですので、誰かの役に立つ内容でないことは事前にご了承ください。

  1. <key>と<output-name>は一致させる必要があるのか?
  2. <key>=<value>という内容をGITHUB_OUTPUTに格納したとき、なぜ<key>がキーとして機能するのか?

1. <key>と<output-name>は一致させる必要があるのか?

以下のコードにおいて"result=Hello"の左辺の変数名とoutputsのキー名はともにresultで一致しています。

# 書籍p.105 コード5.9より抜粋
steps:
- id: generate
  run: echo "result=Hello" >> "${GITHUB_OUTPUT}"
outputs:
  result: ${{ steps.generate.outputs.result }}

しかしこの構文を一般化すると以下のようになるそうです。

# 書籍p.105より抜粋
steps:
- id: <step-id>
  run: echo "<key>=<value>" >> "${GITHUB_OUTPUT}"
outputs:
  <output-name>: ${{ steps.<step-id>.outputs.<key> }}

もしGITHUB_OUTPUTへ格納される式の左辺と<output-name>が一致する必要があるなら以下のように書くんじゃないかな?と思いました。

# <output-name>-><key>に変更
steps:
- id: <step-id>
  run: echo "<key>=<value>" >> "${GITHUB_OUTPUT}"
outputs:
  <key>: ${{ steps.<step-id>.outputs.<key> }}

という細かすぎるポイントが気になってしまったので調べた結果、結論「keyとoutput-nameは一致しなくてもいいけど、敢えて一致させないメリットがないので揃える」ということらしいです。

具体例を挙げると以下のようにしてもエラーにはなりませんが、my_resultの中身はresultなんですが、my_resultにアクセスするときは${{ steps.<step-id>.outputs.my_result }}を使用することになり、ちぐはぐな状態になります。たぶんエラーやバグの発生につながってしまうんじゃないかと思います。

# 一致させない場合
steps:
- id: generate
  run: echo "result=Hello" >> "${GITHUB_OUTPUT}"
outputs:
  my_result: ${{ steps.generate.outputs.result }}

そう考えるとそもそも仕様として一致させるようにしておいた方がよかったのでは?と思ってしまったので、意図的に一致させないユースケースをもしご存じの方がいらっしゃいましたらぜひ教えてください。重ねて申し上げますが、筆者はCI/CD初心者ですので悪しからず。

2. <key>=<value>という内容をGITHUB_OUTPUTに格納したとき、なぜ<key>がキーとして機能するのか?

上述した内容につっかかっていたら、連鎖的にさらに疑問が生じてしまいました。echo "result=Hello" >> "${GITHUB_OUTPUT}"の部分です。
私のイメージでは"${GITHUB_OUTPUT}""result=Hello"という式がまるごと格納されているイメージです。しかし、後段の処理で「<output-name>: ${{ steps.<step-id>.outputs.<key> }}でしれっとに<key>と<value>の紐づけがなされた上にキーとして機能している」ことが引っ掛かりました。引っ掛かったポイントは以下の2点です。

  • "${GITHUB_OUTPUT}"に格納されているのは"result=Hello"という文字列でしかないこと
  • pythonでいうところの辞書のような形でresult:Helloならまだしも代入式でなぜkeyとvalueの関係性が構築されるのか?

結論を雑に言いますと「GitHub Actionsがいい感じに処理してくれているから」でした。
GitHub ACtionsはGITHUB_OUTPUTに書き込まれた内容(ここではresult=Hello)を解析し、キーと値のペアとして解釈してくれるそうです。

総括

  • GitHub Actionsはすごい便利
    • 自動でいい感じに処理してくれる機能がある
  • まだ書籍を読み切ってないし、活用はできていないのでそのすごさの一端しか感じられていない
    • 「GitHub CI/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用」おすすめです!
  • 初心者なので当たり前のことに驚いているかもしれませんが、ご容赦ください

Discussion

yorifujiyorifuji

同じ書籍を購入した者です。

全体的に「3.9 step間でのデータの共有(p.54)」と「Job間のデータの共有」の話が混ざっているようです。両者は別物なので違いを意識すると理解が早いかと思います。

そのうえで、こちらに関しては

<key>と<output-name>は一致させる必要があるのか?

一致させるかどうかは状況次第です。例えばGitHubのドキュメントの例では一致していません。

書籍のコード(p.105 コード5.9)の例に話を戻すと私の方では以下のように理解しています。

  • steps.generate.outputs.result というデータをstep間で共有している
  • output の仕組みを使ってJob間のデータ共有として result というkey名でsteps.generate.outputs.result のデータを共有している
    • この時のkey名は状況に合わせて適切な名前を割り当てれば良い(stepのkey名と一致する場合もあれば、そうでない場合もある)

意図的に一致させないユースケース

outputのkey名はJob内でユニークであるため以下のようなケースでは一致させることができません

step1及びstep2の両方で共通のresultというkey名を使ってStep間のデータ共有を行い、それぞれの値をJob間のデータの共有を使って共有する場合、outputのkey名はユニークである必要があるのでどちらかはresult以外のkey名を指定する必要があります

修行僧修行僧

はじめまして、このような記事にコメントくださってありがとうございます!
まずは私のような初心者が書評などと大それたことをする意図は薄いのですが、もし本記事で気分を害されたようでしたら申し訳ございませんでした。先んじてお詫び申し上げます。

その上で内容についてです。
私の状況を総括しますと「GitHub Actionsを初めて使うので、慣例のようなものがあるのかもわからずおっかなびっくりで読んでいる」ということです。つまり、「<key>=<value>における<key><output-name>は一致することを強制していない意図を感じるものの、なぜ一致させないのかわからない」という感じです。単純に自分がCI/CDを使った経験値が少ないので、机上では一致させた方がわかりやすいように第一印象では感じてしまいまして、「どういう風に使いこなすのか(key名を一致させない)」のイメージがついていないのがすべての元凶です。
とりあえず一致させるのが通例!みたいな断定調の部分は修正致しますね。

次にstep間のデータ共有Job間のデータ共有についてです。
すごくわかりやすくコメントいただきありがとうございます。たぶんですが、イメージはつかめたと思います。
p.105 コード5.9をベースに考えますと、beforeJOBとafterJOBの間でsteps.generate.outputs.resultを通じて、データのやりとりがされているのはわかります。
私の疑問としましては単純に、「書籍では全てresultという名前にそろえているけれど、データ共有に使うキー名は別に以下のように書いても問題ないのだろうか?」という一点でした。

name: Share job data
on: push
jobs:
  before:
    runs-on: ubuntu-latest
    steps:
      - id: generate                                   # ステップのID
       run: echo "result=Hello" >> "${GITHUB_OUTPUT}" # ステップレベルの出力値
    outputs:
-      result: ${{ steps.generate.outputs.result }}     # ジョブレベルの出力値
+      foo: ${{ steps.generate.outputs.result }}     # ジョブレベルの出力値
  after:
    runs-on: ubuntu-latest
    needs: [before]                                    # 依存するジョブIDの指定
    steps:
      - env:
-          RESULT: ${{ needs.before.outputs.result }}   # 依存ジョブの出力値を参照
+          RESULT: ${{ needs.before.outputs.foo }}   # 依存ジョブの出力値を参照
        run: echo "${RESULT}"
  • steps.generate.outputs.resultresultecho "result=Hello" >> "${GITHUB_OUTPUT}""result=Hello" の左辺と紐づいている
  • 一方でneeds.before.outputs.resultresultoutputsで設定したキーと紐づいている
    • outputsで設定するキーをresultfooとすればそれに連動してneeds.before.outputs.resultneeds.before.outputs.fooになる

要は書籍のresultの統一が必須なのかどうかを知りたかった感じです。
仮に必須じゃない場合、上記のようなすごく意地悪で汚いコードで書いた方が個人的には処理を追いやすかったなあという感想文でした。
詳しい方に対して失礼に値するかもしれないと思いつつ、ビギナーも情報発信しようという気概を見せるべく疑問点を書きましたが、反応をいただけるとは思っていなかったのでとてもありがたいです!

yorifujiyorifuji

ご返信ありがとうございます!
こちらこそ、私のコメントで不安を感じられたのであれば申し訳ありません、そのような意図は全くありませんでした🙇‍♂️
素晴らしい記事をありがとうございます。

コメントに記載されたコードを拝見した限りでは疑問点は解消されているように思えましたので、私のコメントが少しでもお役に立てたようでしたら幸いです🙏