Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

GitHub Actionsを使ってマークダウンをPDFに変換する

こんにちは、お散歩の時に道に落ちていたごみを見た娘が「こんな所に捨てて駄目ね、この美しい私達の街を守らないと!」と言ってきてビックリした木村です。何事かと思ったらどうも何かのTVのセリフを覚えてたようです。

この記事はInfocom Advent Calendar 2022の14日目の記事です。

弊社はGitHubの公式パートナーで、GitHub Enterpriseも取り扱っています。

www.alterbooth.com

そういったこともあり、今年のオルターブース Advent Calendar 2022はGitHub関連の記事も沢山エントリーしていますので皆さん是非ご覧いただけたらと思います。

adventar.org

せっかくなので私もGitHub Actionsでやってみたことを書こうかと思います。

報告書作成のワークフロー

皆さん、普段の報告書はどういう形で残されていますか?Wordで書かれたり、何らかの文書管理システムで書かれたりというケースは多いかと思いますが、弊社では基本的にマークダウン形式で記載し、gitで管理しています。そして、お客様に提出する際はPDFに変換して送付しています。

今年に入って社内のワークフローをGitHubに寄せていこうという動きの中で、この報告書についてもGitHubで管理することになりました。ワークフローは以下のような感じです。

  • 報告書を保存するリポジトリを作成する(初回のみ)
  • 報告書を作成するときはIssueを作成し、そこから作成用のブランチを作って報告書を書く
  • 報告書に必要な情報のやり取りや質問はIssue内で行う
  • マークダウン形式で報告書を書き終えたら、プロジェクトの責任者をレビュアーとしてプルリクエストを作成する
  • 責任者は問題なければプルリクエストをマージする
  • マージされた文書をPDFに変換し、顧客に送付する
  • 提出したらIssueをクローズする

一般的なgitでの開発フローをそのまま文書作成にも利用している感じですね。
このフローの「PDFに変換する」という部分は、これまではVisualStudio CodeのMarkdown PDF拡張機能を使って手動でやっていたのですが、せっかくGitHubでやってるので自動化しようと思ってちょっと試してみました。

作成するワークフロー

今回、以下のようなワークフローを作成することにします。

  • masterブランチへのコミットをターゲットに動かす
  • コミットで更新された全てのマークダウンファイルについて、PDFに変換する
  • 変換は独自に準備したコンテナで行う
  • 変換にはmd-to-pdfを使う
  • 最後に更新されたReleaseに、docs-yyyymmdd-id.tgzという名前でアップロードする
  • subdir1/subdir2/sample.mdのようなファイル名の場合、subdir1_subdir2_sample.pdfというファイル名になる
  • ファイル名やディレクトリ名は日本語も可

PDFファイルはアーティファクトにアップロードするだけでも良かったのですが、Releaseへのファイル配置も試してみたかったので盛り込んでいます。

コンテナイメージの準備

md-to-pdfを動かすために、Node.jsの最新版のイメージをベースにして必要なライブラリ等をインストールしていきます。
Dockerfileは以下のようになります。

FROM node:latest
RUN apt update -y
RUN apt install -y libnss3-dev
RUN apt install -y libatk1.0-0
RUN apt install -y libatk-bridge2.0-0
RUN apt install -y libcups2
RUN apt install -y libdrm2
RUN apt install -y libxkbcommon-x11-0
RUN apt install -y libxcomposite-dev
RUN apt install -y libxdamage-dev
RUN apt install -y libxrandr-dev
RUN apt install -y libgbm-dev
RUN apt install -y libasound2
RUN apt install -y fonts-noto
RUN apt install -y fonts-ipafont-gothic
RUN apt install -y locales
RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen
RUN locale-gen
RUN update-locale LANG=ja_JP.UTF-8
RUN yarn add md-to-pdf
COPY md2pdf.sh /usr/bin/

apt installは1行にまとめられると思いますが、必要なライブラリを確認しながら作成した状況を残すためにこの形にしています。
最後に/usr/bin/にコピーしている、変換を行うシェルスクリプトmd2pdf.shはこちらです。引数で渡されたファイルに対して、出力ファイル名を調整しながらmd-to-pdfを動かす感じですね。

#!/bin/bash
export LANG=ja_JP.UTF-8
for file in $@;do
    suf=`echo $file | sed 's/^.*\.\([^\.]*\)$/\1/'`
    if [ $suf = 'md' ] ; then
        outfile=`echo $file | sed 's/\//_/g' |sed 's/\.md$/\.pdf/i'`
        cat $file | npx md-to-pdf --launch-options '{"args": ["--no-sandbox"]}' --md-file-encoding utf-8 > $outfile
    fi
done
tar cvfz docs.tgz `find . -name '*.pdf'`

このコンテナイメージを適当なところに置きます。私はdocker hubに置いています。

ワークフローを作成する

ワークフローを以下のように作成します。

name: CI
on:
  push:
    branches: [ master ]
jobs:
  container-test-job:
    runs-on: ubuntu-latest
    container:
      image: kenichirokimura/md2pdf:latest
      options: --cpus 1
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Git config
        run: git config --local core.quotepath false
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v10.1
      - name: Md2pdf
        run: /usr/bin/md2pdf.sh ${{ steps.changed-files.outputs.all_modified_files }}
      - name: Gets latest created release info
        id: latest_release_info
        uses: jossef/action-latest-release-info@v1.1.0
        env:
          GITHUB_TOKEN: ${{ github.token }}
      - name: Upload Release Asset
        id: upload-release-asset
        uses: WebFreak001/deploy-nightly@v1.1.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.latest_release_info.outputs.upload_url }}
          release_id: ${{ steps.latest_release_info.outputs.id }}
          asset_path: ./docs.tgz
          asset_name: docs-$$.tgz
          asset_content_type: application/x-gzip

先ほどのコンテナイメージをjobs.container-test-job.container.imageで指定しています。

Get changed filesステップで、今回のコミットで変更になったファイル名の一覧を取得します。取得にはtj-actions/changed-filesを使います。

Md2pdfステップでは、Get changed filesステップの結果(変更になったファイル名の一覧)をsteps.changed-files.outputs.all_modified_filesで取得して、コンテナ内のmd2pdf.shシェルスクリプトを起動します。

Gets latest created release infoステップでは、最新のリリース情報を取得します。取得にはjossef/action-latest-release-infoを使います。

最後にUpload Release Assetステップで、Gets latest created release infoステップで取得したアップロード先のURLならびにリリース先を使って、作成したファイルをアップロードします。アップロードにはWebFreak001/deploy-nightlyを使います。このアクションの機能で、Releaseにdocs-{yyyy}{mm}{dd}-{id}.tgzというファイルがアップロードされます。

なお、このワークフローでは「最後に更新したReleaseに対してアップする」という動作のため、事前にReleaseを1つ作っておく必要がありますのでご注意ください(手抜き)。

まとめ

今回作ったワークフローで、マークダウンファイルのPDFへの変換を自動化できました。現在はPDFをReleaseにアップロードしていますが、いずれは作成したPDFをお客様に提出する部分まで自動化できればと思っています。

ここまで作ったものを、以下のリポジトリで公開しています。実際にアクションを動かしてReleaseにPDFを設置もしていますので、興味のある方はそちらを参照したり、forkして色々と試してみてください。

github.com

皆様の参考になれば幸いです。