[GitHub Actions] GitHub CLIで取得したアイテムのリストをmatrix構文で処理する

2022.05.28

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、GitHub Actionsで、GitHub CLIで取得したアイテムのリストをmatrix構文で処理してみました。

matrix構文とは

GitHub Actionsのmatrix構文を使うと、単一のJobの記述で複数パターンの変数要素を指定し、それぞれのパターンに対してJobを実行することができます。

使い方は次のようになります。Jobのstrategy.matrixで配列形式で変数要素を指定します。

jobs:
  example_matrix:
    strategy:
      matrix:
        version: [10, 12, 14]
        os: [ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.version }}

上記はosが2要素、versionが3要素であるため、合わせて6パターンのJobが実行されます。

  • {version: 10, os: ubuntu-latest}
  • {version: 10, os: windows-latest}
  • {version: 12, os: ubuntu-latest}
  • {version: 12, os: windows-latest}
  • {version: 14, os: ubuntu-latest}
  • {version: 14, os: windows-latest}

このように、変数要素のパターンを変えながらJobを実行したい場合に、要素ごとにWorkflowの実行やJobの記述を分けたりする必要が無くなってとても便利です。

GitHub CLIで取得したアイテムのリストをmatrixで処理する

matrix構文で指定する変数要素は、Workflow実行内で動的に生成することも可能です。ここではGitHub CLIのgh pr listコマンドで取得したPull Requestのリストを処理してみます。

次のWorkflowでは、1つ目のJobでPull RequestのIssue番号のリストをContextに指定し、2つ目のJobでmatrixに使用しています。このようにJobを2段階にすることによりmarixの要素を動的に生成することができます。

.github/workflows/matrix.yml

on:
  push

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
  get_pr_matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.value }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - id: matrix
        run: |
          pr_list=$(gh pr list --json number --limit 5 | jq '[.[].number]')
          echo "::set-output name=value::$(echo $pr_list | jq -c .)"
  do_process:
    needs: get_pr_matrix
    runs-on: ubuntu-latest
    strategy:
      matrix:
        value: ${{ fromJson(needs.get_pr_matrix.outputs.matrix) }}
    steps:
      - name: Processing for each pull request
      - run: |
          echo ${{ matrix.value }}

ポイントとしては、CLIで取得したアイテムをjqコマンドでJsonパースしてContextに指定する際にコンパクト処理(-cオプション)をしている所です。

# コンパクト処理をしない場合
$ gh pr list --json number --limit 5 | jq '[.[].number]'
[
  1381,
  1376,
  1373,
  1370,
  1368
]

# コンパクト処理をした場合
$ pr_list=$(gh pr list --json number --limit 5 | jq '[.[].number]')
$ echo $pr_list | jq -c .
[1381,1376,1373,1370,1368]

コンパクト処理をしない(改行記号が含まれている)配列はmatrix要素に指定することはできないためです。(また、1回目のjq実行でコンパクト処理をしていない理由としては、コンパクト処理していない値をGitHub Actions Workflow上ではなぜか変数で上手く扱えなかったためです。)

PushしてWorkflowを実行すると、matrixによりPull Request毎にJobが分けられて処理されています。(13XXが取得したPull RequestのIssue番号です。)

matrixにより実行されるそれぞれのJob名は<Step名> (matrix要素)となり、Workflow実行画面上での表示にも使用されるため、それを意識した要素(今回はIssue番号)を使うと良いかと思います。

おまけ:そもそもやりたかったこと

参考までに、matrix構文を使用して元々やりたかったこととしては、Cronで実行して条件に合致するPull Requestを自動でMergeする処理です。この時にGitHub CLIで取得したそれぞれのPull Requestに対して、条件の一致チェックとBase BranchへのMergeを行う必要があったため、matrix構文がとても役に立ちました。

.github/workflows/cron.yml

# そもそもやりたかったこと
on:
  schedule:
    - cron: 0 15 * * *
  push:

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
  get_pr_matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.value }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - id: matrix
        run: |
          pr_list=$(gh pr list --json number | jq '[.[].number]')
          echo "::set-output name=value::$(echo $pr_list | jq -c .)"
  merge_pr:
    needs: get_pr_matrix
    runs-on: ubuntu-latest
    strategy:
      matrix:
        value: ${{ fromJson(needs.get_pr_matrix.outputs.matrix) }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set pull request info to Context
        id: set_pr_info_to_context
        run: |
          pull_request=$(gh pr view ${{ matrix.value }} --json title,commits,comments | jq .)
          echo ${pull_request} | jq .

          last_comment_body=$(echo ${pull_request} | jq -r '.comments | .[-1].body')
          echo "LAST_COMMENT_BODY=${last_comment_body}" >> $GITHUB_ENV

          last_commented_at=$(echo $pull_request | jq -r '.comments | .[-1].createdAt')
          unixtime_last_commented_at=$(date -d ${last_commented_at} '+%s')
          echo "UNIXTIME_LAST_COMMENTED_AT=${unixtime_last_commented_at}" >> $GITHUB_ENV

          last_commit_date=$(echo $pull_request | jq -r '.commits | .[-1].committedDate')
          unixtime_last_committed_at=$(date -d ${last_commit_date} '+%s')
          echo "UNIXTIME_LAST_COMMITTED_AT=${unixtime_last_committed_at}" >> $GITHUB_ENV

          echo "UNIXTIME_THREE_DAYS_AGO=$(date --date "-3 day" '+%s')" >> $GITHUB_ENV
      - name: Merge Pull Request
        if: 
          env.LAST_COMMENT_BODY == 'published' &&
          env.UNIXTIME_LAST_COMMITTED_AT < env.UNIXTIME_LAST_COMMENTED_AT &&
          env.UNIXTIME_LAST_COMMENTED_AT < env.UNIXTIME_THREE_DAYS_AGO
        run: |
          gh pr merge ${{ matrix.value }} --merge

おわりに

GitHub Actionsで、GitHub CLIで取得したアイテムのリストをmatrix構文で処理してみました。

元々はShellのfor loop構文を使って行っていた処理が、matrix構文を使うことにより簡略化され、また実行画面上でも処理が可視化できるようになりました。GitHub Actionsの便利なBuilt-in機能なのでどんどん使っていきたいですね!

参考

以上