GitHub PagesのSourceにGitHub Actions(beta)を指定し、AsciiDocとSwaggerUIを公開する構成

2024.03.04

はじめに

GitHub PagesへデプロイするSourceの指定先は2つあります。それぞれ以下のような違いがあります。

指定先 概要
Deploy From a branch ホスティングするビルド成果物をコミット、GitHub Pagesで公開
GitHub Actions(beta) GitHub Actionsでビルド成果物を作成、GitHubのartifactとしてアップロードし、GitHub Pagesで公開

※ リポジトリのページから「Settings」->「Source」設定確認可能

Source指定にDeploy From a branchと比較し、GitHub Actions(beta)を利用するメリットには以下があります。

  • ホスティングのために用意しているブランチやビルド成果物が不要になり、認知負荷を下げられる
  • ビルド資材をコミットする作業がなくなる
  • ビルド資材の容量が大きい場合、pullやcheckoutにかかる時間を削減できる(モノレポの場合コードに対するCIにも影響する)

設定方法

前提

今回は以下をホスティングする想定にします。

  • Swagger UI
  • AsciiDoc with PlantUML

何らかのHTTP REST APIの仕様書を作る想定です。

リポジトリテンプレート

リポジトリテンプレートは公開しています。用途に応じてカスタマイズして頂けたらと思います。

ファイル/フォルダ構成

$ exa -L 4 -T --git-ignore
.
├── bin
│  └── build.sh ... ./documents配下をビルドし、./distディレクトリに出力する
├── documents
│  ├── assets
│  │  ├── swagger-ui.html ... SwaggerUIのHTML。同一ドメインの./api.ymlにアクセスし、内容APIの内容をレンダリング
│  │  └── VL-Gothic-Regular.ttf ... PlantUMLの文字化け防止
│  └── docs
│     ├── blog-api
│     │  ├── api.yml ... APIのOpenAPI定義
│     │  └── spec.adoc ... APIの仕様を書いたドキュメント
│     └── index.adoc ... 仕様書のルートページ
└── README.md

リポジトリの設定

Settings -> Source から、GitHub Actions(beta) を選択

ビルド設定

documents/docs配下をビルドして、./distに結果を出力するシェルです。個人的にはシェルスクリプトではなくzxやdaxを使う方が推奨だったりします。

bin/build.sh

#!/bin/bash

BIN_PATH="$(dirname -- ${0})"
WS_ROOT_PATH="$BIN_PATH/../"
DIST_PATH="$BIN_PATH/../dist"
DOCUMENT_DIR="docs"

rm -rf $DIST_PATH

# AsciiDocの再起的ビルド
for filename in $(find ./docs -type f |cut -d/ -f3- | grep .adoc); do
  asciidoctor \
    -a imagesdir@=images \
    -a imagesoutdir=$DIST_PATH/images \
    -a outdir=$DIST_PATH \
    -o $DIST_PATH/${filename%.*}.html \
    -r asciidoctor-diagram \
    -b html5 \
    $DOCUMENT_DIR/${filename}
done

# swaggerの設定
cp $WS_ROOT_PATH/docs/blog-api/api.yml $DIST_PATH/blog-api/
perl -pe 's/<ymlPath>/api.yml/g' $WS_ROOT_PATH/assets/swagger-ui.html > $DIST_PATH/blog-api/api.html

ドキュメント(サンプル)

テスト用に以下のようなドキュメントを用意します。

仕様書のルートページ

documents/docs/index.adoc

:docname: サンプルブログ
:lang: ja
:doctype: book
:icons: font
:toc: left
:toc-title: 目次
:toclevels: 2
:example-caption: 例
:table-caption: 表
:figure-caption: 図
:chapter-label:
:imagesdir: images
:imagesoutdir: images

= サンプルブログ仕様書

== サンプルブログAPI

==== link:blog-api/spec.html[設計書^]
==== link:blog-api/api.html[API仕様書^]

API仕様書ページ

documents/docs/blog-api/spec.adoc

:docname: API仕様書
:lang: ja
:doctype: book
:icons: font
:toc: left
:toc-title: 目次
:toclevels: 3
:example-caption: 例
:table-caption: 表
:figure-caption: 図
:chapter-label:
:imagesdir: ../images
:imagesoutdir: images

= サンプルブログドキュメント

== 仕様書

=== 記事取得

[cols="3*", options="header"]
|===
|メソッド|パス|補足
|GET|/users/{userId}/articles|
|===

[plantuml]
----
@startuml
box "Web" #fff2df
  participant "blog.sample.dev" as blog
end box
box "AWS" #fff2df
  participant "APIGateway" as apig
  participant "lambda" as lambda
  database "User" as db_user
  database "Article" as db_article
end box
blog -> apig: 記事取得
activate blog
activate apig
apig -> lambda: 記事取得
activate lambda
lambda -> db_user: UIDを取得(userId)
activate db_user
alt 存在しない場合
  db_user-->lambda: 取得NG
  lambda-->apig: 400 BadRequest
  apig-->blog: 400 BadRequest
end
lambda --> apig: 200 OK + 記事情報
apig --> blog: 200 OK
deactivate lambda
deactivate apig
deactivate blog
@enduml
----

SwaggerUIページ

documents/docs/blog-api/api.yml

openapi: "3.0.0"

info:
  title: "サンプルブログAPI"
  version: "1"

servers:
  - url: "https://api.sample.dev/v1"

tags:
  - name: "User"
    description: "ユーザーに関する操作"
paths:
  /users/{userId}/articles:
    get:
      tags:
        - "User"
      summary: "ユーザーの記事一覧取得"
      security: []
      parameters:
        - in: "path"
          name: "userId"
          required: true
          schema:
            type: string
          description: "userId"
        - in: "query"
          name: "type"
          required: true
          schema:
            type: string
          description: "記事タイプ"
      responses:
        200:
          description: "成功"
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  articles:
                    type: "array"
                    items:
                      $ref: "#/components/schemas/article"
components:
  schemas:
    article:
        required:
          - "articleId"
          - "content"
          - "title"
          - "type"
          - "category"
          - "description"
          - "publishAt"
          - "ogpUrl"
          - "updateAt"
          - "createAt"
        type: "object"
        properties:
          articleId:
            type: "string"
            example: "xxxxxxxxxxx"
          content:
            type: "string"
            example: "# h1"
          title:
            type: "string"
            example: "XXをやってみた"
          type:
            type: "string"
            enum: ["tech", "gaget", "life"]
            example: "tech"
          category: 
            type: "string"
            example: ["typescript"]
          description:
            type: "string"
            example: "記事の説明!"
          ogpUrl:
            type: "string"
            example: "https://res.cloudinary.com/sample"
          thumbnail:
            type: "string"
            example: "https://res.cloudinary.com/sample"
          createAt:
            type: "number"
            example: 160915346897
          updateAt:
            type: "number"
            example: 160915346897
          publishAt:
            type: "number"
            example: 160915346897
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: access token for API

security:
  - bearerAuth: []

GitHub Actions設定

name: Deploy document

on:
  push:
    branches:
      - main

permissions:
  id-token: write
  contents: read
  actions: read

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - name: build documents
        run: |
          sudo gem install asciidoctor
          sudo gem install asciidoctor-diagram

          mkdir -p /usr/share/fonts/VLGothic
          cp ./documents/assets/VL-Gothic-Regular.ttf /usr/share/fonts/VLGothic

          ./bin/build.sh
      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    runs-on: ubuntu-latest
    needs: build
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

GitHub Pagesで公開された内容

うまくホスティングされていることが確認できました。

さいごに

ホスティングする場所は、S3の静的サイトホスティングなど他にも色んな候補があります。GitHub Pagesのメリットは、EnterpriseだとGitHubのユーザーで認証ができる点があると思います。S3の静的サイトホスティングと比較すると、過去のビルド結果も保持できるといいなと思います。SourceにGitHub Action(beta)を指定することで、より手軽で柔軟にサイトをホスティング出来るので、試して頂けたらと思います!