CircleCI 2.0でhugoのブログ生成を自動化する

このブログは hugo で生成している。生成過程を自動化したくて、CircleCIへ任せることにしようと思ったところ、そういえばCircleCI 2.0をまだ触っていないことに気付いたので、2.0で自動化した。

設定の書き方

1.0におけるcircle.ymlのように、設定ファイルを書いてそれに基づいた処理が行われるという点は変わらないものの、設定の書き方はガラッと変わって後方互換性は一切なくなっていた。

一応マイグレーションガイドがあるので、circle.ymlからステップ踏んで移行できるようにはなってる。

実行タスク

1.0ではCircleCIがリポジトリの言語から実行タスクを自動判定していたので、設定の書き方は基本的に「デフォルトの実行内容と異なることをやりたければ override する」という形だった。2.0ではデフォルトタスクがなくなり、すべて自前で書く形になった。

正直、デフォルトのタスクをそのまま使うことはほとんどなかったので、すべて自前で書ける方が何も考えずに済んで楽になった。

ジョブとワークフローという概念

タスクは1つの環境上で一気通貫に実行される形ではなくなり、実行したい内容を Job としてステップで分けて定義する形になった。 Job ごとに環境も分離されていて、それぞれDocker imageを定義して起動する。各言語で必要なimageは CircleCIが用意している が、それ以外のimageでももちろんよい。

Job をどの順番で実行するかは Workflow として定義する。テストのジョブが成功した場合のみデプロイのジョブを実行するなど、 Job 間の依存関係を定義することもできる。

キャッシュの使い所

Job ごとにDockerコンテナが起動する都合上、コンテナ間で同じファイルを共有したい場合や、実行の度に同じファイルを使うようなときにはキャッシュを利用する。前者としては、 git clone したソースコードをどの Job でも使いまわすようなとき、後者としては、依存するライブラリのダウンロードなどが考えられる。

キャッシュはコンテナ内のどのディレクトリを、何という名前でキャッシュするか、という形で定義する。すでに同名のキャッシュが存在する場合は、それを上書きすることはないので、このあたりが設計上肝になる。

キャッシュ名には 変数 (Template) が使えて、この使い方で「いつキャッシュするか」をコントロールできる。{{ checksum }} を使うと特定ファイルのチェックサムがキャッシュ名に入るので、Gemfileなどを指定すれば、これに変更があったときだけキャッシュを上書き=ライブラリの再ダウンロードが促せる。 {{ epoch }}を使えば実行時刻に応じたキャッシュ名となり、またキャッシュリストア時は最新の epoch が入ったキャッシュが選ばれるので、毎回ダウンロードし直すことになるソースコードのキャッシュに使える。

実装

実装を以下に置く。hugoなのでいわゆるソフトウェアのテストは回しておらず、 textlint で文章校正だけ行い、OKであれば build と deploy が走るようになっている。

textlintは当初全記事にかける形にしていたが、今までの記事ほとんどでNGが出てしまったので、「ブランチ名と一致するファイル名のmarkdown」にだけ実行する形にした。それでもデフォルトで使っていると結構厳しく感じるので、設定は改めたい。。

 1version: 2
 2
 3jobs:
 4  checkout_code:
 5    docker:
 6      - image: circleci/golang:1.8
 7    working_directory: ~/hugo
 8    steps:
 9      - checkout
10      - save_cache:
11          key: hugo-cache-{{ epoch }}
12          paths:
13            - ~/hugo
14
15  textlint:
16    docker:
17      - image: circleci/node:9.2.0
18    working_directory: ~/hugo/.circleci
19    steps:
20      - restore_cache:
21          keys:
22            - hugo-cache
23            - hugo-nodemodules-{{ checksum "package.json" }}
24      - run:
25          command: |
26            npm install
27            npm run textlint "../content/blog/${CIRCLE_BRANCH}.md"
28      - save_cache:
29          key: hugo-nodemodules-{{ checksum "package.json" }}
30          paths:
31            - ~/hugo/.circleci/node_modules
32
33  build:
34    docker:
35      - image: circleci/golang:1.8
36    working_directory: ~/hugo
37    steps:
38      - restore_cache:
39          key: hugo-cache
40      - run:
41          command: |
42            git submodule sync
43            git submodule update --init
44            go get github.com/gohugoio/hugo
45            git clone https://github.com/chroju/chroju.github.io public
46            rm -rf public/*
47            sudo cp /usr/share/zoneinfo/Japan /etc/localtime
48            hugo
49      - save_cache:
50          key: hugo-cache-public-{{ epoch }}
51          paths:
52            - ~/hugo/public
53
54  deploy:
55    machine:
56      enabled: true
57    working_directory: ~/hugo/public
58    steps:
59      - restore_cache:
60          key: hugo-cache-public
61      - run:
62          command: |
63            git config --global user.name chroju
64            git config --global user.email chor.chroju@gmail.com
65            git add --all
66            git commit -m "${CIRCLE_BRANCH} (Circle CI)"
67            git push git@github.com:chroju/chroju.github.io
68
69workflows:
70  version: 2
71  build_and_deploy:
72    jobs:
73      - checkout_code
74      - textlint:
75          filters:
76            branches:
77              ignore: master
78          requires:
79            - checkout_code
80      - build:
81          requires:
82            - checkout_code
83      - deploy:
84          filters:
85            branches:
86              only: master
87          requires:
88            - textlint
89            - build

hugo theme の変更

余談にはなるけど、今回のCircleCI実装に合わせて、 hugo のテーマもcocoaというものに変更した。

nishanths/cocoa-hugo-theme: Configurable, responsive blogging theme for Hugo

hugo のテーマ変更は初めてなのだけど、configの書き方がテーマによって少し違いがあり、そのまま移植という形にはできなかったのでちょっと時間がかかった。