GIthub Actions で Git Submodule を 最新に更新して処理する

はじめに

ポートフォリオサイトを、Github Actionsでビルド・Github Pagesで公開というように運用しているんですが、他のリポジトリにあるプロジェクトもGithub Pagesで公開したくなることがありました。Github Pagesのリポジトリで、公開したいプロジェクトのリポジトリをGit Submoduleとして参照することで、これは実現できます。しかしこのままでは、Github PagesのリポジトリでSubmoduleを更新しないと、参照先のリポジトリへの変更が適用されません。これは面倒なので、参照先リポジトリが変更されたタイミングで、自動でSubmoduleを更新してビルドをするようなものを作りました。

WebhookでWorkflowをトリガーする

Github ActionsのWorkflowは、特定の条件でそれが起動するように記述します。例えば、「masterブランチにpushされた」などです。前述のようなことを実現するには「リポジトリAのmasterブランチにpushされた」をトリガーに、リポジトリBのWorkflowを起動できれば良さそうです。しかし、直接そのようなトリガーを記述することはできません。
そこで使うのが repository dispatch event です。repository dispatch event を使えば、リポジトリ外部からのAPI呼び出しをトリガーにすることができます。
参考:Github Actions を API から実行する - Qiita

以下、Submodule参照元リポジトリをsubmod_action_main、Submodule参照先のリポジトリをsubmod_action_sub とします。
GitHub - w-haibara/submod_action_main
GitHub - w-haibara/submod_action_sub

参照元リポジトリ submod_action_main に、repository dispatch event でトリガーするWorkflowを作成します。
下記のWorkflowは「このリポジトリに対してAPI呼び出しがあったとき、Github Actions上でecho "Hello!" を実行する」ものです。

name: Update Submodule

on:
  repository_dispatch:
    types: [update]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Hello
      run: |
        echo "Hello!"

このWorkflowを実行するためのAPI呼び出しは、以下のようにcurlで行うことができます。
この際、指定するURLは、
https://api.github.com/repos/<ユーザー名>/<リポジトリ名>/dispatches
となります。
また、環境変数 DISPATCH_TOKEN は、API呼び出しの対象とするリポジトリのシークレットです。
ターミナルで下記のcurlを実行してみます。

curl -vv \[f:id:w_haibara:20200613025711p:plain]
  -H "Authorization: token ${{ DISPATCH_TOKEN }}" \
  -H "Accept: application/vnd.github.everest-preview+json" \
  "https://api.github.com/repos/w-haibara/submod_action_main/dispatches" \
  -d '{"event_type": "update"}'

実行すると、Status: 204 No Content が帰ってくると思います。
Workflowの実行結果を見てみると、echo "Hello!" が実行されていることを確認できます。
f:id:w_haibara:20200613025711p:plainf:id:w_haibara:20200613025705p:plain


これで、Web APIを通して外部からトリガーできるWorkflowを作ることができました。

WorkflowからAPI呼び出し

次に、先ほどターミナル上のcurlで行ったAPI呼び出しを、Workflowから行います。
下記に、「masterブランチにpushがあったとき、API呼び出しを行う」Workflowを示します。
ここで ${{ secrets.DISPATCH_TOKEN }} としている部分は、Github内に DISPATCH_TOKEN という名前で登録した、API呼び出しの対象とするリポジトリのシークレットです。

name: Dispatch
on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: |
          curl -vv -H "Authorization: token ${{ secrets.DISPATCH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" "https://api.github.com/repos/w-haibara/submod_action_main/dispatches" -d '{"event_type": "update"}'

submod_action_sub/dispatch.yml at master · w-haibara/submod_action_sub · GitHub

このWorkflowと、先ほどのrepository dispatch event を組み合わせると、冒頭で言っていた「リポジトリAのmasterブランチにpushされたら、リポジトリBでビルドをする」ということが可能となります。
(リポジトリAのmasterブランチにpush --> Web API 呼び出し (リポジトリAのWorkflow) --> repository dispatch eventが発火 (リポジトリBのWorkflow) --> リポジトリBのビルド)

Submoduleの更新

後は、参照元リポジトリ(repository dispatch event を使ったリポジトリ)で、Submoduleを更新してビルドするだけです。Workflowは下記のようになります。

name: Update Submodule

on:
  repository_dispatch:
    types: [update]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
      with:
        submodules: true
    - name: Update submodule
      run: |
        git submodule update --remote ./submod_action_sub/
    - name: Git commit
      run: |
        git config --local user.name "w-haibara"
        git config --local user.email "hwhaibarawataru@gmail.com"
        git add -A
        git status
        git commit -m "update submodule (by update_submodule.yml)"
    - name: Git push
      uses: ad-m/github-push-action@v0.5.0
      with:
        branch: master
        github_token: ${{ secrets.GITHUB_TOKEN }}
    - name: Main process
      run: |
        bash ./main.sh

submod_action_main/update_submod.yml at master · w-haibara/submod_action_main · GitHub

やっていることは
Submoduleの更新 --> それに伴う変更をステージング --> 変更をコミット --> メイン処理
という流れです。
ここでは、メイン処理としてシェルスクリプトmain.sh を実行しています。

完成形を試す

さて、完成したシステムを動かしてみます。
初期状態として、submod_action_subには、Helloという文字列が書き込まれたtext.txtが置かれています。
また、submod_action_mainには下記のシェルスクリプトmain.shが置かれています。これは ./submod_action_
sub/text.txtの内容を出力するものです。

#!/bin/bash
while read line
do
  echo $line
done < ./submod_action_sub/text.txt

まず、submod_action_mainをクローンし、submod_action_subをSubmoduleとして取り込みます。

$ git clone https://github.com/w-haibara/submod_action_main
$ cd submod_action_main
$ git submodule add https://github.com/w-haibara/submod_action_sub

こうすることで、現時点で最新のsubmod_action_subの内容が取り込まれます。
試しに、main.shを実行すると、現在の./submod_action_sub/text.txtの内容である「Hello」が出力されるはずです。

$ bash ./main.sh
Hello

次に、submod_action_subを別にクローンし、text.txtに変更を加えます(「Yeah!」という文字列を書き込みます)。

$ git clone https://github.com/w-haibara/submod_action_sub
$ cd submod_action_sub
$ echo "Yeah!" > ./text.txt
$ git add .
$ git commit -m "Update text.txt"
$ git push origin master

プッシュを終えたら、Githubでsubmod_action_subのActionsのページを確認します。
f:id:w_haibara:20200613034820p:plain
API呼び出しのWorkflowが完了しています。
次に、Githubでsubmod_action_mainのActionsのページを確認します。
f:id:w_haibara:20200613035051p:plain
こちらもWorkflowが完了しているようです。
Workflowの詳細を見てみます。
f:id:w_haibara:20200613035341p:plain
先ほど書き込んだ「Yeah!」が出力されています!無事にSubmoduleの更新が行われていることを確認できました。

おわりに

これで肥大化したGithub Pagesのリポジトリを分割できる!