Shogo's Blog

Oct 23, 2020 - 2 minute read - github

GitHub Actions を使って簡単なボットを作る

リリース当初は git push など GitHub 上のイベントしかトリガーにできなかった GitHub Actionsですが、 workflow_dispatch イベント の登場により手動実行ができるようになりました。

社内でもこの機能を利用してワークフローの手動実行をしていたのですが、人間とは欲深いもので「毎回ワークフローを選択してポチポチするのだるい」という声があがってきました。 そういうわけで、Pull Request のコメントをトリガーにしてワークフローを実行する簡単なボットを作ってみました。

方針

workflow_dispatchissue_comment をトリガーにしたワークフローを作ればいいだけの気もしますが、 以下のような理由からワークフローからワークフローを呼び出す形にしました。

  • workflow_dispatch を使った既存のワークフローがあるので、それを流用したい
    • トリガーが複数あると、イベントの種類に応じてペイロードの形式が異なるので、地味に処理が大変
    • issue_comment は全部のコメントに反応するので、本当に見たいログが埋もれてしまう
  • コメントを投稿した Pull Request のHEADでワークフローを実行して欲しい
    • issue_comment はイベントの発生元として、デフォルトブランチのHEADが渡ってきます
    • イベントのペイロードには、プルリクエストへのリンクが入っているだけで、HEADの情報はわからない

実装

jfurudo1 がサードパーティのアクションを使ってゴニョゴニョやっていたものの、 あんまりうまく行ってなさそうだったので、bash script でエイヤッと書き直しました。

build」 とコメントすると、.github/workflows/build.yml のワークフローを実行するサンプルです。

name: comment hook

on:
  issue_comment:
    types: [created]

jobs:
  distribute:
    runs-on: ubuntu-latest
    steps:
      - name: dispatch workflow
        run: |
          # イベントに関する詳細情報を取ってくる
          PAYLOAD=$(cat "$GITHUB_EVENT_PATH")
          NUMBER=$(echo "$PAYLOAD" | jq -c '.issue.number')

          # Issue と Pull Request のコメントが混ざってくるので、Issueは無視する
          if [[ "$(echo "$PAYLOAD" | jq -c '.issue.pull_request')" = "null" ]]; then
            echo "It's not pull request. Skip it."
            exit 0
          fi

          # 前述の通り $PAYLOAD にはプルリクエストの詳細が入っていないので、GitHub CLIを使って詳細を取得
          PULL_REQUEST=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$NUMBER")

          # jq でコメントの内容を取り出し、正規表現マッチ
          if [[ "$(echo "$PAYLOAD" | jq -c '.comment.body | test("build"; "i")')" = "true" ]]; then
            # レスポンスを返してあげる
            gh api "repos/$GITHUB_REPOSITORY/issues/$NUMBER/comments" -F "body=ビルドを実行します :rocket:"

            # ワークフロー呼び出し
            # Pull Request の .head.ref を渡してあげているのがポイント
            gh api "repos/$GITHUB_REPOSITORY/actions/workflows/build.yml/dispatches" -F "ref=$( echo "$PULL_REQUEST" | jq -r '.head.ref')"
          fi          
        env:
          # 標準で渡ってくる secrets.GITHUB_TOKEN は他のワークフローを呼び出せないので、
          # コメント専用にトークンを発行する
          GITHUB_TOKEN: ${{ secrets.USERS_GITHUB_TOKEN }}

GitHub CLI がGAになり 程なくして GitHub Actions にも Pre Install されるようになったのでこれを使っています。 残念ながらIssueへの書き込み等は対応しておらず gh api を使ってほぼ生のAPIを叩くことになります。 しかし認証ヘッダーを環境変数から読み取ってくれる分 curl で頑張るよりは少し楽になりました。

ちなみに gh api "repos/:owner/:repo" のようにレポジトリ名を表すプレースホルダーが使えるのですが、上のワークフローでは使っていません。 代わりに GITHUB_REPOSITORY 環境変数を使っています。 なぜかというとレポジトリをクローンせずにYAMLファイルの中で完結しているので、GitHub CLI がレポジトリを特定できないんですね。 プレースホルダーを使ったほうが短くかけるし、手元でのデバッグもやりやすいので、使えると良かったんですけどね。

まとめ

やはり頼れるのは jq と curl。あとついでに GitHub CLI

参考