Github ActionsのComposite Actionを利用して他リポジトリへPRを送る雛形を作成する

ZOZO Advent Calendar 2023 4日目の記事になります。

こんにちは!今年もAdvent Calenderの時期がやってきました!小ネタを絞り出して記事を書こうかと思います。

はじめに

皆さんはGtihub Actionsの複合アクション(Composite Action)を利用したことがありますか?Github ActionsでCIを作成しているとWorkflowの処理に似通った処理をするStepが出てくると思います。2-3stepで処理が済めば良いのですが、5-10stepくらいにまたがる処理の場合はWorkflowの見通しが悪くなり、修正も大変になります。この問題を解決するのがComposite Actionになります。 今回はComposite Actionの使用例としてCIから他リポジトリへPRを作成するような処理を紹介します。Composite Actionの基本的な利用方法は下記ドキュメントにまとまっているので併せてご確認ください。

docs.github.com

仕組みの説明

具体的なComposite Actionの中身を見る前にどのような仕組みを作りたいか簡単に説明します。 利用背景例として設定ファイルを管理するリポジトリがあり、設定ファイルを任意のタイミングで他リポジトリへ反映したい場合を考えます。イメージとして下記図のような形でPRを送る形にすれば、他リポジトリの設定を任意に更新できます。

Composite Actionの実装

下記実装がComposite Actionの実装になります。 処理の流れとしては、最初に設定を同期したいリポジトリをCheckoutします。次に設定ファイルが配置されている場所(今回はdownload-artifactを利用しています)と同期するリポジトリ内のファイルをrsyncコマンドで同期します。最後に同期したディレクトリのdiffを確認して、必要であればPRを作成するような処理になります。汎用性を持たせるためにinputs部分で諸々パラメータを渡せるようにしています。

name: 'Create github pull request'
description: 'Create github pull request and sync data'
inputs:
  sync-source-and-destination-dir:
    description: 'Receive json data to sync another respository directories. ex: [{"source_dir": "source_dir", "dst_dir": "dst_dir"}]'
    required: true
  download-artifact-name:
    description: 'download artifact name'
    required: true
  download-artifact-path:
    description: 'download artifact path'
    required: true
  branch-name:
    description: 'branch name'
    required: true
  pull-request-title:
    description: 'pull request title'
    required: true
runs:
  using: "composite"
  steps:
    - name: delete branch if exists
      run: |
        git push origin :${{ inputs.branch-name }} || true
      shell: bash   

    - name: create branch
      run: |
        git switch -c ${{ inputs.branch-name }}
      shell: bash

    - name: show cuurent branch
      run: |
        git branch
      shell: bash

    - name: Extract to an appropriate directory
      uses: actions/download-artifact@v3
      with:
        name: ${{ inputs.download-artifact-name }}
        path: ${{ inputs.download-artifact-path }}

    - name: set git config
      run: |
        git config user.name github-actions
        git config user.email github-actions@github.com
      shell: bash
    # 同期するディレクトリを指定する
    - name: sync data and add git
      run: |
        for row in $(echo '${{ inputs.sync-source-and-destination-dir }}' | jq -c '.[]'); do
          source_dir=$(echo $row | jq -r '.source_dir')
          dst_dir=$(echo $row | jq -r '.dst_dir')
          rsync -v --delete -d $source_dir $dst_dir
          git add $dst_dir
        done
      shell: bash
    # diffを確認して後続の処理を行うかを判断する
    - name: check diff
      id: changes
      run: |
        diff_file=`git diff --cached --name-status | wc -l`
        if [ $diff_file -gt 0 ]; then
          echo "skip_ci=true" >> $GITHUB_OUTPUT
        else
          echo "skip_ci=false" >> $GITHUB_OUTPUT
        fi
      shell: bash

    - name: git commit
      if: ${{ steps.changes.outputs.skip_ci != 'false' }}
      run: |
        git commit -m "update views" ${DOWNLOAD_PATH} 
      shell: bash

    - name: git push
      if: ${{ steps.changes.outputs.skip_ci != 'false' }}
      run: |
        git push origin HEAD
      shell: bash
    # gh commandを利用するための認証
    - name: gh command authorization
      if: ${{ steps.changes.outputs.skip_ci != 'false' }}
      run: |
        echo ${PERSONAL_ACCESS_TOKEN} > token.txt
        gh auth login --with-token < token.txt
      shell: bash

    # PRの作成
    - name: create Pull Request
      if: ${{ steps.changes.outputs.skip_ci != 'false' }}
      run: |
        gh pr create --title "${{ inputs.pull-request-title }}" --body "- [ ] ${{github.event.pull_request.html_url}}"
      shell: bash

Composite Action呼び出し側の実装

Composite Actionの呼び出し側は下記イメージで呼び出します。呼び出し部分はSync backend repository directories and Create PRというjobで行われます。with部分でComposite Actionに渡すパラメータを指定しています。sync-source-and-destination-dirjsonでパラメータを渡すようにできており、PRを作成したいリポジトリの複数ディレクトリを同期できるようにしています。

name: "sample"

on:
  pull_request:
    branches:
      - 'main'
    types: [opened, synchronize, closed]

jobs:
  backend-pr:
    runs-on: ubuntu-latest
    env:
      PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
      DOWNLOAD_NAME: config_files
    steps:
      # PRを作成したいリポジトリをチェックアウト
      - uses: actions/checkout@v3
        with:
          token: ${{ env.PERSONAL_ACCESS_TOKEN }}
          repository: hoge/backend
      
      - uses: actions/checkout@v3
        with:
          path: fuga

      - name: Sync backend repository directories and Create PR
        # Composite Actionが格納されているディレクトリを指定
        uses: ./hoge/.github/actions
        with:
          branch-name: update-config-$(date "+%Y%m%d")
          download-artifact-name: ${{ env.DOWNLOAD_NAME }}
          download-artifact-path: ${{ runner.temp }}
          sync-source-and-destination-dir: '
            [{
              "source_dir": "${{ runner.temp }}/hoge_setting/",
              "dst_dir": "backend/hoge_setting/"
            },
            {
              "source_dir": "${{ runner.temp }}/fuga_setting/",
              "dst_dir": "backend/fuga_setting/"
            }]'
          pull-request-title: 'update settings $(TZ=JST-9 date +"%Y-%m-%d %T %Z")'

まとめ

Composite Actionを利用することで複数のstepにまたがる処理をまとめる事ができました。CIは気がついたらyamlの記述量が膨大になって見通しが悪くなることが多いのでComposite Actionで共通処理をうまくまとめてみると良さそうです