えいのうにっき

a-knowの日記です

コード化したはてなブログリポジトリの更新を CircleCI 2.0 で自動化した

昨日、自分のはてなブログ(このブログ)の内容を GitHub で管理しはじめた、と書いた。

blog.a-know.me

コード化したとはいえ、はてなブログのエディタが使いやすく、僕もそれに慣れきってしまっていることなどもあり、ブログの更新などはいままでどおり、はてなブログの管理画面で実施するつもり。

となると、この状態のままだと、「はてなブログ側は更新されているのに、リポジトリ側がそれに追随できていない状態が発生しやすい」といった課題が残る。

理想的には、「はてなブログ側で記事の更新が発生したら、それをトリガーにリポジトリ側にも Pull Request が作成される」といった仕組みができるといいのだけど、さすがにそれはできないので、次点案として「はてなブログ側とリポジトリ側の差分を定期的にチェックし、差分があったら自動的に Pull Request を作成する」のはどうかと考えた。つまり、フロー的には以下のような感じ。

  1. 僕がはてなブログ管理画面でブログ記事を更新。
  2. 定期的に動作している仕組みが更新による差分を検知。追従するための Pull Request を自動的に作成。
  3. その Pull Request の内容を僕が確認し、問題なければマージする
    • これで master ブランチの内容とはてなブログの内容との差分がなくなる

上記の仕組みを実現するための手段として CircleCI 2.0 を候補に挙げ、あれこれ調べながらやってみたらなんとかそれっぽい仕組みを作ることができたので、今回はその内容を紹介する。ちなみに CircleCI 2.0 を触るのは今回が初めて(古い方のはずっと使ってる)。

やったこと

事前準備

CircleCI の基本的な使い方についてはここでは省略。対象のリポジトリ(今回だと https://github.com/a-know/blog.a-know.me )を CircleCI 側で Add Project する。

以前は circle.yml みたいな設定ファイル名だった気がするけど、今回作成を促されたのは .circleci/config.yml 。これを、ドキュメントなどを読みつつ作っていくわけだけど、CircleCI 2.0 からの新要素として Workflow がある。斜め読み・だいぶ適当に書くけど、jobs で定義した様々なジョブの実行制御をするためのものが Workflow 、だと思ってよさそう。

config.yml の作成

YAML を書く前に、今回 CircleCI にやってもらいたいことを整理する。

  1. Go環境のセットアップ
  2. GOPATH の設定
  3. blogsyncgo get
  4. blogsync 用の設定ファイルの作成
  5. リポジトリの checkout
  6. blogsync pull で はてなブログからの記事情報の取得
  7. リポジトリの内容と blogsync pull で取ってきた内容との差分のチェック
    1. で差分があった場合、それをもとに Pull Request を作成する

こんなかんじになる。

1 については、CircleCI が用意してくれている Go 環境用のコンテナイメージを利用すればいい。

2 以降を config.yml として素朴に書くと、こんなかんじ。

version: 2
jobs:
  # ジョブ名称は任意のものを付けられる。`build` という名前だとデフォルトジョブとして認識される?(自信なし)
  update_check:
    environment:
      - GOPATH: /home/circleci/go
    docker:
      - image: circleci/golang:1.9
    working_directory: /go/src/github.com/a-know/blog.a-know.me
    steps:
      # GOPATH の設定。environment だけじゃ無理っぽい? https://qiita.com/tomiyan/items/6142113011243c5b5cd1
      - run: echo 'export PATH=${GOPATH}/bin/:${PATH}' >> $BASH_ENV
      # blogsync 用の config ファイルを置く場所の作成
      - run: mkdir -p ~/.config/blogsync
      # blogsync 用の config ファイルの書き出し。コロンをエスケープするために使ったダブルクォートを tr コマンドで無理矢理削除している
      - run: echo -e "a-know.hateblo.jp\":\"\n  username\":\" a-know\n  password\":\" ${HATEBLO_PASSWARD}\ndefault\":\"\n  local_root\":\" /go/src/github.com/a-know/blog.a-know.me\n" | tr -d \" >> ~/.config/blogsync/config.yaml
      # blogsync の go get
      - run: go get github.com/motemen/blogsync
      # リポジトリからチェックアウト
      - checkout
      # GitHub への push をおこなうためには add-ssh-keys が必要らしい。see also http://made.livesense.co.jp/entry/2017/11/07/090000
      - add-ssh-keys:
          fingerprints:
            - "xx:xx:xx:xx:xx:xx:xx:xx"
      - run:
          name: Blog update check and create PR
          command: |
            if [ "${CIRCLE_BRANCH}" = "master" ]; then
              # はてなブログ側からの情報を取得
              blogsync pull a-know.hateblo.jp
              # リポジトリにない内容のものが pull されてきているかどうかを git status で判定。see also http://made.livesense.co.jp/entry/2017/11/07/090000
              if [ -n "`git status -sb | grep entry`" ]; then
                # これ以降はブランチを作って PR を作成、送信している
                BRANCH=blog-update-`date -u "+%Y%m%d"`
                git config --global user.email ${MY_EMAIL}
                git config --global user.name ${MY_NAME}
                git checkout -b ${BRANCH}
                git add .
                git commit -m "Automatic blog update `date -u '+%Y-%m-%d'`"
                if git push ${CIRCLE_REPOSITORY_URL} ${BRANCH}
                then
                  curl \
                    --header "Accept: application/vnd.github.v3+json" \
                    --data "{\"title\": \"${BRANCH}\", \"head\": \"${CIRCLE_PROJECT_USERNAME}:${BRANCH}\", \"base\":\"${CIRCLE_BRANCH}\" }" \
                    https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/pulls?access_token=${GITHUB_ACCESS_TOKEN}
                fi
              fi
            fi

別途 CircleCI 側に設定が必要な環境変数は以下。プロジェクトごとの設定から Environment Variables で設定可能。

  • HATEBLO_PASSWARD
    • はてなブログ Atom Pub API のパスワード。はてなブログの管理画面でわかる
  • MY_EMAIL
    • PRを作るGitHubアカウントのメールアドレス。今回は面倒だったので自分自身のアカウントから PR を作ることにした。
  • MY_NAME
    • PRを作るGitHubアカウントの名前。
  • GITHUB_ACCESS_TOKEN

定期実行のための Workflow を定義する

CircleCIO 2.0 の Workflow の triggers キーが schedule オプションをサポートしており、これを使えば cron ライクに CircleCI Workflow を起動できる。

circleci.com

今回のケースだと↓こんな感じ。

workflows:
  version: 2
  nightly:
    triggers:
      - schedule:
          cron: "30 * * * *"
          filters:
            branches:
              only:
                - master
    jobs:
      - update_check

上記の内容で、

  • 毎時30分に
  • master ブランチに対してだけ
  • update_check ジョブを実行する

という定義(になってるはず)。ちなみに */1 */20 みたいなステップシンタックスは現時点ではサポートされていないようなので注意。

ここまでやって、試しにはてなブログ側に下書きを保存して時間が来るのを待ってみる(blogsync は下書き保存したエントリも取ってきてくれるので便利&注意)。

f:id:a-know:20180304143915p:plain

動き出した!

f:id:a-know:20180304143939p:plain

作られた!

f:id:a-know:20180304143956p:plain

内容も問題なし!

気になる料金

CircleCI は、Public Repository に対しては1コンテナ(※同時実行数)・1500分/月 までは無料で利用可能。

今回作った Workflow は一回の実行が約30秒・それが毎時間実行されるので、

0.5分 x 24時間 x 30日 = 360分

だいぶ余裕を持って使えそう。

感想

以前より CircleCI は使っていたのだけど、今回2.0の正式リリースもあり、そろそろキャッチアップしなきゃなあとは思っていたので、そのための取り組みとしては良い内容のものができたと思う。

前にも 自動で・定期的に CI を実行させてリポジトリが "腐る" のを防ぐ with CircleCI - えいのうにっき とかで似たような仕組み(自動的に bundle update してくれる君)を構築したこともあり、「いけるやろ!」とは思っていたけど、前回は heroku との組み合わせでなければうまくできなかったことも今回は CircleCI の機能だけで完結できるようになり、便利な世の中になったなぁ、などと思ったりもした。

CircleCI 2.0 のもうひとつの特長として「任意のコンテナをCIに利用できる」ことがあると思っていて、今回でいうと、blogsync の go get だったりその設定ファイルの作成だったりを、予めコンテナイメージとして固めておいてやる・それを利用することで、ジョブの実行時間をもっと短縮させることはできそう。

あとは textlint とか mixed content のチェック(来る HTTPS 化に備えて!)みたいな、本来のCIっぽいことを、master ブランチ以外のブランチに対しても徐々に設定していってみたいところ。僕は、ブログを投稿するたびに Mackerel のサービスメトリックグラフにグラフアノテーションの投稿を手動でやってたりするので、それもCIフローに組み込めたら楽ができそう、とか考えたりもしてる。

そもそも、はてなブログの管理画面を使わずに blogsync push でブログの更新をおこなうようなフローにすれば、フロー全体をもっと綺麗にすることもできる。

  • ブログの下書きを push、Pull Request にする
  • 内容をレビューし、マージする
  • マージされたものを blogsync push させる(CircleCI に)
  • リポジトリの内容とはてなブログ側の内容の乖離を scheduled workflow でチェックする

こんなかんじかな。ただ、はてなブログの管理画面、便利なんだよな......。。(リンクや画像の埋め込みとかが)

参考にさせていただきました