WordPressで記事を投稿したらGitHub Actions経由でGridsomeのビルドをする。

Markdownに変換した既存のブログ記事をちまちま修正するのがしんどいのと、WordPressをHeadless CMSとして運用した方が楽だと判断したので、以下の構成でこのブログを運用することにしました。

  • 記事の作成

    • Headless CMS化したWordPress
  • 記事・RSS・サイトマップのビルド

    • Gridsome
  • ホスティング

    • 自分が借りてるVPS

自分以外の人はWordPressにアクセスできないため、セキュリティ対策やプラグインの脆弱性に煩わされることがなくなり、運用の負荷が軽くなったのが最高です。あとテンプレートを修正するのにPHPをいじる必要がないのがうれしすぎる。
今の構成だと、WordPressで記事を投稿したタイミングでGridsomeにビルドしてもらう必要があるのですが、毎回自分でビルドコマンドをたたくのは馬鹿らしいし、手元にNode.jsの実行環境がないとビルドできなくて詰むという問題点があります。GitHub ActionsならWebhook経由でgridsomeコマンドが実行できるので、GitHub Actionsを使ってこの問題を解消することにします。

全体の流れ

前提条件として、サイトジェネレーターでビルドするためのソースコードをGitHub上にpushしておく必要があります。プライベートかパブリックかは問いません。

  1. WordPressのアクションフックを使って、記事投稿時にrepository dispatch eventでGitHub Actionsのワークフローを呼び出す
  2. GitHub Actionsのワークフローでyarn->gridsome buildを実行
  3. ビルド後にdistの中身をVPS(ホスティング先)にrsync

1の作業はWordPressのプラグインの作成が必要で、2,3の作業はGitHub Actionsでワークフローを定義する必要があります。

WordPressのプラグインを作成する

gistにNetlifyのWebhookを叩くプラグインがあったので、それを流用しました。ありがたいです。

GitHubのWebhookを呼び出すにあたり、repoにアクセス権のあるPersonal Access Tokenが必要になるので、ない場合は発行しておきます。

<?php
/**
 * Plugin Name: GitHub Actions build hook
 */

// 記事予約投稿時。私は使わないのでコメントアウト
// add_action('publish_future_post', 'nb_webhook_future_post', 10);

// 記事投稿時
add_action('publish_post', 'nb_webhook_post', 10, 2);

// 記事削除時
add_action('delete_post', 'nb_webhook_post', 10, 2);

// 固定ページ投稿時。私は使わないのでコメントアウト
// add_action('publish_page', 'nb_webhook_post', 10, 2);

//  記事更新時。私の環境では記事投稿時にupdatedのアクションも実行されるため、コメントアウト
// add_action('post_updated', 'nb_webhook_update', 10, 3);

function nb_webhook_future_post( $post_id ) {
  nb_webhook_post($post_id, get_post($post_id));
}

function nb_webhook_update($post_id, $post_after, $post_before) {
  nb_webhook_post($post_id, $post_after);
}

function nb_webhook_post($post_id, $post) {
  $header = [
    'Authorization: token {GitHubのPersonal Access Token}',
    'Accept: application/vnd.github.everest-preview+json',
    // phpのcURLはデフォルトでUAが設定されていない
    // GitHubのAPI実行にはUAが必須
    // http://developer.github.com/v3/#user-agent-required
    'User-Agent: WordPress_webhook_post'
  ];
  $data = [
    'event_type' => '{Workflowで使いたい名前}',
  ];
  if ($post->post_status === 'publish') {
    $url = curl_init('https://api.github.com/repos/{アカウント or organization名}/{リポジトリ名}/dispatches');
    curl_setopt($url, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($url, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($url, CURLOPT_HEADER, true);
    curl_setopt($url, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($url, CURLOPT_HTTPHEADER, $header);
    curl_exec($url);
  }
}
?>

GitHub Actionsでワークフローを定義する

特筆すべきことはないです。他人に知られたくない情報はsecretsに設定して、rsync用のアクションに Burnett01/rsync-deployments: GitHub Action for deploying code via rsync over ssh を使っています。

name: webhook-trigger

on:
  # 手動でも実行したいのでworkflow_dispatchを設定
  workflow_dispatch:
  repository_dispatch:
    types:
      - {WordPressプラグインのevent_typeに設定した名前}

jobs:
  build:
    name: build
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    # 私の環境では .envにWordPressのURLを設定している
    # - name: echo env
    #   run: echo "${{ secrets.API_URL }}" > .env
    - name: Install modules
      run: yarn
    - name: Run build
      run:  ./node_modules/.bin/gridsome build > /dev/null
    - name: rsync deployments
      uses: burnett01/rsync-deployments@4.1
      with:
        switches: -avzr
        path: dist/
        remote_path: ${{secrets.DEPLOY_PATH}}
        remote_host: ${{secrets.SSH_HOSTNAME}}
        remote_port: ${{secrets.SSH_PORT}}
        remote_user: ${{secrets.SSH_USERNAME}}
        remote_key: ${{secrets.SSH_PRIVATE_KEY}}

これで記事を投稿したときにGitHub Actions経由でビルド結果がrsyncされるようになりました。

問題点

このままでも特に困らないのですが、repository_dispatchがデフォルトブランチでしか使えないのはかなり痛いです。event_typeの値に応じてワークフロー内でブランチを切り替えるとか、工夫が必要です。
ワークフローをトリガーするイベント – GitHub Docs

Note: This event will only trigger a workflow run if the workflow file is on the default branch

そもそも

ホスティングにNetlifyVercelを使っている場合は、Webhook経由でビルドからデプロイまで勝手にやってくれるので、ここまで苦労しません。
Netlifyは運が悪いとSSL周りでトラブルを引きまくるのと、日本からだと遅いので個人的にはVercelを推しています。シンプルでとても使いやすいですし、情報量はNetlifyに劣りますが、ドキュメントを見れば大体わかるので今のところ困っていないです。

Gridsome周りでいくつか質問を受けたので、やる気が出たら詳細を記事にしようと思います。