Gatsby.jsで作った静的サイトで予約投稿を実現する
ちょっと前にGatsby.jsを使ってブログを運用しているのですが,予約投稿ができなくて困っていました.
現状の構成はGitHubのプライベートリポジトリでブログを管理して,Netlifyにデプロイしています. これだとGitHubのmasterにコミットしたタイミングでデプロイされてしまうので,予約投稿ができませんでした.
そこで,Firebase Functionを使って自動的にプルリクエストをmergeし,予約したタイミングで自動でデプロイされるようにしてみました.
ブログ作成時にPRを作成し,Firebase Functionが定期的にPRをチェックし,投稿時刻を過ぎていたら自動でマージしています.
投稿時刻はPRのタイトルをreserve-YYYYMMDD-hhmmss
のようにしておき,時間が過ぎていたらマージされます.
投稿時刻にスケジューラを実行させるようなフローも考えられますが,自分が普段触るのはGitHubに統一しておきたかったので,このような構成にしました.
環境
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.5
BuildVersion: 18F132
$ firebase --version
7.6.2
$ node --version
v8.11.4
手順
Firebaseプロジェクトの作成
まず,Firebseのプロジェクトを新規作成します. どちらでもよいと思いますが,ファンクションだけ利用するのでGoogle Analyticsは使用しないオプションで作成しました.
その後,firebase-toolsでログインして,予約投稿ファンクション用のプロジェクトを作成します.
$ firebase login
$ mkdir blog-auto-merger && cd blog-auto-merger
$ firebase init functions
(略)
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: <さきほど作成したプロジェクト>
(略)
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
✔ Wrote functions/package.json
✔ Wrote functions/.eslintrc.json
✔ Wrote functions/index.js
✔ Wrote functions/.gitignore
後述するように,Firebase FunctionをGoogle Cloud Schedulerで定期実行するため,プロジェクトをBlaze(従量課金プラン)にアップグレードしておく必要があります.
また,Google Cloud Schedulerの実行のために,GCPリソースロケーションを選択しておきます.
リソースロケーションはどこでも良いですが,GitHubにアクセスするだけなので,私はus-east1
にしておきました.
GitHubアクセストークンの発行
GitHub API経由でPRの取得とマージを行うため,アクセストークンを発行します.こちらのページで発行できます.
スコープはrepo
だけでOKです.
発行されたアクセストークンは,次の環境変数設定で利用します.
Firebase Functionの環境変数設定
GitHubのユーザ,リポジトリ,アクセストークンはファンクションに含めず,GCPのRuntime Configというサービスで環境変数として設定しました. Runtime Configの使い方は以前Qiitaに書いたので参照してください.
参考: Cloud Functionsで環境変数を設定する方法 - Qiita
実際には次のように環境変数を設定します.
$ gcloud --project <your-project-name> beta runtime-config configs create github
Created [https://runtimeconfig.googleapis.com/v1beta1/projects/<your-project-name>/configs/github].
$ gcloud --project <your-project-name> beta runtime-config configs variables set OWNER <your-github-owner> --config-name github --is-text
Created [https://runtimeconfig.googleapis.com/v1beta1/projects/<your-project-name>/configs/github/variables/OWNER].
$ gcloud --project <your-project-name> beta runtime-config configs variables set REPO <your-github-repo> --config-name github --is-text
Created [https://runtimeconfig.googleapis.com/v1beta1/projects/<your-project-name>/configs/github/variables/REPO].
$ gcloud --project <your-project-name> beta runtime-config configs variables set TOKEN <your-github-token> --config-name github --is-text
Created [https://runtimeconfig.googleapis.com/v1beta1/projects/<your-project-name>/configs/github/variables/TOKEN].
<your-project-name>
, <your-github-*>
は自身の環境に置き換えてください.
Firebase Function用コードの作成
完成コードはこちらに置いています.→ ottijp/blog-auto-merger
GitHubのPRの取得とマージは,GitHub APIのPull Requestsを使っています
async getPulls () {
return await axios.get(`${BASE_URL}/repos/${this.owner}/${this.repo}/pulls?access_token=${this.token}`)
}
async mergePulls (numbers) {
await Promise.all(numbers.map(number => {
return axios.put(`${BASE_URL}/repos/${this.owner}/${this.repo}/pulls/${number}/merge?access_token=${this.token}`)
}))
}
マージすべきタイミング(予約時刻を過ぎた)かどうかはPRのタイトルから判断しています.
static _shouldMergePull (prName, currentDate) {
const m = prName.match(/reserve-([0-9]{4})([0-9]{2})([0-9]{2})-([0-9]{2})([0-9]{2})([0-9]{2})/)
if (!m) return false
const reservedDate = Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}+09:00`)
return reservedDate <= currentDate
}
その他は完成コードを見てください.
デプロイ
次のようにデプロイします.
$ firebase use <your-project-name>
$ npm run deploy
次のようなエラーが表示される場合,前述のGCPのリソースロケーションが設定されていないので,設定してください.
Error: Cloud resource location is not set for this project but scheduled functions requires it. Please see this documentation for more details: https://firebase.google.com/docs/projects/locations.
予約投稿する
予約投稿用のブランチに記事を投稿し,PRを作っておきます.
ここでは2019/11/09 17:00に投稿したいので,PR名をreserve-20191109-170000
としています.
その後,2019/11/09 17:00:00以降のFirebase Functionの実行で,自動マージされました.
気になること
こちらのページに次のような注記があるのですが,GAEアプリ分の料金が課金されるということなんでしょうか? 料金がどの程度になるのかちょっと不明なので,あとでわかったら追記します.
重要: Cloud Scheduler では、プロジェクトに Google App Engine(GAE)アプリが必要です。このアプリの設定中には、プロジェクトのデフォルトの Google Cloud Platform(GCP)リソース ロケーションを選択するように求められます。このロケーションは、プロジェクト内のロケーション設定が必要な GCP サービスで使用されます。具体例としては、デフォルトの Cloud Storage バケットや Cloud Firestore データベースなどがあります。