🦀

【Github Actions】定期的に変更コミットをチェックしてアクション実行判断する

2024/03/02に公開

はじめに

この記事では、GitHub Actions を利用してリポジトリの変更有無を確認して何らかの処理をトリガーさせる方法について説明します。

前回 Rust プロジェクトにて自動でクレートのバージョンアップをするワークフローを紹介しましたが、何らかの条件によりこういった処理をトリガーしたいケースがあります。

https://zenn.dev/linnefromice/articles/gh-actions-auto-crate-versionup

今回は定期的にリポジトリに変更があったかどうかをチェックして処理をトリガーさせるような仕組みを構築してみます。

完成状態

構築するワークフローの実装はこちらです。
アップデートのためのチェックを実施し、そのチェック結果を以て、実処理のためのワークフロー呼び出しに使用しています。
今回実処理には、前回構築した自動でクレートバージョンアップを行うためのワークフロー (workflow_dispatch) を使用します。

name: Check for Version up
on:
  schedule:
    - cron: '0 18 * * *'
env:
  INTERVEL_SEC: 86400 # same to cron interval
  TARGET_PATH: src # check for updates in these files/folders
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the source code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Check for updates
        id: check
        run: |
          LATEST_COMMIT_UNIX_TIME=$((git log -1 --format=%ct -- ${{ env.TARGET_PATH }}))
          CUREENT_TIME=$(date +%s)
          DIFF=$((CUREENT_TIME - LATEST_COMMIT_UNIX_TIME))
          IS_EXIST_NEW_COMMIT=$(($CUREENT_TIME - ${{ env.INTERVEL_SEC }} - $LATEST_COMMIT_UNIX_TIME < 0))
          echo "is_exist_new_commit=$IS_EXIST_NEW_COMMIT" >> $GITHUB_OUTPUT
      - name: if check flag is true, request to update
        if: ${{ steps.check.outputs.is_exist_new_commit != '0' }}
        uses: convictional/trigger-workflow-and-wait@v1.6.5
        with:
          owner: (owner_name)
          repo: (repo_name)
          workflow_file_name: (workflow_filename)
          github_token: ${{ secrets.GITHUB_TOKEN }}

Step By Step

前のセクションで完成したワークフローを展開しましたが、ここでは必要とするパーツごとに解説を加えながら完成系を作る流れを紹介していきます。

リポジトリチェックアウト

対象にするリポジトリのコミット情報を使用する必要があるので actions/checkout@vN を使用する際に fetch-depth: 0 を指定します。(コストはかかるので、多少深さをコントロールしても良いでしょう。)

Set fetch-depth: 0 to fetch all history for all branches and tags.

Source: https://github.com/actions/checkout

name: Check for Version up
on: # todo
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the source code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

期間内にコミットがあったかチェック

実行時に現在までの指定した時間範囲においてコミットがあったかどうかをチェックします。現在時刻は date で取得し、最新のコミットは git log を利用して取得しましょう。時間データ自体を比較する際にはフォーマットやタイムゾーンを気にしたり基準を揃えることを意識する必要がありますが、今回は UNIX時間 を使用します。

UNIX時刻はシステム依存だが、大多数のシステムでは、協定世界時 (UTC) の時刻に基づき、1970年1月1日午前0時0分0秒 (UNIXエポック) からの経過秒数を、閏秒の存在を無視し形式的な差を計算した値に等しい。

Source: https://ja.wikipedia.org/wiki/UNIX時間

date では +%s を付与することで Unix epoch からの秒数を取得することができます。

% date +%s
1709022219

直前のコミットに関しては、git log -1 で取得しますが、ここにフォーマットの指定 --format=%ct を入れることで "直前のコミットのUNIX時間" を取得することができます。

% git log -1
commit 0055b997df1ead6a67cc2ed9711e6bef652dd27b (HEAD -> main)
Author: linnefromice <linnefromice@gmail.com>
Date:   Mon Feb 26 21:40:07 2024 +0900

    chore: xxxxx

% git log -1 --format=%cd
Mon Feb 26 21:40:07 2024 +0900

% git log -1 --format=%ct
1708951207

Reference: Git - pretty-formats Documentation

これらのコマンドを利用して、更新有無をチェックします。以下のように変数を利用するので、後述のワークフロー実装を理解する際の参考にしてください。

  • 最終コミット時刻 -> 変数 LATEST_COMMIT_UNIX_TIME に代入
  • 現在時刻 -> 変数 CUREENT_TIME に代入
  • 更新があったと判断する現在時刻からの時間範囲 -> GitHub ワークフローの envINTERVEL_SEC に設定
    • 今回はこの値を "1日" に設定しています。(24 * 60 * 60)

これらを踏まえて、以下のような実装になります。

...
+env:
+  INTERVEL_SEC: 86400 # same to cron interval
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Check for updates
        run: |
+          LATEST_COMMIT_UNIX_TIME=$((git log -1 --format=%ct))
+          CUREENT_TIME=$(date +%s)
+          DIFF=$((CUREENT_TIME - LATEST_COMMIT_UNIX_TIME))
+          IS_EXIST_NEW_COMMIT=$(($CUREENT_TIME - ${{ env.INTERVEL_SEC }} - $LATEST_COMMIT_UNIX_TIME < 0))
+          echo $IS_EXIST_NEW_COMMIT

上記のワークフローにより、更新要否が変数 IS_EXIST_NEW_COMMIT に格納されています。次のステップでこの変数を使用可能とするように、GitHub Actions の仕組みを使用しましょう。

...
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Check for updates
+        id: check
        run: |
          LATEST_COMMIT_UNIX_TIME=$((git log -1 --format=%ct))
          CUREENT_TIME=$(date +%s)
          DIFF=$((CUREENT_TIME - LATEST_COMMIT_UNIX_TIME))
          IS_EXIST_NEW_COMMIT=$(($CUREENT_TIME - ${{ env.INTERVEL_SEC }} - $LATEST_COMMIT_UNIX_TIME < 0))
-          echo $IS_EXIST_NEW_COMMIT
+          echo "is_exist_new_commit=$IS_EXIST_NEW_COMMIT" >> $GITHUB_OUTPUT

ステップに id を設定し $GITHUB_OUTPUT に出力したい変数を echo で出力することで後続ジョブで使用することができます。詳細は以下を参考にしてください。

https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter

今回の場合であれば、後続ジョブで steps.check.outputs.is_exist_new_commit を利用することでチェック結果を使用することができます。

チェックフラグを確認し、実処理を呼び出す

今回のポイントは、前ステップで判断した結果に応じて処理を分岐する部分です。その後の実処理の呼び出しはおまけなので、用途に応じてカスタマイズをしてください。最初に前ステップで判断した結果を使用します、前ステップで判定結果を変数に出力したので、それを if 条件で使用します。

...
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Check for updates
        id: check
        run: |
          ...
          echo "is_exist_new_commit=$IS_EXIST_NEW_COMMIT" >> $GITHUB_OUTPUT
+      - name: if check flag is true, request to update
+        if: ${{ steps.check.outputs.is_exist_new_commit != '0' }}
+        uses: # todo (uses, run etc)

上記のように if 条件で、ステップ自体を実行するかどうか判断することができます。

今回の実処理は、バージョンアップをするために別のワークフローを呼び出します。そのために convictional/trigger-workflow-and-wait@vX.Y.Z を利用します。

https://github.com/convictional/trigger-workflow-and-wait

...
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      ...
      - name: if check flag is true, request to update
        if: ${{ steps.check.outputs.is_exist_new_commit != '0' }}
-        uses: # todo (uses, run etc)
+        uses: convictional/trigger-workflow-and-wait@v1.6.5
+        with:
+          owner: (owner_name)
+          repo: (repo_name)
+          workflow_file_name: (workflow_filename)
+          github_token: ${{ secrets.GITHUB_TOKEN }}

以上で、ワークフロー全体の実装が完了しました。少しだけ改善を加えて完成させましょう。

改善: チェック対象ディレクトリ/ファイルの絞り込み

以前のセクションで更新有無をチェックするステップを追加しましたが、このステップは全てのフォルダをチェック対象にしています。今回指定した実処理のように自動で更新を加えるような処理がトリガーで実行される場合、それによって積まれたコミットもチェックしてしまいます。こういった事態を避けるために、チェックする対象のフォルダを絞り込みます。

git コマンドの引数に指定することで対象フォルダを選択することができるのでこちらを利用しましょう。-- (フォルダパス) とコマンドに加えることで絞り込みができます、今回は src フォルダのみをターゲットとしました。

...
env:
  INTERVEL_SEC: 86400
+  TARGET_PATH: src
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Check for updates
        id: check
        run: |
-          LATEST_COMMIT_UNIX_TIME=$((git log -1 --format=%ct))
+          LATEST_COMMIT_UNIX_TIME=$((git log -1 --format=%ct -- ${{ env.TARGET_PATH }}))
          CUREENT_TIME=$(date +%s)
          DIFF=$((CUREENT_TIME - LATEST_COMMIT_UNIX_TIME))
          IS_EXIST_NEW_COMMIT=$(($CUREENT_TIME - ${{ env.INTERVEL_SEC }} - $LATEST_COMMIT_UNIX_TIME < 0))
          echo "is_exist_new_commit=$IS_EXIST_NEW_COMMIT" >> $GITHUB_OUTPUT
      ...

改善: タイマー実行

今回実装したワークフローは "1日" 以内に更新があったかどうかをチェックするようにしています。このワークフロー自体も1日単位で自動実行するようにしてみましょう。

トリガーさせるイベントに schedule を設定することで時間間隔による定期実行を組み込むことができます。この時間設定は cron 形式で行います。詳細は下記を参考にしてください。

https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule

1日単位で実行するために下記のように設定してみましょう。

name: Check for Version up
+on:
+  schedule:
+    - cron: '0 0 * * *'
env:
  ...

終わりに

この記事では、定期的にリポジトリの変更をチェックして何らかの処理をトリガーする GitHub Actions ワークフローを構築する方法について説明しました。
定期的なリポジトリの変更の監視は、自動化されたタスクの実行や適切なタイミングでの処理の実行に役立つケースがあると思います。
ぜひ利用してみてください。

Discussion