Github Actions でGithub Pages更新用ビルド、プルリクを自動化する（mkdocs mike利用）

# はじめに

- [Github Pagesの公開ページの版管理（MkDocs plugin mike利用）](https://qiita.com/tomoten/items/39b87fc114d8f41b5ebd)にて、手動でのGithub Pagesを公開する使い方を勉強しました。
- ただ、こういった作業はなるべく自動化したい（CIしたい）ので、Github Actionsを用いてGithub Pagesへデプロイする仕組みを実装しました。
  - ただし、最終的にはデプロイするためのPull Requestする、というところに着地しました。

## 0. 参考ページ

- [Github Pagesの公開ページの版管理（MkDocs plugin mike利用）](https://qiita.com/tomoten/items/39b87fc114d8f41b5ebd)
    - 前回の内容。ここのページで作成したMkDocs文書に対してGithub ActionsによるCIを導入します。
- [Github Actions](https://docs.github.com/ja/actions)
    - Github Actions公式ページ。知りたいことはほぼこのページに記載されています。
- [create-pull-request](https://github.com/peter-evans/create-pull-request)
    - PRをだすworkflowのページ。使用方法を勉強しました。

## 1. 構想

### 1.1. 前提・制約条件

- 前提条件：メインとなるブランチはmasterブランチとします。（ただし、特に名称に意味はないです）
    - 最近は、ブランチ名をmainにするらしいです。
- 前提条件：Github Pagesの公開ページの紐づけブランチは、gh-pagesブランチとします。
- 制約条件：公開リポジトリで実施するため、gh-pagesブランチは`protected branch`に設定します。つまり、pushによるアップロードを禁止します。
    - 結構、push禁止でCI回すのにハマったので、CI構築のポイントがまとめられればと思います。

### 1.2. やりたいこと

- masterブランチにてMkDocs文書を管理します。mkdocsの文書をGithub Pagesにアップロードするタイミングは、以下の2点とします。
    - masterブランチのものを、任意のタイミング（自分がやりたい時）にGithub Pagesにアップロードする。
        - 「masterにpushされたときCIを回す」はよくあるのですが、masterが頻繁に更新されるときにPull Requestが連発するのは正直面倒なので、masterの内容のアップロードは自分がやりたい時（→workflow_dispatch利用）としています。
    - masterブランチのものに対して、リリースする（≒tag打ちする）ときにGithub Pagesにアップロードする。
        - masterブランチの特定のcommitに対してtagが打たれたときに（tag push event利用）Actionsが動作するようにします。

## 2. リポジトリ構成

- [Github Pagesの公開ページの版管理（MkDocs plugin mike利用）](https://qiita.com/tomoten/items/39b87fc114d8f41b5ebd)で構築した[my-mkdocs-mike](https://github.com/tomoten-umino/my-mkdocs-mike)リポジトリを利用します。
- 構成は以下の通りです。

```bash
my-mkdocs-mike
├── .devcontainer
│   └── devcontainer.json
├── .github
│   └── workflows
│       └── mkdocs-deploy.yml   <ーこれがActionsを定義するファイル
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── docs
│   ├── index.ja.md
│   ├── index.md
│   └── topic1
│       └── 内容省略
├── mkdocs.yml
├── requirements.txt
└── site
```

## 3. 構築したCI

- まずは全体像を提示したあと、ポイントをまとめます。

### 3.1. Actions処理全体

```yaml
name: MkDocsBuild

on:
  workflow_dispatch:
  push:
    tags:
      - '*'

env:
  PR_BRANCH: gh-pages-${{ github.run_number }}

jobs:
  release-PR:
    runs-on: ubuntu-latest
    steps:
      - name: execute apt install
        run: |
          sudo apt update
          sudo apt install -y hub

      - name: setup python
        if: ${{ success() }}
        uses: actions/setup-python@v2
        with:
          python-version: '3.10.0'

      - name: checkout code
        uses: actions/checkout@v2
        with:
          ref: ${{ github.ref }}

      - name: pip install
        if: ${{ success() }}
        run: |
          pip install -r requirements.txt 

      - name: setup mkdocs project
        if: ${{ success() }}
        env:
          REF: ${{ github.ref }}
        run: |
          export VER=`echo "${REF##*/}"`
          echo "version=${VER}" >> $GITHUB_ENV
          git remote set-url origin https://github-actions:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}
          git config --local user.name "${GITHUB_ACTOR}"
          git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com"
          git fetch

      - name: make PR_BRANCH
        if: ${{ success() }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git checkout -b ${{ env.PR_BRANCH }} origin/gh-pages
          git push origin ${{ env.PR_BRANCH }}
          git checkout ${{ github.ref }}

      - name: check mike list previous
        if: ${{ success() }}
        continue-on-error: true
        run: |
          mike list --branch ${{ env.PR_BRANCH }} | grep ${{ env.version }}
          if [ $? ]; then
            mike delete --branch ${{ env.PR_BRANCH }} ${{ env.version }}
          fi

      - name: build mkdocs
        if: ${{ success() }}
        run: |
          mike deploy --branch ${{ env.PR_BRANCH }} ${{ env.version }}

      - name: checkout PR_BRANCH
        if: ${{ success() }}
        run: |
          git checkout ${{ env.PR_BRANCH }}

      - name: pull request
        if: ${{ success() }}
        uses: peter-evans/create-pull-request@v3
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          commit-message: Update MkDocs 
          committer: GitHub <noreply@github.com>
          author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
          signoff: false
          branch: ${{ env.PR_BRANCH }}
          base: gh-pages
          delete-branch: true
          title: 'Update MkDocs'
          body: |
            Update report
          labels: |
            automated pr
          draft: false
```

### 3.2. ポイント１：PRを出すためのブランチをCI内で自動生成する

- `PR_BRANCH: gh-pages-${{ github.run_number }}`にて、PRするためのブランチ名をgh-pages-ジョブナンバーで定義します。
    - PRブランチは、Merge後に（人が）削除する想定だが、「`人はよくブランチを消し忘れる`」ので、その対策でCIでPRするブランチ名が毎回違うものになるようにしています。
- PRブランチの派生元は、当然ですがgh-pagesです。
    - `git checkout -b ${{ env.PR_BRANCH }} origin/gh-pages` <-ココの箇所
- PRのたびに、PRブランチを生成するポイントは、長命ブランチを避けるためです。PRブランチを固定してしまうと長命ブランチとなり、修正が増えるたびにコンフリクトを起こす状態になります。
    - ブランチは、なるべく短命に、役割を終えたら消す（または使わない）ことを心がけています。
    - また、CIが動作するときに、前回状態に依存した作りにしないためでもあります。
- 流れとしては、以下の通りです。
    1. プロジェクトをcloneしてfetchする。
    1. gh-pagesを派生元として、gh-pages-XXXブランチを作成する。（ただし、XXXはworkflowの実行通し番号(github.run_number)である）
    1. 一度、リモートブランチにgh-pages-XXXブランチをpushする。
    1. 以降の処理において、gh-pages-XXXブランチに修正をcommit -> push
        - このとき、mkdocsの版管理ツールmikeを用いて、master（or tagの内容）をmike deploしてgh-pages-XXXへビルド内容をcommitする。
    1. gh-pages-XXX -> gh-pagesへPRを出す。

### 3.3. ポイント２：actionsのオプション「continue-on-error: true」は便利

- この「continue-on-error」は、以下のような設定をするオプションです。
    - （[Github Actions公式より引用](https://docs.github.com/ja/actions/learn-github-actions/workflow-syntax-for-github-actions)）「ステップが失敗してもジョブが失敗にならないようにします。 trueに設定すれば、このステップが失敗した場合にジョブが次へ進めるようになります。」
- step name: check mike list previous　ですが、このstepは、「mike listしたときに登録したい名称で登録済みの場合、それを削除する」という処理となります。
    - この処理、「登録されていない」場合、grep処理の帰り値がreturn 1となるため、step失敗扱いになります。
- この処理は、たとえreturn 1（grep失敗）となっても、step処理的には失敗ではないので、次stepへ処理を継続させるために本オプションを設定します。

## 4. Github Actionsが動作したときの流れ

※workflow_dispatchでも動きますが、ここではtag打ちからの流れを示します。

![gh-actions-mike-demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624259/487b9848-e1a6-c63a-3a99-4f8dd7706854.gif)

## 5. おわりに

- このCIを組むとき、[Github Pagesの公開ページの版管理（MkDocs plugin mike利用）](https://qiita.com/tomoten/items/39b87fc114d8f41b5ebd)の知見から、「mike deploy」してgh-pagesに登録された成果物をpushすればいいや、くらいのノリで考えていました。
- でも、考えていくうちに、gh-pagesが簡単にpushできたら、簡単にGithub Pagesが編集できてしまうのでは？、と思い、いろいろ思考錯誤した結果、本記事の構築内容となりました。
    - privateリポジトリでやるのもいいのですが、なるべく公開したいですね。（承認欲求なのか、OSSの考えなのか、定かではないですが）
- 今回は触れていませんが、本来のCIとしては、masterブランチへのPR時に、ビルドチェック、静的解析、を実施するものがあります。そちらについても別題材で記事を書きたいと思っています。