GitHub Actionsを使ってリポジトリ間でファイルを機械的に同期する

サーバ側に存在するリソースと同等のファイルをアプリ内部で抱え持つことは度々ある。
手動だと都度サーバ側のリポジトリから最新リソースファイルをアプリ側のリポジトリにコピーする必要があり、
この運用は大抵忘れられがちになるし事故の元。

そこでGitHub Actionsを使って、サーバ側リポジトリの特定ディレクトリを監視して更新されたらアプリ側リポジトリで通知を受け取りファイル群を差し替えてプルリクエストを作る、というのをやってみた。（マージまで自動で行うこともできそうだが流石に確認がないと怖いのでプルリク）

構文については[GitHub Actionsのワークフロー構文](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)を見ると便利。

# サーバ側にワークフローを用意する

リポジトリのActionsタブ > New workflowから新しいワークフローを追加する。
テンプレートは使わないので「set up a workflow yourself」を選択する。
![スクリーンショット 2021-05-18 11.49.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/4001d1f6-7a49-a528-ac89-d48b1ee81130.png)
デフォルトでこのようなYAMLができるが、
![スクリーンショット 2021-05-18 11.49.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/17c855c8-e025-4f43-a707-2fea22f07029.png)

やりたいことと全然違うので内容は以下のように書き換える。
説明は↓のコード中にコメント。

勝手に追加したらチームに怒られる場合は `ローカルリポジトリ/.github/workflows/hogehoge.yml` を追加してプルリクすればOK。

```Yaml
# リポジトリのアクションページに表示される名称
name: Dispatch assets files updated
on:
  # masterブランチのAssets以下へのpushを監視する
  push:
    branches:
      - master
    paths:
      - Assets/*
jobs:
  # ジョブID（英字または_で始まり、英数字と-、_で構成されていたらなんでもOK）
  dispatch-directory-changes:
    strategy:
      matrix:
        # 様々なジョブの設定を定義しておける。ここでは通知を受け取るリポジトリ一覧を定義しておく。
        repo: ['<オーナー名>/<アプリ側リポジトリ名>']
    name: Do dispatch-directory-changes
    # ジョブが実行されるマシンの種類。
    runs-on: ubuntu-latest
    steps:
      # アプリ側リポジトリで `repository_dispatch` を使って通知を受け取るので `peter-evans/repository-dispatch` を使用
      - name: Do repository-dispatch
        uses: peter-evans/repository-dispatch@v1
        with:
          # 対象がPrivateなリポジトリのためtokenをセット（内容は後述）
          token: ${{ secrets.<GitHubシークレットキー名> }}
          # 定義しておいた通知対象リポジトリを指定
          repository: ${{ matrix.repo }}
          # イベントタイプを `directory-changes` にしておく
          event-type: directory-changes
```

`repository_dispatch` は複数のワークフローを実行でき、カスタムイベントやイベント型を作成可能という特徴があり、
似たようなのに`workflow_dispatch`があり、これは単一の特定ワークフローの実行、ブランチやタグの指定が可能というものらしい。
詳しくは[ワークフローをトリガーするイベント](https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#)

挙動としては、
Assets以下の変更がmasterブランチにpushされることをトリガーとして、
matrix.repoに指定したリポジトリに対して`directory-changes`イベントを発行する。

# アプリ側にもワークフローを用意する

サーバ側と同じように新しくワークフローを追加する。
![スクリーンショット 2021-05-18 11.39.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/f73a3c91-7db6-3d19-2c09-f254ffc69f96.png)
![スクリーンショット 2021-05-18 11.39.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/95d97922-67d2-7198-48b4-d9fa6300de67.png)

ymlの内容は以下のように書き換える。
説明は↓のコード中にコメント。

```Yaml
# リポジトリのアクションページに表示される名称
name: Copy Assets from <サーバ側リポジトリ名>
on:
  # repository_dispatchでサーバ側のymlで指定したevent-type `directory-changes`をフックする
  repository_dispatch:
    types: [directory-changes]
jobs:
  directory-changes:
    name: Copy Assets
    runs-on: ubuntu-latest
    steps:
      # アプリ側のリポジトリをclone
      - name: Clone
        uses: actions/checkout@v2
      # サーバ側リポジトリをclone
      - name: Clone <サーバ側リポジトリ名>
        uses: actions/checkout@v2
        with:
          repository: <オーナー名>/<サーバ側リポジトリ名>
          path: <サーバ側リポジトリ名>
          token: ${{ secrets.<GitHubシークレットキー名> }}
      # サーバ側リポジトリ/Assets以下のファイルをアプリ側リポジトリ/Assetsへコピー
      - name: Copy assets files
        run: cp <サーバ側リポジトリ名>/Assets/* Assets
      # コピーが済んだためcloneしたサーバ側リポジトリを削除
      - name: Clean <サーバ側リポジトリ名>
        run: rm -rf <サーバ側リポジトリ名>
      # プルリク作成
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v3
        with:
          token: ${{ secrets.<GitHubシークレットキー名> }}
          commit-message: 'Update Assets'
          branch: feature/asset-files-changes
          base: master
          # 同一ブランチ名にならないよう後ろにタイムスタンプを付加
          branch-suffix: timestamp
          delete-branch: true
          # レビュワーの指定
          reviewers: <ユーザ名 or グループ名など>
```

例ではプライベートリポジトリにアクセスするために[個人アクセストークン](https://docs.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token)をサーバ側/アプリ側それぞれのリポジトリ > Settings > Secretsに登録する。
キー名は`GITHUB_`から始まるものは使えないので適当に考える必要がある。

アクセストークンの権限はrepoがあればOK
![スクリーンショット 2021-05-18 14.09.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/6df1bfdd-83b5-298c-02fe-1c7279856b08.png)

登録したキー名を`<GitHubシークレットキー名>`の部分に使う。

例では以下も適宜ご自身のものに変更が必要。
* `<オーナー名>` 
* `<サーバ側リポジトリ名>`、
* 各`name`や
* コミットメッセージ: `commit-message`、
* ブランチ名: `branch`
* 監視対象ディレクトリ: `Assets` 
* レビュワー: `<ユーザ名 or グループ名など>`

[peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) を見るとプルリクを作る際にtitleやbody、labelやmilestoneなどなどいろいろ設定できる！便利

# 実行
サーバ側でAssetsディレクトリの中にhoge.htmlを入れて内容変更した時にアプリ側でうまくプルリクが作られるか検証。

1. サーバ側リポジトリでAssets/hoge.htmlを編集してcommit & push。リポジトリ > Actionsタブを見ると走り出した。
![スクリーンショット 2021-05-18 17.22.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/d9d18851-ee7e-6853-3d77-4ff0cb87307d.png)
2. 数秒でエラーなく終わったようだ。
![スクリーンショット 2021-05-18 17.22.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/a31bd462-4f3d-acac-b54c-e6fea65f7839.png)
3. アプリ側のActionsを見てみるとこちらも数秒で完了。
![スクリーンショット 2021-05-18 17.23.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/5b42dfcc-fa80-f001-222a-7e9f57e36388.png)
4. アプリ側でプルリクが作られてた。
![スクリーンショット 2021-05-18 17.23.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/af6ab0eb-b648-d977-9830-98c1d7a1fd2b.png)
5. hoge.htmlの変更内容も正しい。
![スクリーンショット 2021-05-18 17.23.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156085/a2d99eb4-f243-aaf1-2f3b-cb04ac2ff12a.png)

素晴らしい！

## 余談
因みに監視対象のディレクトリ名（例でいうAssets）をリネームするとイベントはハンドリングされるのでアプリ側のワークフローは実行されるけど、
directory-changesのstepsで単に固定ディレクトリ名の内容をcpだけしか行っていないのでエラーになるはず。