はてなブログ作成から投稿までを自動化したGitHub Actionsのワークフロー
はじめに
こんにちは、M-Yamashitaです。この記事は、GitHub Actions Advent Calendar 2022 の23日目の記事です。投稿が遅れてしまいすみません。
今回の記事は、はてなブログの下書き作成、更新、公開までをGitHub Actionsで自動化した話です。
以前「自動化大好きエンジニアLT会 - vol.9」でこの自動化について登壇しました。この話では、スライドで紹介できなかった実際のワークフローとその説明について伝えます。
この記事で伝えたいこと
- 各ワークフローの紹介
前提
はてなブログでは、はてなブログAtomPubが公開されています。ここでは、はてなブログの記事の参照、作成、公開、削除できるREST APIが公開されています。
このREST APIのCLIクライアントとなるものがblogsyncとなります。今回の自動投稿では、GitHub Actionsのワークフローに加えて、OSSのblogsyncを使っています。
ワークフローの紹介
はてなブログの下書き作成、更新、公開を行なうワークフローやconfigファイルを以下リポジトリにて公開しています。
以降の説明では、このリポジトリを使用して詳細を説明します。
ディレクトリ構成
はてなブログに自動投稿するためのリポジトリ構成は以下のとおりです。
.
├── .github
│ └── workflows
├── blogsync.yaml
└── draft.md
blogsync.yaml
、draft.md
についての説明は後述します。
blogsync用の設定ファイル
blogsync.yaml
はblogsyncを使用する際に必要な構成ファイルです。この中身は以下のとおりです。
m-yamashii.hatenablog.com:
username: xxxxx
password: $HATENA_API_KEY
default:
local_root: entries
それぞれの項目について、blogsyncのREADMEから引用します。
キー(例: motemen.hatenablog.com ): ブログのドメイン。はてなブログのダッシュボードからブログの設定画面などを開いたとき、URL に含まれる文字列です。技術的には AtomPub API における「ブログID」になります。
"default" という名前のキーは特別で、すべてのブログの項目のデフォルト値として扱われます。
<blog>.username: そのブログに投稿するはてなユーザの ID。
<blog>.password: そのブログに投稿するための API キー。はてなユーNザのパスワードではありません。ブログの詳細設定画面 の「APIキー」で確認できます。
<blog>.local_root: ブログのエントリを格納するパスのルート。
password
にはAPIキーをそのまま載せるわけにはいかなかったので、$HATENA_API_KEY
と置いています。なお、APIキー自体はGitHub secretに保存しています。この$HATENA_API_KEY
はGitHub Actions実行時に、secretの値に変えるようにしました。詳しくは後述します。
下書き記事について
はてなブログの下書きをGitHub Actionsで自動作成するにあたり、下書きのテンプレートをdraft.md
として用意しました。
中身はタイトルとサンプル文章を載せるシンプルな下書きテンプレートとなっています。
---
Title: xxxxx
---
テスト文章です。
各ワークフロー説明
各ワークフローでは、yamlをそれぞれ載せており、説明が必要と思われる部分のみ記載しています。
そのため、チェックアウトやインストールなどのステップについての説明は省いています。
はてなブログの下書き作成
ワークフロー開始トリガー
on: create
jobs:
create-blog:
name: Create draft blog
if: ${{ contains(github.ref, 'create-blog') }}
runs-on: ubuntu-latest
下書き作成では、ブランチ作成、かつブランチ名に指定名を持つ場合をトリガーにこのワークフローを実行させます。
もし指定名を持つという条件を持たせないと、記事作成以外の目的でブランチを作成した場合でもワークフローが実行されてしまいます。その実行は意図に反した動きなので、ブランチ名が指定名の場合という制限を持たせています。
configファイルのAPIキー置換
- name: Replace secrets key to secrets value
uses: jacobtomlinson/gha-find-replace@v2
with:
find: "$HATENA_API_KEY"
replace: "${{ secrets.HATENA_API_KEY }}"
include: blogsync.yaml
regex: false
このステップでは、blogsync用の設定ファイルであるconfig.yaml
のAPIキー($HATENA_API_KEY
)を、GitHubのsecretに保存しているAPIキーに置換しています。
プライベートリポジトリにしておけば、configファイルにAPIキーをハードコードし、このステップをなくしても良いのかもしれませんが、コードの漏洩リスクがあるため、secretに保存したAPIキーを参照するにようにしています。
はてなブログの下書き作成
- name: Post hatena draft blog to hatena blog
run: |
~/go/bin/blogsync post --draft m-yamashii.hatenablog.com < draft.md
このステップでは、はてなブログの下書きをはてなブログ上に投稿します。
blogsync post
コマンドを使うことで、テンプレート記事(draft.md
)を基に、下書きをはてなブログ上に作成できます。
コマンドを実行すると、はてなブログに投稿した下書きブログが、entries/m-yamashii.hatenablog.com/entry/2022/10/10/073657.md
のようなファイルパスで、GitHub Actionsのランナー上に作成されます。
Run ~/go/bin/blogsync post --draft m-yamashii.hatenablog.com < draft.md
POST ---> https://blog.hatena.ne.jp/m_yamashii/m-yamashii.hatenablog.com/atom/entry
201 <--- https://blog.hatena.ne.jp/m_yamashii/m-yamashii.hatenablog.com/atom/entry
store entries/m-yamashii.hatenablog.com/entry/2022/10/10/073657.md
作成されたmdファイルの上部には、YAML Frontmatter形式のメタデータが記載されます。
---
Title: xxx
Date: 2022-10-10T07:36:57+09:00
URL: https://m-yamashii.hatenablog.com/entry/2022/10/10/073657
EditURL: https://blog.hatena.ne.jp/m_yamashii/m-yamashii.hatenablog.com/atom/entry/xxxxx
Draft: true
---
この各項目の説明については、blogsyncのREADMEから引用します。
Title: エントリのタイトル。
Date: ブログに表示されるエントリの投稿日時。2006-01-02T15:04:05-07:00 といったフォーマットを期待しています。
URL: エントリの URL。これは自動的に与えられ、書き換えても効果はありません。
EditURL: エントリを一意に区別する URL。名前のとおり、AtomPub の編集用の URL です。
Category: エントリーのカテゴリの配列
Draft: この値が "yes" のとき、下書きとして扱われます。
この下書きブログは、ユーザーでブログを更新するために必要になります。そのため、次ステップでコミット、プッシュをします。
作成された下書きをコミット
- name: Commit draft blog
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Create draft blog.
file_pattern: 'entries/**/*.md'
commit_author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
前述のステップで作成された下書きブログを、stefanzweifel/git-auto-commit-action
アクションを使用してコミットします。
コミット対象のファイルは、file_pattern
に正規表現を使用することで指定できます。
ワークフロー作成し始め当初では、作成された下書きブログのみを指定する方法を考えていました。ですが、以下2点の理由によりentries/**/*.md
のような正規表現の指定にしました。
-
blogsync post
コマンドを行なうステップにおいて、下書きブログのファイルパス取得が難しそうであること - このジョブで新たに作成されるファイルは下書きブログ1つのみであるため、正規表現での指定にしたとしても取得されるファイルは1つのみとなる
下書き更新
ブランチで変更された記事のファイルパス取得
- name: Get changed files in the entries folder
id: changed-files
uses: tj-actions/changed-files@v34
with:
files: |
entries/**
tj-actions/changed-files
は、変更されたファイルパス一覧を取得してくれるアクションです。
ここではfilesにentries/**
を指定しているので、このアクションはentries
ディレクトリ以下の変更ファイルをすべて取得します。
このワークフローを実行するブログ作成ブランチでは、変更されるファイルは基本的にentries
配下のmdファイル1つのみなので、この方法を採用しています。
このアクションで取得した変更ファイルは、後述の「はてなブログの下書き更新」でsteps.changed-files.outputs.all_changed_files
の形式で使用します。
はてなブログの下書き更新
- name: Update blog in hatena
run: |
~/go/bin/blogsync push ${{ steps.changed-files.outputs.all_changed_files }}
blogsync post
コマンドを使用して、はてなブログの下書きを更新します。ここで使用する更新対象の記事は、前述の取得した変更ファイルとなります。
全体公開
投稿日時の修正
- name: Remove Date value from draft blog
uses: jacobtomlinson/gha-find-replace@v2
with:
find: 'Date: \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d[+-]\d\d:\d\d'
replace: "Date:"
include: ${{ steps.changed-files.outputs.all_changed_files }}
regex: true
このステップでは、下書きブログに記載されているDate
項目の日時を、ワークフロー実施時の日時に修正します。
Date
はブログに表示されるエントリの投稿日時です。はてなブログの画面上では以下画像の部分になります。
このDate
は下書きブログ作成のワークフローで作られているので、このステップで修正しないと、下書きブログ作成のワークフロー実行日時が投稿日時となってしまいます。
修正に使用するファイルは、tj-actions/changed-files
アクションで取得したmdファイルを使用します。
下書きフラグを公開フラグに変更
- name: Replace draft flag to false
uses: jacobtomlinson/gha-find-replace@v2
with:
find: "Draft: true"
replace: "Draft: false"
include: ${{ steps.changed-files.outputs.all_changed_files }}
regex: false
このステップでは、下書きブログに記載されているDraft
項目のフラグをtrue
に変更します。
この変更をしてblogsyncを行なうことで、下書きブログを全体公開にできます。
公開したブログをgit管理下にあるオリジナルのブログに上書き
run: |
latestBlogFilePath=`git ls-files --others --exclude-standard`
cat $latestBlogFilePath > ${{ steps.changed-files.outputs.all_changed_files }}
git mv -f ${{ steps.changed-files.outputs.all_changed_files }} $latestBlogFilePath
前ステップのblogsync push
コマンド実行した場合、最新ブログのファイルが新しく作成されます。
このステップ実行直前では、以下のような2つのファイルがあります。
- (1)GitHub Actionsのワークフローで作成され記事を更新してきたもともとのブログ
- ブログのファイルパスには、下書き作成ワークフローで作成された日時がある(例:
entries/entries/m-yamashii.hatenablog.com/entry/2022/10/10/073657.md
) - git管理化
- ブログのファイルパスには、下書き作成ワークフローで作成された日時がある(例:
- (2)blogsync pushコマンドにより新しく作成されたブログ
- ブログのファイルパスには、この全体公開ワークフローで作成された日時がある(例:
entries/entries/m-yamashii.hatenablog.com/entry/2022/10/11/123456.md
) - git管理外
- ブログのファイルパスには、この全体公開ワークフローで作成された日時がある(例:
(2)のブログをコミットし、(1)のブログを削除する方法もありますが、git履歴がなくなる問題があります。
そのため、(2)のブログ内容を(1)のブログにcat
で移し替え、git mv
で(1)を(2)にリネームすることで、今までの履歴を持たせられます。またgit mv
することで投稿日時とファイルパスの日時を一致させられるメリットもあります。
この一連のコマンドを実行した後、次ステップで(2)をコミットすることで、ワークフローは完了となります。
制限・仕様事項
スライドでも説明したとおり、はてなブログ上で直接編集した場合、GitHub上のブログとのマッピングができない問題があります。
また、GitHub上の下書きブログに画像をアップロードして公開した場合、公開したブログで画像が見えてしまうようです。
※登壇資料では、画像が見えない制限ありと記載していました。
これはGitHub側にて、アップロードしたURLがプライベートリポジトリであっても全体公開となってしまう仕様のためのようです。
参考資料:
おわりに
この記事では、GitHub ActionsとOSSのblogsyncを使って、はてなブログの下書き作成、更新、公開を自動化したワークフローの話をしました。
ワークフローのコードだけでは把握しづらい意図や背景を紹介できたかなと思います。
この記事が誰かのお役に立てれば幸いです。
Discussion