はじめに
こんにちは、最近毎朝ブルーベリースムージーを作っている、2023年新卒入社のこうざい(@yukhy_BE)です。おすすめのスムージーレシピを募集しています!
主にSchooのプロダクトのインフラエンジニアとして働いています。
Schooでは、CI/CDの一部にGitHub Actionsを利用しています。最近、GitHub Actions内のif節で少し厄介な問題に遭遇しました。この記事では、その経験から学んだことと、GitHub Actionsのif節を正しく使用するためのポイントを共有したいと思います。
GitHub Actionsのif節
GitHub Actionsは、GitHubのリポジトリでソフトウェア開発ワークフローを自動化するためのCI/CDプラットフォームです。その中でif節は、ワークフロー内の特定のステップやジョブが実行されるかどうかを条件に基づいて制御するための機能です。
基本的な使用例は以下の通りです。
steps:
- name: 条件付きステップ
if: 条件
run: echo "条件が真の場合に実行される"
遭遇した問題
既存のActionsファイルに以下のようなif条件がありました。
if: "(env.environment == 'hoge') && (github.ref_name == 'hoge')"
これに新しい条件を追加しようとして、次のように書きました。
if: "((env.environment == 'hoge') && (github.ref_name == 'hoge')) || ((${{ env.environment }} == 'fuga') && (github.ref_name == 'fuga'))"
ここで問題が発生しました。OR条件の2つ目の部分で、env.environment
を${{}}
で囲んでいます。
if節の条件式の一部を${{}}
で囲むと、条件式の内容に関わらず、そのif文は常に真を返してしまいます。
GitHub Actionsにおける${{}}
の扱い
GitHub Actionsでは、${{}}
は式を評価するために使用されます。つまり、${{}}
で囲まれた部分は文字列としてではなく、式として解釈されます。
例えば、
env:
sample1: endsWith("Schoo", "o")
sample2: ${{ endsWith("Schoo", "o") }}
この場合、sample1
にはendsWith("Schoo", "o")
という文字列が代入されますが、sample2
にはBool値のtrue
が代入されます。
endsWith()
は文字列が指定した文字列で終わる場合にtrue
を返す組み込み関数です。
if節の正しい書き方
if節の条件式を書く場合、基本的に${{}}
の記載が必要です。ただし、一部の条件下では省略が可能です。
GitHubのドキュメントによると、
if 条件の中で式を使う際には、任意で式構文
${{ }}
を省略できます。これは、GitHub Actions が if 条件を式として自動的に評価するためです。ただし、この例外はどこでも適用されるわけではありません。
https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif
つまり、以下のステップは全て同じ挙動になります。
steps:
- name: Test1
if: ${{ false }}
run: echo "Test1 Pass"
- name: Test2
if: "${{ false }}"
run: echo "Test2 Pass"
- name: Test3
if: false
run: echo "Test3 Pass"
いずれの場合も、if節は偽と評価されるため、runの項目は実行されません。
特に注意が必要なのは、否定演算子!
を使う場合です。
!
は YAML 形式で予約された表記であるため、必ず${{ }}
構文の式を使用するか、式が!
で始まる場合は''
、""
、または()
でエスケープする必要があります。
https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif
以下のTest4、5はTest1~3と同様に動作しますが、Test6はActionsに含まれているだけで構文エラーになります。
steps:
- name: Test4
if: ${{ !true }}
run: echo "Test4 Pass"
- name: Test5
if: "!true"
run: echo "Test5 Pass"
- name: Test6
if: !true
run: echo "Test6 Pass"
検証
以下のActionsを用意し、実行しました。
steps:
- name: Test7 # 式全体をエスケープ、一部を${{}}でネスト
if: "${{ false }} == true"
run: echo "Test7 Pass"
- name: Test8 # 一部を${{}}でネスト
if: ${{ false }} == true
run: echo "Test8 Pass"
- name: Test9 # 式全体を${{}}でネスト
if: ${{ false == true }}
run: echo "Test9 Pass"
- name: Test10 # 式全体をエスケープ、式の一部を${{}}でネスト、式に否定演算子
if: "${{ !true }} == true"
run: echo "Test10 Pass"
- name: Test11 # 式の一部を${{}}でネスト、式に否定演算子
if: ${{ !true }} == true
run: echo "Test11 Pass"
- name: Test12 # 式全体を${{}}でネスト、式に否定演算子
if: ${{ !true == true }}
run: echo "Test12 Pass"
実行結果
step名 | runが実行されるかどうか |
---|---|
Test7 | ◯ |
Test8 | ◯ |
Test9 | × |
Test10 | ◯ |
Test11 | ◯ |
Test12 | × |
わかったこと
- 条件式の一部だけを
${{}}
で囲むと、予期せぬ結果になります。- 例:Test7, 8, 10, 11
- 条件式全体を
${{}}
で囲むと、正しく評価されます。- 例:Test9, 12
結論
GitHub Actionsのif節を使う際は、以下のルールを守ることで、人間にとって理解しやすく、かつ予期せぬ結果を避けることができます。
条件式全体を必ず${{}}
で囲む。
このルールを守ることで、以下のメリットがあります。
- 否定演算子の有無に関わらず、式全体を
${{}}
で囲むことでネストし忘れがなくなる。 - 式の一部を
${{}}
で囲んでしまうミスがなくなる。
最後に
今回の検証に使用したコードは、以下のリポジトリで公開しています。
GitHub Actionsをより安全に使用する際の参考になれば幸いです。
参考資料
We're Hiring!
Schooでは一緒に働く仲間を募集しています!