趣味プロダクトにおけるGitHub Actionsの活用事例

この記事は [ミクシィグループ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/mixi) 21日目の記事です。

趣味で[hato-bot](https://github.com/dev-hato/hato-bot)というSlack botの開発に参加していますが、こちらのプロジェクトでGitHub Actionsをどう使っているかをいくつか紹介します。
なお、本記事内でのコードや名称は意図がわかりやすいよう一部簡略化・変更を加えていますが、実際のコードへのリンクも併記しています。

# GitHub Actionsとは
GitHubが提供しているCI/CDになります。
個人的には以下のような利点があると感じています。

* パブリックリポジトリであれば無料で利用できるので、趣味の開発やOSSでCI/CDを導入したいときに便利。
* 事前の設定なしにGitHub APIを使えるため、PRにコメントする等のリポジトリに対する操作を伴うCI/CDを組みやすい。

# 活用事例
## Dockerイメージをビルドできるかテストする
apk (Alpine Linuxのパッケージ管理システム) はデフォルトでは最新バージョンのパッケージしか対応していません。
そのため、パッケージがアップデートされると、インストール時に指定したバージョンとapkが対応しているバージョンの間にズレが生じ、パッケージのインストールに失敗します。
これが原因で、正常にビルドできていたDockerイメージがいつの間にかビルドできなくなる事態が起きていたので、それを検知しやすくなるよう、Dockerイメージをビルドできるか確かめるCIを作成しました。

### CIのコード
```.github/workflows/pr-test.yml
name: pr-test

# PRで何らかのイベントが発生した際に発動する
on:
  pull_request:

jobs:
  pr-docker:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
    　# インストール等のステップなしでdocker-composeが使える
      - name: Build docker image
        env:
          DOCKER_BUILDKIT: 1
          COMPOSE_DOCKER_CLI_BUILD: 1
        run: docker-compose build --no-cache
```

### 実際のコード
https://github.com/dev-hato/hato-bot/blob/8e3fbdfd7fbd78c91b795fe20f5c9a40938b2be4/.github/workflows/pr-test-hato-bot.yml

## 別リポジトリへ差分を反映する
メインのリポジトリ ( `nanashi-san/main-repo` とします) での差分を関連するリポジトリ ( `nanashi-san/related-repo` とします) に手動で反映するという作業が発生しており、それを以下のようなCIで自動化しました。

* `nanashi-san/main-repo` 側CI: developにpushされた差分を関連するリポジトリの `pr-copy` ブランチへpushする
* `nanashi-san/related-repo` 側CI: `pr-copy` ブランチがpushされたらPRを立てる

### CIの作り方
1. 秘密鍵と公開鍵のペアを生成する。
1. 公開鍵を `nanashi-san/related-repo` のdeploy keysに設定する。
2. 秘密鍵を `nanashi-san/main-repo` のsecretsに `RELATED_REPO_PRIVATE_KEY` という名前で設定する。
1. `nanashi-san/main-repo` 側で以下のようなCIを組む。
   
    ```.github/workflows/pr-copy.yml
    name: pr-copy

    # developにpushされたら発動する
    on:
        push:
            branches:
                - develop

    jobs:
        pr-copy:
            runs-on: ubuntu-latest

            steps:
                # nanashi-san/main-repo 取得
                - uses: actions/checkout@v2
                  with:
                    fetch-depth: 0
                    # 『作業ディレクトリ/main-repo』にmain-repoをcloneする
                    path: main-repo
                # nanashi-san/related-repo 取得
                - uses: actions/checkout@v2
                  with:
                    fetch-depth: 0
                    repository: nanashi-san/related-repo
                    # 『作業ディレクトリ/related-repo』にrelated-repoをcloneする
                    path: related-repo
                # nanashi-san/related-repo のファイルを nanashi-san/related-repo にコピー
                - name: Copy
                  run: |
                    cp main-repo/path/to/dir/file related-repo/path/to/dir/
                # nanashi-san/related-repo における差分を取得
                - name: Set diff
                  id: set_diff
                  run: |
                    cd related-repo
                    echo "::set-output name=diff::$(git diff)"
                # nanashi-san/related-repo で差分が生じている場合、それを nanashi-san/related-repo のpr-copyブランチにpushする
                - name: Push
                  if: steps.set_diff.outputs.diff != ''
                  run: |
                    cd related-repo
                    git config user.name "nanashi-san"
                    git config user.email "nanashi-san@example.com"
                    git add -u
                    git commit -m "nanashi-san/main-repo の差分を反映"

                    # nanashi-san/related-repo の秘密鍵取得
                    echo "${{secrets.RELATED_REPO_PRIVATE_KEY}}" > deploy_key.pem
                    chmod 600 deploy_key.pem

                    # nanashi-san/related-repo にpush
                    GIT_SSH_COMMAND="ssh -i deploy_key.pem -o StrictHostKeyChecking=no -F /dev/null" git push -f "git@github.com:nanashi-san/related-repo.git" HEAD:refs/heads/pr-copy
    ```
1. `nanashi-san/related-repo` 側で以下のようなCIを組む。
   
    ```.github/workflows/pr-copy.yml
    name: pr-copy

    # pr-copy ブランチがpushされたら発動する
    on:
        push:
            branches:
                - pr-copy

    jobs:
        pr-copy:
            runs-on: ubuntu-latest

            steps:
                - name: Create PullRequest
                  # actions/github-scriptを使うと、octokit/rest.js ( https://octokit.github.io/rest.js/v18 ) 経由でGitHub APIを利用できる
                  # octokit/rest.jsを使用する際はドキュメントの『oktokit.~』を『github.~』と読み替える
                  uses: actions/github-script@v3
                  with:
                    # 自リポジトリのトークンは予めsecretsにセットされているので、それを以下のようにセットすると、自リポジトリに対する操作を行える
                    github-token: ${{secrets.GITHUB_TOKEN}}
                  script: |
                    const common_params = {
                        owner: context.repo.owner,
                        repo: context.repo.repo
                    }
                    const pull_params = {
                        head: "ref/head/pr-copy",
                        base: "master",
                        ...common_params
                    }
                    const pulls_list_params = {
                        state: "open",
                        ...pull_params
                    }
                    console.log("call pulls.list:")
                    console.log(pulls_list_params)
                        
                    // pr-copy ブランチをheadとするPRの一覧を取得する
                    github.pulls.list(pulls_list_params).then(list_res => {
                        // 該当するPRがなければPRを作成する
                        // 既にPRが立てられている場合はpushした時点でPRに反映されるので何もしなくて良い
                        if (list_res.data.length === 0) {
                            const pulls_create_params = {
                                title: "nanashi-san/main-repo の差分を反映",
                                ...pull_params
                            }
                            console.log("call pulls.create:")
                            console.log(pulls_create_params)
                            github.pulls.create(pulls_create_params)
                        }
                    })
    ```

### 実際のコード
* `nanashi-san/main-repo` 側CI: https://github.com/dev-hato/hato-bot/blob/5913d97ea2039267ceb920bb65c7c9935f67d35b/.github/workflows/pr-copy-ci-hato-bot.yml
* `nanashi-san/related-repo` 側CI: https://github.com/dev-hato/sudden-death/blob/617bd175d27b15c47f090fbab6f684f541e96413/.github/workflows/pr-copy-ci-sudden-death.yml

## コードのフォーマットを直すPRを自動的に作成する
[hato-bot](https://github.com/dev-hato/hato-bot)の開発に参加している @Goryudyuma さんによる記事があるので、こちらをご覧ください。
https://qiita.com/Goryudyuma/items/18b89b2c279a75d4b309