見出し画像

Github Actionsでレビュワーを自動アサインする

こんにちは、Webエンジニアの山本です。こっちはクロネコのジジ。
ここ2ヶ月の間に、iPhone水没&液晶割れ・メガネを軽く拭いていたらフレームがボキッと折れてスカウター状態・電子レンジの突然死、という心温まる出来事がありました。おちこんだりもしたけれど、私は元気です。

弊社では最近、AFI(エーエフアイ) Daysというプチハッカソン的な取り組みを行いました。
AFI Daysでは普段の施策外では出来ない様なモダンな技術の調査や生産性の向上、負債解消など個人ごとに何をやるかを決めて各々活動します。
ちなみにAFIは弊社のエンジニア行動指針である下記3つの単語の頭文字です。 not アフィリエイト!!

Action: まずやる
Fastest: 最速で
Impact: インパクトあることを

何をしたか

コードレビューのアサイン自動化を試してみました。

弊社のレビュールールとして、特定の重要ファイル(config系など影響の大きいもの)の修正有無や変更行数の多寡によってアサインするメンバーを分けているのですが、レビュイーが都度判断するもの面倒くさいよねというのが動機です。

どうしたか

自動アサインの仕組みにはGitHub Actionsを使用しました。

Github Actionsを使用すればリポジトリへのプッシュやプルリクエストの作成/変更をトリガーとして特定の処理を実行することができます。
この処理はGithubが提供する仮想マシン上もしくはDockerコンテナで実行されるためこちらで環境を用意する必要がありません。
とってもうれしいですね。
一部リポジトリではCIツールとして自動テストに活用しています。

ディレクトリ構成
今回は下記のようなディレクトリ構成で作成しました。

.github/
 ├─ actions/
 │   └─ assign_reviewer/
 │       ├─ dist/
 │       │   └─ index.js
 │       ├─ src/
 │       │   └─ index.ts
 │       └─ action.yml
 └─ workflows/
     └─ reviewer.yml

流れとしてはワークフローからプライベートアクションを呼び出して実行する形になります。
まずreviewer.ymlから見ていきましょう

.github/workflows/reviewer.yml

name: Assign Reviewer
on:
 pull_request:
   types: [opened]
jobs:
 assign_reviewer_job:
   runs-on: ubuntu-latest
   name: A job to assign reviewer
   steps:
     - name: Checkout
       uses: actions/checkout@v2
     - name: Assign
       uses: ./.github/actions/assign_reviewer
       id: assign_reviewer
       with:
         github-token: ${{ secrets.GITHUB_TOKEN }}
     - name: Get the output time
       run: echo "The time was ${{ steps.assign_reviewer.outputs.time }}"

・on
ワークフローをトリガーするイベントを細かく設定できます。ここではP-Rが作成されたタイミングにのみ発火するようにしています。
設定可能なイベント

・jobs
ジョブの詳細設定です。ここでプライベートアクションファイルの指定やアクションに渡すinputの設定を行います。開発ブランチにしかアクションがない場合、事前にCheckoutしていないと動かないので注意してください。

・secrets.GITHUB_TOKEN
Github Action用にdefaultで用意されている認証トークンです。APIの操作で必要になるのでこれをwith経由で渡してアクション側で受け取れるようにします。

続いてaction.ymlを見ていきましょう

.github/actions/assign_reviewer/action.yml

name: 'Assign reviewer'
description: 'Action to automatically assign a reviewer'
inputs:
 github-token:
   description: 'github token'
   required: true
outputs:
 time: # id of output
   description: 'Current Time'
runs:
 using: 'node12'
 main: './dist/index.js'

ここでアクションを定義。inputs、outputsともにここでつけたキー名で受け渡しを行います。
重要なのはrunsで、mainキーのvalueに実行するファイルを渡します。
ちなみにプライベートアクションのymlはどこに置いてもいいですが、ファイル名は必ずaction.ymlかaction.yamlにしなければいけません。

最後にindex.jsです。

.github/actions/assign_reviewer/src/index.ts

import * as core from '@actions/core';
import * as github from '@actions/github';

async function run() {
 try {
   const pullRequestNumber = github.context.payload.number;
   const token = core.getInput("github-token");
   const octokit = github.getOctokit(token);
   
   const { data } = await octokit.pulls.listFiles({
     owner: github.context.repo.owner,
     repo: github.context.repo.repo,
     pull_number: pullRequestNumber,
   });
   let reviewers: string[];
   let changes = 0;
   data.forEach(file => changes += file.changes);
   if (changes > 200) {
     reviewers = ["hoge", "fuga"];
   } else {
     reviewers = ["bar", "baz"];
   }

   await octokit.pulls.requestReviewers({
     owner: github.context.repo.owner,
     repo: github.context.repo.repo,
     pull_number: pullRequestNumber,
     reviewers,
   });

   // 何か返したい値があればsetOutputに渡す
   const time = (new Date()).toTimeString();
   core.setOutput("time", time);
 } catch (error) {
   core.setFailed(error.message);
 }
}

run();

少し長いので分割していきます。

const token = core.getInput("github-token");
const octokit = github.getOctokit(token);

まずgetInputでwithで渡ってきた認証トークンを受け取り、そのトークンを使用して公式のGithub APIクライアントであるoctokitを初期化します。

const { data } = await octokit.pulls.listFiles({
  owner: github.context.repo.owner,
  repo: github.context.repo.repo,
  pull_number: pullRequestNumber,
});
let reviewers: string[];
let changes = 0;
data.forEach(file => changes += file.changes);
if (changes > 200) {
  reviewers = ["hoge", "fuga"];
} else {
  reviewers = ["bar", "baz"];
}​

変更行数によってレビュワーを変えたかったので、ファイルの一覧を取得して行数の判定を行っています。

await octokit.pulls.requestReviewers({
  owner: github.context.repo.owner,
  repo: github.context.repo.repo,
  pull_number: pullRequestNumber,
  reviewers,
});

ここが今回の核心です。選定したレビュワーをrequestReviewersのreviewers引数に渡すことでアサインされます。

と、これでP-R作成時に自動でレビュワーをアサインすることは出来ているんですが、時間の関係でまだ大きな課題を残したまま終わってしまいました。
レビュワーの対象からP-R作成者を外したり、バランス良くレビューを割り振ったりする処理を追加する必要がありますし、現状ではレビュワー候補者のリストをymlかactionのファイル内に記載しなければならず運用の手間があります。つらたん。

今後の改善ポイント

レビュワーの割り振りについては、requestReviewersの任意引数であるteam_reviewersにアサインしたいチームを渡せばGithubのCode review assignment機能で良い感じにチームメンバーを割り当ててくれるんじゃないかな〜と。

ただ、実際に書くと↓下記のエラーが発生してしまいます。

Error: Validation Failed: "Could not resolve to a node with the global id of 'XXXXXXXX'.

これがGithub APIのバグか仕様かはわかりませんが、おそらく自動生成される認証トークンにはレビュワーとしてチームをアサインする権限がないようです。

WorkAroundですがPersonalAccessTokenを使えばいけそうなので、引き続き試していきたいと思います!
Authentication in a workflow - GitHub Docs

この記事が気に入ったらサポートをしてみませんか?