🤖

スプレッドシートの内容を GitHub のリポジトリに自動的に同期する仕組みを作った

2023/02/18に公開

概要

ひょんなことから2,3ヶ月に渡る期間開催されるスプラトゥーン3の大会のポータルサイトの開発をすることになりました。
チーム情報や大会の対戦情報はスプレッドシートで管理されており、その内容をポータルサイトに反映する仕組みを作ったので紹介します。

具体的にはこのような仕組みを作りました。

雑に説明するとスプレッドシートを CMS として扱い、CMS とリポジトリの同期を GitHub Actions で実現しました。

仕組み

スプレッドシートの差分検知

スプレッドシートの内容を JSON ファイルに変換し、その JSON ファイルをコミットする方針にしました。これにより git diff の結果があれば差分があると検知することができます。

スプレッドシートの内容を JSON に変換

まずはスプレッドシートの内容を JSON に変換することを考えます。
例えば、以下のようなデータがあったときに

チーム名 メンバー1 メンバー2 メンバー3 メンバー4
チームA Aさん Bさん Cさん Dさん

次のような形式の JSON に変換しています。

[{ "teamName": "チームA", "players": ["Aさん", "Bさん", "Cさん", "Dさん"] }]

スプレッドシートの内容を Node.js で取得

スプレッドシートの内容を Node.js で取得するために @googleapis/sheets を使用しました。
https://www.npmjs.com/package/@googleapis/sheets

認証情報は Application Default Credentials (以下 ADC) が設定されていることを前提にしました。事前に認証情報に紐付いているメールアドレスに対して、スプレッドシートを閲覧できる権限を与えておく必要があります。

これにより以下のコードでスプレッドシートの内容を取得することができます。

const sheetId = "";
const auth = new GoogleAuth({
  scopes: [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/spreadsheets.readonly",
  ],
});
const sheets = getSheets({
  version: "v4",
  auth,
});
const res = await sheets.spreadsheets.values.get({
  spreadsheetId: sheetId,
  range: "チーム!A1:B",
});

console.log(res.data.value);

差分検知の仕組みを GitHub Actions で動かす

差分検知は定期実行されてほしいので、GitHub Actions の schedule イベントの cron を使用しました。
Actions での認証情報は、元々あったポータルサイトを Firebase Hosting に自動デプロイする仕組みで使用しているサービスアカウントを利用しました。
シークレットに保存されているサービスアカウントを利用するには google-github-actions/auth が便利です。

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v1
        with:
          credentials_json: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ROOKIE_LEAGUE_PORTAL }}"

認証情報を環境変数に設定してくれるため ADC を前提にしているプログラムならコードを書き換えずに動作してくれます。

検知した差分をコミットするプルリクを作成

ここまでで、GitHub Actions 上でスプレッドシートとの差分を検知して JSON に変換し、差分がある場合は git diff が発生するところまで完成しました。
次にこの差分を持つプルリクを作成する仕組みを作ります。

GitHub Actions でプルリクを作成する場合は peter-evans/create-pull-request が便利です。
https://github.com/peter-evans/create-pull-request

差分があるときのみにプルリクを作成、すでにプルリクが存在する場合は force-push で上書きなどの機能を持ちます。

GitHub Actions の仕様で、通常の GitHub Actions で作成したプルリクでは pull_request イベントが発火しません。これは無限ループの発生により無限に GitHub Actions が呼び出されることを防止するためです。この仕様は peter-evans/create-pull-request で作成されるプルリクであっても適用されます。

今回のポータルサイトではプルリクが作成されるたびに、プレビュー環境が作成され動作確認ができる仕組みが用意されていました。そのためスプレッドシートの差分をプレビュー環境で確認するにはもうひと手間必要でした。

この問題の対応策は peter-evans/create-pull-request のドキュメント で言及されています。色々方針がありますが今回は Authenticating with GitHub App generated tokens を選択しました。

全体像

最後に GitHub Actions の設定ファイルを紹介して終わります。

name: Synchronize Sheets

on:
  workflow_dispatch:
  schedule:
    - cron: "0 * * * *"

env:
  TZ: "Asia/Tokyo"

jobs:
  sync-sheets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18.12.1
          cache: "npm"

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v1
        with:
          credentials_json: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ROOKIE_LEAGUE_PORTAL }}"

      - uses: tibdex/github-app-token@v1
        id: generate-token
        with:
          app_id: ${{ secrets.APP_ID }}
          private_key: ${{ secrets.APP_PRIVATE_KEY }}

      - run: npm ci
      - run: npm run sync-sheets
        working-directory: packages/sync-sheets

      - run: git status

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v4
        with:
          add-paths: |
            packages/app/src/data
          branch: sync-sheets
          title: Sync sheets
          body: |
            マスターシートのデータを json ファイルに同期するプルリクです
            diff とプレビュー環境で意図した変更か確認して良さそうならマージお願いします
          commit-message: Sync sheets
          labels: |
            sync-sheets
          reviewers: |
            odanado
          delete-branch: true
          token: ${{ steps.generate-token.outputs.token }}

Discussion