はじめに

タイトルのとおりですが、意外と苦労したのでやり方をまとめておきます。

使う道具

  • CirleCI 2.0
  • markdown-pdf
    • MarkdownをPDFに変換するためのCLIツール
    • npmでインストール
  • gdrive
    • Googleドライブを操作するためのCLIクライアント
    • PDFのアップロードに使う
    • バイナリをダウンロードしてインストール
    • ソース(golang)をコンパイルしてインストール(後述)

具体的な方法

事前準備

CircleCIからGoogleドライブにファイルをアップロードするためには、サービスアカウント での認証が必要なので、以下のとおり準備しておきましょう。

  1. Google API Console でサービスアカウントを作る
  2. デプロイ先にしたいGoogleドライブ上のフォルダを、サービスアカウントに共有する image
  3. CircleCIのプロジェクト設定で、サービスアカウントの認証情報のJSON文字列を一行化して環境変数として登録する image

markdown-pdfでのPDF生成方法

例えば、src/ 配下にMarkdownファイル群が階層構造で格納されているとして、それらを同じ階層構造で build/ 配下に拡張子だけを .pdf に変えて出力したい場合、以下のようなシェルスクリプトで実現できます。

for file in `find src -type f` ; do
    name=`echo $file | sed -r 's/src\\/(.*)\\.md/\\1/'` # src/<ファイル名>.md から <ファイル名> を取り出す
    npx markdown-pdf -s css/style.css -o build/$name.pdf $file
done

-s css/style.css でレンダリングに使用するCSSファイルを指定しています。これは本来は必須ではありませんが、こちらで指摘されているとおり、デフォルトのCSSだと [text](url) というMarkdownが text (url) という表記に変換されてしまうため、これを回避するために最低限以下のCSSを当てる必要があります。

abbr[title]:after,
a[href]:after {
    content: "";
}

GitHub風のCSSを適用

markdown-pdfはMarkdownパーサーとして remarkable を使っており、remarkableはデフォルトで GFM(GitHub Flavored Markdown)をコンパイルできるようです

せっかくなのでGitHub風のCSSを適用して、それらしい見た目でPDFが生成されるようにしておきましょう。

とりあえず今回は泥臭く、github-markdown-cssのcss をコピペして .markdown-bodybody に置換しました。フォントサイズも必要に応じて変更するとよいでしょう。

body に置換すればいいと判断したのは、markdown-pdfのデフォルトのcss がそうなっていたからです。

ワンライナー化してpackage.jsonにscriptsとして追加

以下のようにワンライナー化して、npm scriptsから実行できるようにしておくと便利です。(ついでに、ビルド時に build/ 配下を一旦全削除するようにしてあります)

{
  "dependencies": {
    "markdown-pdf": "^8.1.1"
  },
  "scripts": {
    "build": "rm -rf build/* && for file in `find src -type f` ; do npx markdown-pdf -s css/style.css -o build/`echo $file | sed -r 's/src\\/(.*)\\.md/\\1/'`.pdf $file ; done"
  }
}

gdriveでのアップロード方法

gdriveコマンドで build/ ディレクトリを丸ごとGoogleドライブの特定のフォルダ配下にアップロードする方法は、以下のとおりです。

gdrive --config $(pwd) --service-account credential.json upload -p <ここにデプロイ先フォルダのID> -r build

credential.json が置かれている場所を --config で指定する必要があります。--config の値はデフォルトでは $HOME/.gdrive になっています。ここでは、credential.json がカレントディレクトリに置いてある想定で、 $(pwd) としています。

後述しますが、--service-account オプションは2018/05/14現在の最新版v2.1.0にはまだ入っていません。最新のソースコードをコンパイルしてインストールしないと使えませんので、ご注意ください。

CircleCIの設定ファイル

上記を踏まえて、CircleCIの設定ファイル .circleci/config.yml を書いてみます。

キャッシングなどを省いて処理の要点だけを書くと、以下のような内容になります。(書式の詳細については 公式リファレンス をご参照ください)

version: 2
jobs:

  # markdown-pdfを使ってPDFを生成するジョブ
  build:
    docker:
      - image: circleci/node
    working_directory: ~/wd
    steps:
      # 日本語のフォントがないと、PDF生成時に日本語部分がレンダリングされない
      - run: sudo apt-get update && sudo apt-get install fonts-ipaexfont -y
      - checkout
      - run: npm i
      - run: npm run build
      # ビルドの成果物(PDFが入っているディレクトリ)をキャッシュしてデプロイジョブに渡す
      - save_cache:
          key: build-{{ .Revision }}
          paths:
            - ~/wd/build

  # gdriveを使ってPDF(が入っているディレクトリ)をデプロイするジョブ
  deploy:
    docker:
      # gdriveをソースからコンパイルするのでgolang環境が必要
      - image: circleci/golang
    working_directory: ~/wd
    steps:
      # gdriveをインストール
      - run: go get github.com/prasmussen/gdrive
      # 成果物をリストア
      - restore_cache:
          keys:
            - build-{{ .Revision }}
      - deploy:
          command: |
            # 環境変数から認証情報を取得してJSONファイルに出力
            echo $GOOGLE_SERVICE_ACCOUNT_CREDENTIAL > credential.json
            # 成果物のディレクトリ名を日時でリネーム
            dirname=`date +%Y%m%d_%H%M%S` && mv build $dirname
            # 成果物のディレクトリを丸ごとデプロイ
            gdrive --config $(pwd) --service-account credential.json upload -p <ここにデプロイ先フォルダのID> -r $dirname

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build

build ジョブでPDFを生成して、deploy ジョブでGoogleドライブにデプロイしています。

日本語フォントが何も入っていない環境だとPDFを生成するときに日本語部分がレンダリングされないので、build ジョブのはじめで fonts-ipaexfont をインストールしています。インストールするフォントは何でもOKです。

deploy ジョブのほうでは、CircleCIのコンテナにサービスアカウントの認証ファイルを設置するために、事前準備で登録しておいた環境変数の中身をJSONファイルに書き出しています。また、build というディレクトリ名のままだと分かりにくいので、日時の名前にリネームしてからデプロイするようにしています。

gdriveの最新バージョンがリリースされた暁には

なお、gdriveをソースからコンパイルしてインストールしていますが、これはgdriveの --service-account 機能が まだバイナリとしてリリースされておらず、最新のソースを自分でコンパイルしないと使えない が故の措置です。(README からダウンロードできるv2.1.0には、この機能は入っていません)

v2.2.0(多分)がリリースされた暁には、インストールはバイナリをダウンロードしてくるだけでよくなるため、golang環境も不要になり、以下のようにジョブを1つにまとめることができるようになります。

version: 2
jobs:
  build:
    docker:
      - image: circleci/node
    steps:
      - run: sudo apt-get update && sudo apt-get install fonts-ipaexfont -y
      - checkout
      # gdriveのバイナリをダウンロードして、チェックサムを確認して、実行パーミッションをつける
      - run: |
          wget "<ここにgdrive-linux-x64のダウンロードURL>" -O gdrive
          [ `sha1sum gdrive | awk '{print $1}'` = '<ここにgdrive-linux-x64のshasum>' ]
          chmod +x gdrive
      - run: npm i
      - run: npm run build
      - deploy:
          command: |
            echo $GOOGLE_SERVICE_ACCOUNT_CREDENTIAL > credential.json
            dirname=`date +%Y%m%d_%H%M%S` && mv build $dirname
            ./gdrive --config `pwd` --service-account credential.json upload -p <ここにデプロイ先フォルダのID> -r $dirname

実装例

以下のGitHubリポジトリに実装例を上げてみたので、参考にしてみてください。

https://github.com/ttskch/markdown-pdf-googledrive-ci-sample

実際に動かすと、以下のように日時のフォルダ配下に成果物がデプロイされます。

image

おわりに

簡単なことのようで意外とつまずきポイントが多かったので自分のためのメモも兼ねてまとめてみました。
どこかの誰かのお役に立てば幸いです。