GitHubのプルリクコメントをトリガーとしてデプロイを実行する

## TL;DR;

1. プルリク中に動作確認のため開発環境にデプロイしたい
2. プルリクに特定のコメントを入れたらデプロイされるって機能があったら便利では！？
3. プルリクコメントに反応するGitHub Actionsを定義して実現した

コード例はこちら。
https://gist.github.com/gimKondo/1cac9b7133d6eb279ccaff4f5ec7324a

## 背景

開発していて、プルリクのレビュー承認前にちょっと開発環境にデプロイして動作を確認したいことがあります。
もちろん、ローカルからデプロイする手段を作っても良いのですが、GCP, Firebase, AWSなどクラウド環境へのデプロイのための認証情報を開発者に配るのは面倒です。
すべてGitHubからのデプロイにしてしまえば、デプロイのための秘密鍵はsecretsに集約できます。

GitHubからデプロイする手段としては、GitHub Actionsが用意されています。
しかし、GitHub ActionsはJenkinsのようにユーザがボタンをポチっと押したらデプロイする、というような「手動」の操作はできません。
とはいえ、GitHub上での様々なイベントをハンドリングして起動することはできます。

ということで、プルリクエクストにコメントを付けることでデプロイする仕組みを作ってみました。

## 実装イメージ

こんなコメントを書いたら

![github_pr_comment.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28487/1a5727b6-d2d0-ba00-77c1-c1a6e267aa49.png)

デプロイが走り、こんな風に

![github_deploy_feature.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28487/7988bf0b-2b6b-4376-b61f-362e562aea76.png)


結果が通知されるものを作ります(この例ではデプロイ失敗してますね :weary: )。

## 方法

コードを見てもらうのが早いでしょう。
`!github deploy-feature` とコメントを打つと、デプロイが走るActionsがこちらになります。


```yaml
name: deploy dev from feature by pull request

# (0) issueのコメント生成をハンドリング
on:
  issue_comment:
    types:
      - created

jobs:
  deploy:
    # add comment of pull request && comment is KEYWORD
    if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github deploy-feature'
    runs-on: ubuntu-latest

    steps:
      # (1) github-scriptを使って、プルリクコメントが付けられたプルリクのマージ元ブランチ名を取得
      - uses: actions/github-script@v2
        id: set-target-branch
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          result-encoding: string
          script: |
            const pull_request = await github.pulls.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            })
            return pull_request.data.head.ref
      # (2) (1)で取得したブランチをチェックアウト
      - uses: actions/checkout@v2
        with:
          ref: ${{ steps.set-target-branch.outputs.result }}
      # (3) チェックアウトしたブランチをデプロイ
      - name: Deploy
        # ---------------------------
        # ここにデプロイの処理を書く
        # ---------------------------  
        run: echo "Deploy Action is here"
      # (4) せっかくなのでSlackへも通知
      - name: Slack Notification
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: "Deploy (${{ steps.set-target-branch.outputs.result }})"
          mention: "here"
          mention_if: "failure"
          url: ${{ secrets.SLACK_WEBHOOK_URL }}
          commit: true
          token: ${{ secrets.GITHUB_TOKEN }}
```

一応、簡単な説明も書いておきます。


### (0) issueのコメント生成をハンドリング

ポイント1です。
GitHub Actionsにはプルリクのレビューをトリガーとするための `pull_request_review` があるのですが、これはコード内へのレビューコメントのみをトリガーにします。
https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-review-event-pull_request_review

プルリク本体へのコメントは `issue_comment` に反応するようになっているのです。
これはGitHubが内部的にプルリクをissueとして扱っていることに起因するのではないかと思ってます。
(余談: 個人的には、これは間違った抽象化だと思っています。歴史的経緯やissueとプルリクを同じ通し番号で扱うための実装上の都合等はあるのでしょうが)


### (1) github-scriptを使って、プルリクコメントが付けられたプルリクのマージ元ブランチ名を取得

ポイント2です。
(1)で書いた通り、このアクションで取れるのはissueのコメントイベントなので、プルリクの詳細情報は取れません。
具体的に言うと、プルリクのマージ元ブランチが取れないのです。
しかし、プルリクのIDは取れます。
このIDを使ってGitHubのAPIを呼び出すことでプルリクの詳細情報を取得し、マージ元ブランチ名を取り出します。
curl等でREST APIを使っても良いのですが、[github-script](https://github.com/actions/github-script)を使うと便利です。
ドキュメントは充実しているとは言い難いので、それなりに試行錯誤になるかもしれません(量はあるが未整理)。

なお、[GitHub Actionsのissue_commentの説明](https://help.github.com/en/actions/reference/events-that-trigger-workflows#issue-comment-event-issue_comment)を見ると、イベントで取れるパラメータについては[issue commentsのREST APIの説明](https://developer.github.com/v3/issues/comments/)を参照するように書いてあります。
しかし、そちらのドキュメントも全情報を網羅しているわけではありません。
(実際、issue_commentからプルリクのIDを取得できることはドキュメントには書かれていませんでした)

あまり気の進む方法ではないですが、パラメータにどんな情報が含まれるかは

```yaml
- name: Dumb event
  run: echo "${{ github.event }}"
```

のようにして、古き悪しきprintfデバッグをして調べるしかなさそうです。

### (2) (1)で取得したブランチをチェックアウト

特に語る事はありません。
取得したブランチをチェックアウトするのみです。

### (3) チェックアウトしたブランチをデプロイ

普段のデプロイ処理をここに書くのみです。

### (4) せっかくなのでSlackへも通知

私は[Slatify](https://github.com/homoluctus/slatify)を使って結果をSlackに通知しています。
どのブランチからデプロイされたか分かると便利かと思いjob_nameを
`"Deploy (${{ steps.set-target-branch.outputs.result }})"`
のように設定しています。

## おまけ

featureをデプロイした後にdev(※開発の統合ブランチ。developという名前の方が一般的かも)に戻したいこともあります。
そんなときのために `!github deploy-dev` と打つと、devをデプロイするActionも用意しておくと便利です。
そのためコードも乗せておきます。

```yaml
name: deploy dev to rollback dev by pull request

on:
  issue_comment:
    types:
      - created

jobs:
  deploy:
    # add comment of pull request && comment is KEYWORD
    if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github deploy-dev'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          ref: dev
      - uses: ./.github/actions/test
      - name: Deploy
        run: echo "Deploy Action"
      - name: Slack Notification
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: "Deploy to Dev (${{ github.event.comment.body }})"
          mention: "here"
          mention_if: "failure"
          url: ${{ secrets.SLACK_WEBHOOK_URL }}
          commit: true
          token: ${{ secrets.GITHUB_TOKEN }}
```