GitHub Actionsでカスタムコマンドを作ってChatOpsを始めよう(フォーマットをする例)
アドベントカレンダーです。
GitHubでChatOpsをぜひやりましょう。ということで、その第一歩として、GitHub Actionsを利用して手軽に実行できるチャットコマンドを作ってみたいと思います。
シチュエーション
GitHubでレビューするときに、ちょっとした編集はGitHubの機能でできます。なので、レビュアー側で片付けてしまいたい。だけれどいざコミットすると行が長くなって改行が必要だった…
といったことがあります。
フォーマットで落ちてしまってスムーズに進められない。
GitHub Codespaceを立ち上げて依存をインストールして…としても良いのですがそれも手間がかかります。
今回はより簡単に、PR内のコメントで「!format」とコメントするだけで解決する方法をGitHub Actionsで実現したいと思います。
完全版のコード
折り畳みの中に完全版のコードを用意しています。
Node.jsを想定した、prettierを実行するためのコードです。バージョンや npm run
の仕方はプロジェクトごとに異なるでしょうから、そのあたりは修正する必要があります。
.github/workflows/command-format.yml
name: 'コマンド: フォーマット'
on:
issue_comment:
types: [created]
env:
NODE_OPTIONS: "--max-old-space-size=4096"
NODE_VERSION: 20.11.1
jobs:
command_format:
if: |
github.event.comment.user.type != 'Bot'
&& github.event.issue.pull_request != null
&& (
false
|| (
github.event.comment.body == '!format'
|| startsWith(github.event.comment.body, '!format ')
|| startsWith(github.event.comment.body, '!format\n')
)
|| (
github.event.comment.body == '!prettier'
|| startsWith(github.event.comment.body, '!prettier ')
|| startsWith(github.event.comment.body, '!prettier\n')
)
)
runs-on: ubuntu-latest
steps:
- name: PRの情報を取得
id: pr_info
run: |
head_ref=$(gh pr --repo ${{ github.event.repository.full_name }} view ${{ github.event.issue.number }} --json 'headRefName' -q '.headRefName')
echo "head_ref=$head_ref" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- name: チェックアウト (PR)
uses: actions/checkout@v4
with:
ref: ${{ steps.pr_info.outputs.head_ref }}
ssh-key: ${{ secrets.COMMAND_ACTIONS_DEPLOY_KEY_SECRET }}
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: "メイン: フォーマットしてプッシュ"
id: main
run: |
corepack npm ci
corepack npm run _lint:prettier -- --write
diff_status=$(git diff --exit-code > /dev/null 2>&1; echo $?)
echo "diff_status=$diff_status" >> "$GITHUB_OUTPUT"
if [ $diff_status -ne 0 ]; then
git config user.name "GitHub Action"
git config user.email "action@github.com"
git add .
git commit --message "chore: フォーマット [AUTO]"
git push
fi
- name: 失敗した場合のコメントを追加
if: failure() && steps.main.outcome == 'failure'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
@${{ github.event.comment.user.login }}
❌フォーマットに失敗しました。
詳細は[Actionsのログ](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})のジョブ`${{ github.job }}`を確認してください。
通知タグ: CMD_NOTIFY_ANY, CMD_NOTIFY_FORMAT, CMD_NOTIFY_FORMAT_FAILURE
- name: 成功した場合のコメントを追加
if: success() && steps.main.outputs.diff_status != '0'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
@${{ github.event.comment.user.login }}
✅フォーマットに成功しました。
詳細は[Actionsのログ](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})のジョブ`${{ github.job }}`を確認してください。
通知タグ: CMD_NOTIFY_ANY, CMD_NOTIFY_FORMAT, CMD_NOTIFY_FORMAT_SUCCESS
- name: フォーマットの差分がなかった場合のコメントを追加
if: success() && steps.main.outputs.diff_status == '0'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
@${{ github.event.comment.user.login }}
⏹フォーマットの差分がありませんでした。
詳細は[Actionsのログ](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})のジョブ`${{ github.job }}`を確認してください。
通知タグ: CMD_NOTIFY_ANY, CMD_NOTIFY_FORMAT, CMD_NOTIFY_FORMAT_SKIP
解説
イベントの設定
イベントは issue_comment.created
[1]を利用します。
また、実行のための if
条件に以下を記載します。
-
github.event.comment.user.type != 'Bot'
: コメントを書いたユーザーを人間に限定します。無限ループなどを防ぎます。- 実行可能な権限をロールで指定したい場合は、
-
github.event.issue.pull_request != null
:issue_comment
イベントはIssueとPRの両方で発火するので、PR内のものに限定します。 -
github.event.comment.body == '!format'
:!format
という内容のコメントのみに対して発火します。-
startsWith
やcontains
関数等を利用して、マッチの仕方を少し緩くすることも考えられます。
-
on:
issue_comment:
types: [created]
jobs:
command-format:
if: |
github.event.comment.user.type != 'Bot'
&& github.event.issue.pull_request != null
&& (
false
|| (
github.event.comment.body == '!format'
|| startsWith(github.event.comment.body, '!format ')
|| startsWith(github.event.comment.body, '!format\n')
)
|| (
github.event.comment.body == '!prettier'
|| startsWith(github.event.comment.body, '!prettier ')
|| startsWith(github.event.comment.body, '!prettier\n')
)
)
PRの情報を取得
- name: PRの情報を取得
id: pr_info
run: |
head_ref=$(gh pr --repo ${{ github.event.repository.full_name }} view ${{ github.event.issue.number }} --json 'headRefName' -q '.headRefName')
echo "head_ref=$head_ref" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
今回利用しているのはPR関連の直接のイベントではないため、PRの情報は gh
コマンド等から取得する必要があります。
今回はチェックアウトするために、PRが作られたブランチ名 (head_ref
) のみ取れれば十分です。他には例えば、マージ先予定のブランチ名 (base_ref
) が必要であれば以下のように取得できます。
base_ref=$(gh pr --repo ${{ github.event.repository.full_name }} view ${{ github.event.issue.number }} --json 'baseRefName' -q '.baseRefName')
echo "base_ref=$base_ref" >> "$GITHUB_OUTPUT"
チェックアウト
コメントのイベントなので、チェックアウトは引数に明示的にrefとssh-keyを渡す必要があります。
- name: チェックアウト (PR)
uses: actions/checkout@v4
with:
ref: ${{ steps.pr_info.outputs.head_ref }}
ssh-key: ${{ secrets.COMMAND_ACTIONS_DEPLOY_KEY_SECRET }}
ref
は上記のステップで作った値を用いればよいです。 ssh-key
は Deploy Key の秘密鍵です。 https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys#set-up-deploy-keys に従い Deploy Key を設定したら、Actions Variablesから COMMAND_ACTIONS_DEPLOY_KEY_SECRET
にその秘密鍵を入れてください。
(注意。Deploy Key として入れるのは公開鍵ですが、 ssh-key
に入れるのは秘密鍵です)
メインの処理
自動化したいことをやります。ここはもう、それぞれですね。あくまでも私のプロジェクトでの例が以下になります。
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: "メイン: フォーマットしてプッシュ"
id: main
run: |
corepack npm ci
corepack npm run _lint:prettier -- --write
diff_status=$(git diff --exit-code > /dev/null 2>&1; echo $?)
echo "diff_status=$diff_status" >> "$GITHUB_OUTPUT"
if [ $diff_status -ne 0 ]; then
git config user.name "GitHub Action"
git config user.email "action@github.com"
git add .
git commit --message "chore: フォーマット [AUTO]"
git push
fi
Actionsからのコミットに利用するemailはなにがいいんだ、という議論があります[2]が、 action@github.com
だとアイコンが表示されてイイカンジという感じです。
完了の通知
成否によってコメントで教えてあげるとわかりやすいでしょう。
- 失敗した場合:
if: failure() && steps.main.outcome == 'failure'
- 成功した場合:
if: success() && steps.main.outputs.diff_status != '0'
- フォーマットの差分がなかった場合:
if: success() && steps.main.outputs.diff_status == '0'
詳細な例はフルバージョンを参照してください。
peter-evans/create-or-update-comment
を利用すれば、コメントをすることが可能です。
@${{ github.event.comment.user.login }}
とかけば、コメントした人へのメンションになります。
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.issue.number }}
body: |
@${{ github.event.comment.user.login }}
⏹フォーマットの差分がありませんでした。
詳細は[Actionsのログ](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})のジョブ`${{ github.job }}`を確認してください。
通知タグ: CMD_NOTIFY_ANY, CMD_NOTIFY_FORMAT, CMD_NOTIFY_FORMAT_SKIP
通知タグは、メールなどで通知をフィルタしやすいように私のチームでは必ず付けています。
これから始まるChatOps
さて、ChatOpsという言葉を聞いたことがありますでしょうか。社内でも使ってる人を見かけないので、広まれば良いなと思っています。チャットでオペレーションしよう、そういうことです。
SlackでもDenoを使って簡単にワークフローが書けますし、今回紹介させていただいたようなワークフローを書けば、簡単に入門できると思います。
これは運用というほどのことをする例ではないかもしれませんが、こういう簡単なところから始めるChatOpsもいいのではないでしょうか。
ぜひ実践してみてください。
世界のラストワンマイルを最適化する、OPTIMINDのテックブログです。「どの車両が、どの訪問先を、どの順に、どういうルートで回ると最適か」というラストワンマイルの配車最適化サービス、Loogiaを展開しています。recruit.optimind.tech/
Discussion