GithubActionsでrunにifを書かない方法で、ステップごとに実行判断をするロジックの書き方・使い方(実例あり)


公式リファレンス

Github Docs

本稿ではstepsコンテキストを使っています

実行結果によって処理をする・しないを分岐する

先に結論を言うと、runでif文を書けるので、shellを書くのと同じ感覚で書けばやりたい事は実現できます。
ロギングがしたいならechoでログを出すか、ログファイルも更新してpushすればGithubActionsを探しに行かなくてよくなります。

が、ここではGithubActionsの流儀に則って制御を考えます。
また、runで実行すると、途中でエラーになった時にどこで失敗したのか特定するのが難しくなるので、運用を考えるならなるべく切り分けたいからです。

やり方は、if: 条件をいれるだけです。
問題は、条件に何を書くかです。

条件分岐で使えるもの

色々なものが使えますが、ここでやりたいのは「前回実行したステップの結果を受け取りたい」というものです。
たとえば、前回実行した結果が全て完了したとか、特定の処理をしたかどうか。
解決アプローチとして、3つのパターンがあります。

  1. そもそもstepをまとめてしまう
  2. 前stepの結果を判定するロジックをrunで書く
  3. 【今回採用】stepを実行する前にif条件を入れる

大事な事なのでもう一度繰り返します。
うまく行かない場合は、今回採用する方法にこだわる必要はありません。
実務で採用する場合にまで、後でできる事を今こだわるとハマって進捗率を下げてしまう、なんて事も考えられます。
状況に合わせて活用してください。

ソース例と解説

今回の制御で見る公式ドキュメントの位置

Thanks:

if: steps.(ステップのid).outcome == "success"が最もわかりやすいです。
以下のような使い方ができます。

成功.yml
- name: 確実に成功する処理
  id: test1
  continue-on-error: true  # 処理が失敗しても継続させる
  run: echo "成功"

- name: 実行される事を確認
  if: steps.test1.outcome == "success"
  run: echo "処理される"
失敗.yml
- name: 確実に失敗する処理
  id: test2
  continue-on-error: true  # 処理が失敗しても継続させる
  run: exit 1

- name: 実行される事を確認
  if: steps.test2.outcome == "success"
  run: echo "スキップ"
失敗した時でも処理したい.yml
- name: 確実に失敗する処理
  id: test2
  continue-on-error: true  # 処理が失敗しても継続させる
  run: exit 1

- name: 実行される事を確認
  if: steps.test2.outcome == "success"
  run: echo "スキップ"

適当なリポジトリを作って、.github/workflows以下に置いて検証してみてください。

実践例

この方法は、Ubuntu20.04にデフォルトで入っているpythonのバージョンが古くて動かないスクリプトがある場合、処理をしないための例です。

参考

Ubuntu22.04 LTSではどうなるか気になるところです。

ソースコード

実践例.yml
runs-on: ubuntu-latest
steps:
  # 以下、ステップ
  - name: PosixPath.with_stemが使えないので、スクリプトを実行できるか判断する
    id: check_python
    continue-on-error: true
    run: |
      # python3のマイナーバージョンが9未満か判定
      if [ "$(python3 --version | cut -d"." -f2)" -lt "${minor}" ]
      then
        exit 1
      fi

  - name: スクリプトを実行
    if: steps.check_python.outcome == 'success'
    run: python3 (実行スクリプト)

check_pythonでexit 1を実行しなければスクリプトを実行します。

応用例

この方法は、Ubuntu20.04にデフォルトで入っているpythonのバージョンが古くて動かないスクリプトを動かすための例です。
もっといい方法があるので、以下はあくまで実践例です。

今回のGAとは関係ないですが、WSLに入れる時に参考になりました。

ソースコード

応用例.yml
# 実行環境
runs-on: ubuntu-latest
steps:
  # 以下、ステップ
  - name: 判定ロジックは同じ。上記では`exit 1`で終わらせたが、こちらはインストールする
    id: check_python
    env:
      minor: 9
    run: |
      pyc=$(python3)

      # python3のマイナーバージョンが9未満の時だけ実施する
      if [ "$(python3 --version | cut -d"." -f2)" -lt "${minor}" ]
      then
        sudo apt update && sudo apt install software-properties-common
        sudo add-apt-repository ppa:deadsnakes/ppa
        sudo apt install python3.${minor}
        pyc=python3.${minor}
        sudo apt update && sudo apt install python3.${minor}-distutils
      fi
      
      echo "::set-output name=pyc::${pyc}"

  - name: pythonスクリプト実行
    env:
      # id: check_pythonの出力からpycを取得してenvに入れる
      pyc: ${{ steps.check_python.outputs.pyc }}
    run: |
      ${pyc} -m pip install --upgrade pip
      ${pyc} -m pip install pathlib
      ${pyc} (実行スクリプト)

pycにはpython3python3.9か、どちらかが入っています。
重要なのは、実行するステップではpycに何が入っているか気にしなくても良い事です。
より厳密なチェックをするなら、which "${pyc}"のパスが有効である事を検証するのも良いかもしれません。

良い方法

環境構築系は自分で頑張るより、usesを探した方が楽です。
pythonのバージョンを指定する方法はあります。

良い方法.yml
steps:
 - uses: actions/setup-python@v3
   with:
     python-version: '3.9'

  - name: pythonスクリプト実行
    run: |
      python -m pip install --upgrade pip
      python -m pip install pathlib
      python (実行スクリプト)

あれだけ長いコードが、こんなに圧縮できるのでusesは非常に優秀です。
usesで使えるものは以下の通り

今回使ったもの

使えるもの

自分でusesを作れます

usesを見ると、Githubユーザー/リポジトリの関係であることに気付きます。
これは具体例を挙げるのが難しい(私が具体例を知らないため)ですが、オリジナルの環境構築ができるのは便利です。
メンテナンスを考えると決して楽な選択ではありませんが、どうしてもという場合には使えそうです。

使用例

私がやりたかったこと

pushされるたびにCHANGELOG.mdを自動で生成してPRを出すスクリプトを書きたかったんですが、

  • github-changelogs(node)
  • github-changelog-generator(ruby)

を使って自動生成する仕組みを作ろうとしていました。
わざわざ自前で環境構築をやってコマンド打ち込んで…とやると大変なので、CIでなんとかならないかと思っていたところでした。
いきなり難しい事をやると手がつかないので、手始めに上記のように簡単なCIを色々作ってみてGithubActionsのナレッジも蓄積してきたので、そろそろ挑戦しようと思っていました。
実務ではなく趣味でやっているCIだと、CIの勉強だけだと運用経験が積めず、なかなか学びが進まないので非常に時間がかかりましたが、ようやくここまで来れました。

本稿の内容を踏まえて、プログラマーを目指す方へ

ぶっちゃけ本題です。
冗長になりすぎたので、別記事にします。