🐣

actdocsでアクション/Reusable Workflowsのドキュメントを自動生成して、GitHub Actionsライフを快適にする

2022/11/30に公開

actdocs』はアクション/Reusable Workflowsのドキュメントを自動する生成ツールです。GitHub Actionsの開発がちょっとだけ楽になる小粋なツールです。

https://github.com/tmknom/actdocs

READMEは大事だよ

GitHub ActionsではアクションReusable Workflowsという、再利用可能な部品を作る機能が提供されています。GitHub Marketplaceでもたくさんのアクションが公開されています。

ただ自分で実装する場合に、メンドウなのがドキュメントです。たとえば「GitHubの公式ガイドライン」では、READMEについて次のような推奨事項が記載されています。

We recommend creating a README file to help people learn how to use your action. You can include this information in your README.md:

  • A detailed description of what the action does
  • Required input and output arguments
  • Optional input and output arguments
  • Secrets the action uses
  • Environment variables the action uses
  • An example of how to use your action in a workflow

利用者のために概要・入出力パラメータ・コード例をREADMEへ書け、という正論が述べられています。せやな感はありますが、いざ自分でやると正直ツラって気持ちになります。

そこで実装したのが『actdocs』です。actdocsはアクション/Reusable WorkflowsのYAMLファイルをパースし、Descriptionなどをマークダウン形式で書き出します。descriptionキーさえきちんと書いておけば、いい感じのドキュメントになります。ちなみにGoで実装しました。

サクッと試してみる

前置きはこれぐらいにして早速使ってみましょう。actdocsはDockerイメージ経由での利用が簡単です。次のコマンドを実行すると、アクションのドキュメントを生成します。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs generate action.yml

生成されたドキュメントをGitHubで表示すると、次のようになります。これだけっちゃこれだけなんですが、ちゃんと管理されてるっぽい雰囲気が出ますね!

Reusable Workflowsの場合も同様です。actdocsは、アクションとReusable Workflowsを自動識別します。ファイルの指定先だけ変更すれば、まったく同じように使えます。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs generate .github/workflows/lint.yml

インストール

前述のとおり、Dockerイメージの利用が楽です。Docker HubとGitHub Packagesに同じイメージが置いてあるので、好きなほうからプルしてください。

  • Docker Hub:
    docker pull tmknom/actdocs
    
  • GitHub Packages:
    docker pull ghcr.io/tmknom/actdocs
    

バイナリファイルがよければ、GitHub Releasesからダウンロードできます。actdocsはパスだけ通せばワンバイナリで動きます。

使い方

actdocsの使い方は大きく2つです。

  • ドキュメントを生成して標準出力
  • ドキュメントを生成してREADME.mdなどのファイルへ挿入

それではもう少し具体的に見ていきます。

ドキュメントの標準出力

たとえば次のようなアクションが、action.ymlに実装されているとします。

action.yml
name: Create PR
description: |
  Create a pull request for changes to your repository.

  Create PR action is designed to be used in conjunction with other steps that change files.
  This action will run following steps:

  1. Add file contents to the index with `git add`
  2. Record changes to the repository with `git commit`
  3. Update remote refs along with associated objects with `git push`
  4. Create a pull request on GitHub

inputs:
  paths:
    required: true
    description: "File paths for the git add"
  branch:
    required: true
    description: "The branch that contains commits for your pull request"
  base-branch:
    default: "main"
    required: false
    description: "The branch into which you want your code merged"
  commit-message:
    required: true
    description: "Message for the git commit"
  title:
    default: "Generated by create-pr-action"
    required: false
    description: "Title for the pull request"
  body:
    default: "Please merge me"
    required: false
    description: "Body for the pull request"

outputs:
  sha:
    value: ${{ steps.push.outputs.sha }}
    description: "The full SHA-1 object name for the revision"

runs:
  using: composite
  steps:
    - run: echo "Create PR"
      shell: bash

このファイルに対し、actdocsのgenerateコマンドを実行してみましょう。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs generate action.yml

すると次のようなマークダウン形式のドキュメントが標準出力されます。

## Description

Create a pull request for changes to your repository.

Create PR action is designed to be used in conjunction with other steps that change files.
This action will run following steps:

1. Add file contents to the index with `git add`
2. Record changes to the repository with `git commit`
3. Update remote refs along with associated objects with `git push`
4. Create a pull request on GitHub

## Inputs

| Name | Description | Default | Required |
| :--- | :---------- | :------ | :------: |
| branch | The branch that contains commits for your pull request | n/a | yes |
| commit-message | Message for the git commit | n/a | yes |
| paths | File paths for the git add | n/a | yes |
| base-branch | The branch into which you want your code merged | `main` | no |
| body | Body for the pull request | `Please merge me` | no |
| title | Title for the pull request | `Generated by create-pr-action` | no |

## Outputs

| Name | Description |
| :--- | :---------- |
| sha | The full SHA-1 object name for the revision |

YAMLファイルの内容を素直にマークダウン化しただけですが、GitHubではだいぶ見やすくなります。InputsのDefaultRequiredが一覧で確認できるのも、地味にうれしいポイントです。

ドキュメントのファイルへの挿入

actdocsは任意のファイルへドキュメントを挿入できます。まずは挿入対象のファイルへ、目印となるコメントを事前に書いておきます。ここではREADME.mdへ書いたとしましょう。

README.md
<!-- actdocs start -->
<!-- actdocs end -->

次にinjectコマンドを実行します。--fileフラグには挿入対象のファイルを指定します。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs inject --file README.md action.yml

これで<!-- actdocs start --><!-- actdocs end -->の間に、generateコマンドと同様のドキュメントが挿入されます。おそらくメインのユースケースはこちらでしょう。

次のリポジトリはactdocsによるドキュメント自動生成の活用例です。

https://github.com/tmknom/create-pr-action

出力のカスタマイズ

actdocsは少しだけ出力をカスタマイズできます。

項目のソート

--sortフラグを使うと、InputsやOutputsの各項目をソートできますgenerateinjectコマンドのどちらにも対応しています。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs inject --sort --file README.md action.yml

項目が空の場合に省略

InputsやOutputsに項目がない場合、デフォルトでは次のような出力になります。

## Inputs

N/A

## Outputs

N/A

しかし--omitフラグを使うと、項目が空の場合に出力自体を抑制できます。もちろん項目が存在する場合は、普通に出力されます。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs inject --omit --file README.md action.yml

JSON形式

actdocsは基本的にマークダウン形式で扱うツールです。ただ、他のフォーマットで扱いたいケースもあるでしょう。そこで加工しやすいように、JSON形式の出力にも対応しています。

docker run --rm -v "$(pwd):/work" -w "/work" \
ghcr.io/tmknom/actdocs generate --format json action.yml

あとはjqコマンドで頑張るなり、自作ツールでJSONを操作するなり好きに使ってください。自力で元のYAMLファイルをパースするよりは、扱いやすい構造になっています。

出力項目の違い

アクションとReusable Workflowsでは微妙にシンタックスが異なります。

そのためactdocsでは、アクションとReusable Workflowsで出力項目を変えています。出力される項目は次のとおりです。

  • アクションの出力項目
    • Description
    • Inputs
    • Outputs
  • Reusable Workflowsの出力項目
    • Inputs
    • Secrets
    • Outputs
    • Permissions

こうやって並べてみると、結構違います。これはactdocsの制約ではなく、GitHub Actionsのシンタックスの制約です。たとえばアクションではPermissionsの定義ができません。そのため不足している項目は、引き続き手作業で書く必要があります。

実運用への組み込み

実運用へ組み込む際のヒントを3つ紹介します。

タスクランナー

お気づきでしょうが、Docker経由で使うとコマンドが長いです。そこでオススメなのは、タスクランナーに組み込んでしまうことです。タスクランナーは好みもあるでしょうから、好きなものを使ってください。個人的にはMakefile[1]へ次のようなスクリプトを書いています。

Makefile
.PHONY: docs
docs: ## generate docs
	docker run --rm -v "$(pwd):/work" -w "/work" \
	ghcr.io/tmknom/actdocs inject --sort --file=README.md action.yml

このように書いておけば、次のコマンドを実行するだけで済みます。

make docs

継続的ドキュメンテーション

チームで開発しているなら、GitHub Actionsへ組み込んでしまうのもよいでしょう。たとえば次のコードは、アクションのREADMEを自動更新するワークフローですaction.ymlファイルを変更するプルリクエストを出すと、自動的にREADMEを最新化してプッシュしてくれます。

.github/workflows/docs.yml
name: Docs
on:
  pull_request:
    paths: ["action.yml"]
permissions:
  contents: write
jobs:
  generate:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    env:
      YAML_FILE: action.yml
      MD_FILE: README.md
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.ref }}

      - name: Inject document
        run: |
          docker run --rm -v "$(pwd):/work" -w "/work" \
          ghcr.io/tmknom/actdocs inject --sort \
          --file "${MD_FILE}" "${YAML_FILE}"

      - name: Push
        run: |
          if [[ "$(git status -s)" == "" ]]; then
            echo 'skipped'
            exit 0
          fi
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add "${MD_FILE}"
          git commit -m "Update ${MD_FILE}"
          git push

READMEへ<!-- actdocs start --><!-- actdocs end -->を追加しておけば、actdocsのローカルへのインストールは不要です。READMEの修正漏れの指摘も、いちいちせずに済みます。

ドキュメント生成のCIへの組み込みは、会社組織では特に威力を発揮します。次のような先駆者の記事なども参考にしながら、ワークフローを組み立ててみるのも一興です。

https://tech.gunosy.io/entry/introduce-continuous-documentation

DescriptionにはUsageも書こう

アクションでのみ使える小技ですが、descriptionキーへUsageも書くと結構体験がよいです。

action.yml
name: Create PR
description: |
  Create a pull request for changes to your repository.

  Create PR action is designed to be used in conjunction with other steps that change files.
  This action will run following steps:

  1. Add file contents to the index with `git add`
  2. Record changes to the repository with `git commit`
  3. Update remote refs along with associated objects with `git push`
  4. Create a pull request on GitHub

  ## Usage

  ```yaml
    steps:
      - name: Create PR
        uses: tmknom/create-pr-action@v0
        with:
          paths: "*.md **/*.md"
          branch: example-pr
          commit-message: Committed by create-pr-action
          title: Generated by create-pr-action
          body: Please merge me
  ```

このように書いておくと、次のようなドキュメントを生成できます。

コピーアンドペーストできるUsageは、利用者からすると地味に便利です。またDescriptionを定義するYAMLファイルでは、Usageがファーストビューに入ってきます。すると久しぶりのコード変更でもスッと思い出せます。ちょっとしたことですが、UXとDXが両方向上してオススメです

なぜ「actdocs」を作ったのか

アクションやReusable Workflowsの数が増えてくると、ドキュメントの維持がメンドクサすぎるためです。軽く検索してみましたが、探した当時は発見できませんでした[2]。というわけで自作に至りました。YAMLを少しパースすればすぐ作れそうに見えたのも、自作を後押しています。

設計の参考にしたのはterraform-docsです。よく仕事でTerraformモジュールを実装しているのですが、ドキュメント生成はこのツールでいつも自動化しています[3]。そこでGitHub Actionsでも同じような開発体験を実現しよう、というコンセプトで実装しました。入出力パラメータが表形式なのは、terraform-docsの影響です。

不思議なものでコードの近くにドキュメントがあると、「ちゃんと書こうかな」という気になります。おそらくコード修正時に視界へ入りやすく、ついでに直すモチベーションが湧きやすいのでしょう。実際actdocsを作ってから、以前よりDescriptionをちゃんと書くようになりました。

まとめ

本記事ではドキュメント生成ツール『actdocs』を紹介しました。もしアクションやReusable Workflowsのドキュメント維持に課題を感じているなら、一度お試しください。

ちなみにREADMEは一応英語で書いてありますが、筆者の英語力はゼロです。完全にDeepL頼みです。そのためもしご意見あれば、日本語でいただけるとありがたいです。

https://github.com/tmknom/actdocs

完全に余談ですが、ドッグフーディングできるのは楽しいですね。仕事ではコードを書く以外の活動も多いため、こういうちょっとしたツールの実装は癒やされます。

脚注
  1. MakefileというかMakeはビルドツールだろというツッコミを受けそうですが、筆者自身は完全にタスクランナーとして使っています。macOSやLinuxであれば環境構築がいらなくて便利です。 ↩︎

  2. 探したのは半年以上前の話です。actdocs自体は結構前にできてたんですが、自分の課題が解決して満足してしまったので、これまで告知なども行っていませんでした。ちなみに今回この記事を書いたのは、チームへ説明するためという理由もあります。 ↩︎

  3. terraform-docsはTerraform界隈では有名なドキュメント生成ツールです。多くのTerraformモジュールで利用されています。どのTerraformモジュールのREADMEでも同じように書かれているため、読み手の認知負荷低減にかなり寄与している印象があります。 ↩︎

Discussion