とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

GitHub ActionsでMarkdownからPDFをジェネレート

社内でMarkdownをPDF化したい要望があり、かつ、GitHub上でファイルの管理からビルドまで完結できるといいよねっということで作ってみました。

github.com

しくみ

GitHubにChapterプレフィックスのMarkdownファイルをコミットすることでGitHub Actionsが発火し、MarkdownからPDFの変換が行われます。MarkdownからPDFへの変換にはPandocを使用しました。

GitHub Actionsのワークフローは以下の2つ

md2pdf-demo/.github/workflows/pandoc.yaml at main · VirtualTech-DevOps/md2pdf-demo · GitHub

md2pdf-demo/.github/workflows/build-container.yaml at main · VirtualTech-DevOps/md2pdf-demo · GitHub

.github/workflows/pandoc.yaml

このワークフローはMarkdownからPDFを生成します。

on:
  push:
    paths:
      - Chapter*.md
      - .github/workflows/pandoc.yaml

ChapterプレフィックスのMarkdownと、このワークフローのyaml自体に変更があった場合にワークフローが実行されます。

jobs:
  markdown_files:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.value }}
    steps:
      - uses: actions/checkout@v3
      - id: matrix
        run: echo -E "value=[$(ls -Qm Chapter*.md | sed 's/\.md//g' | tr -d ' \n')]" >> "$GITHUB_OUTPUT"

markdown_filesジョブはChapterプレフィックスのファイルの一覧から.md拡張子を削除した文字列をカンマ区切りの配列としてアウトプットするようにしています。["Chapter0","Cpater1",...]こんな感じにアウトプットされます。

  build-pdf:
    needs: markdown_files
    runs-on: ubuntu-latest
    strategy:
      matrix:
        value: ${{ fromJson(needs.markdown_files.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v3
      - uses: docker://ghcr.io/virtualtech-devops/md2pdf-demo:latest
        with:
          args: >-
            -o ${{ matrix.value }}.pdf
            -N
            --toc
            --toc-depth=3
            -F pandoc-crossref
            -H listing.tex
            --listings
            --highlight-style=tango
            -V documentClass=ltjarticle
            -V mainfont=IPAPGothic
            --pdf-engine=lualatex
            ${{ matrix.value }}.md
      - uses: actions/upload-artifact@v3
        with:
          name: ${{ matrix.value }}.pdf
          path: ${{ matrix.value }}.pdf

build-pdfジョブでMarkdownからPDFを生成しています。markdown_filesジョブで生成した配列をstrategy.matrixに渡しています。受け取った配列の要素数分ジョブが増殖して並列実行されます。GitHub ActionsのMatrixについては以下の公式ドキュメントをご覧ください。

docs.github.com

MarkdownからPDFを生成するためにdocker://ghcr.io/virtualtech-devops/md2pdf-demo:latestというコンテナをusesしています。このコンテナは次に説明するワークフローでビルドされています。中身は公式のpandocコンテナにフォントを追加したカスタムコンテナです。

          args: >-
            -o ${{ matrix.value }}.pdf
            -N
            --toc
            --toc-depth=3
            -F pandoc-crossref
            -H listing.tex
            --listings
            --highlight-style=tango
            -V documentClass=ltjarticle
            -V mainfont=IPAPGothic
            --pdf-engine=lualatex

この部分でpandocのオプションを指定しています。

  • -o アプトプットするファイル。
  • -N 目次に番号をつける。セクション(#から始まる行)の末尾に{.unnumbered}を記載すると番号なしの見出しをつけられる。
  • --toc 目次を自動生成。
  • toc-depth=3 目次に含めるセクションレベル。3を指定しているので###から始まるセクションが目次に表示される。
  • -F pandoc-crossref Pandoc ASTを変換するフィルタを指定。別のファイルフォーマットに変換するためのフィルタ。
  • -H listing.tex 引数に指定されているファイルをヘッダの末尾に挿入。指定されているlisting.texはリポジトリ直下にコミットされている。
  • --listings コードブロックにLaTeXのlistingsパッケージを適用。行の折り返しやシンタックスハイライトが可能になる。
  • --highlight-style=tango ソースコードのシンタックスハイライトスタイルを指定。
  • -V テンプレート変数を設定。
  • --pdf-engine=lualatex PDFの変換で日本語が使えるようにLuaLaTeXを指定。LuaTex-jaはコンテナにインストール済み。

.github/workflows/build-container.yaml

このワークフローはMarkdownをPDFにビルドするためのコンテナイメージをビルドします。

on:
  workflow_dispatch:
  push:
    branches:
      - main
    paths:
      - Dockerfile
      - .github/workflows/build-container.yaml

Dockerfileと、このワークフローのyaml自体に変更があった場合にワークフローが実行されます。また、workflow_dispatchも指定しているので手動でワークフローを実行することもできます。

jobs:
  build-container:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: metadata
        run: |
          echo "image=$(echo ${{ github.repository }} | tr '[A-Z]' '[a-z]')" >> "$GITHUB_OUTPUT"
          echo "tag=$(git rev-parse --short $GITHUB_SHA)" >> "$GITHUB_OUTPUT"
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          platforms: linux/amd64
          push: true
          cache-from: type=gha
          cache-to: type=gha,mode=max
          tags: |
            ghcr.io/${{ steps.metadata.outputs.image }}:latest
            ghcr.io/${{ steps.metadata.outputs.image }}:${{ steps.metadata.outputs.tag }}

build-containerジョブでコンテナイメージのビルドからghcrへのPushまでを実行しています。

    permissions:
      contents: read
      packages: write

ghcrにPushするためにpackages: writeを指定しています。このpermissions、デフォルトはwrite-allなんですが、1つでも値を指定すると他のスコープ(contentsとかpackages)がnoneになってしまいます。これではリポジトリのファイルをPullできないのでcontents: readもつけています。詳細については公式ドキュメントを参考にしてください。

docs.github.com

全体の流れは以下です。

PDFをビルドするためのコンテナイメージ

github.com

pandoc/extraというコンテナイメージにfonts-ipafontパッケージとHaranoAjiFontsフォントを追加でインストールしています。

ビルドしたコンテナイメージはGitHub Container Registryで公開しています。

github.com

いざ

MarkdownからPDFを生成するためのカスタムされたPandocコンテナが必要になるので、まずはコンテナイメージを作ります。コンテナイメージを作るには以下の画像の通り、Actionsタブを選択し左メニュのbuild pandoc containerを選択するとRun workflowが現れるので、ここからワークフローを実行します。

今回はちょうど新しいバージョンのフォントがリリースされていたのでDockerfileを変更してPushしてしまいました。

コンテナイメージがビルドできたらPDFのジェネレート準備は完了です。

ChapterプレフィックスのMarkdownファイルをコミットします。するとActionsが動きだし、ジョブが成功するとArtifactsにPDFがアップロードされます。

Chapterプレフィックスのmarkdownファイルを増やすとジョブが増え、並列で実行されるようになります。

まとめ

ChapterX.mdをコミットすることでPDFをジェネレートできるようになりました。PDFをビルドするためのツールやリソースをローカルに用意する必要はありません。手元ではテキストのみ書ければいいのです。PDFの構文についてはPandocの日本語ドキュメントに記載がありますので、こちらを参考にしてみてください。