これは、GitHub Actions Advent Calendar 2021の10日目の記事です。
こんにちは、つい先日(12月初めに)無料で使える中品質なテキスト音声合成ソフトウェア、VOICEVOXのOSS版レビュワーになった、Yちゃんです。
レビュワーになった・レビュワーに選んでもらえた経緯はいろいろあると思うのですが、いろんな形で貢献できたことが一つあるかなと思います。
そんな中で、私がGitHub Actionsを用いて貢献した例を一つ紹介していこうかなと思います。
CI整備をするIssueを引き受けた
VOICEVOXには主に、音声ライブラリを保持するコア、コアを用いて音声合成に必要な処理を仲介するエンジン、エンジンの処理結果を用いてUIを構成するエディタの3つのパーツがあります。
コアは版権等々の問題により、完全にOSSではありませんが、それ以外の部分は完全にOSSです。
その中で、エンジンにテストを書いてCIを整備するIssueが立ち、私はそれを引き受けました。
現状、まだテストは完全ではありませんが、かなり進められたと思います(大体80%ぐらい進めました)。
テスト整備のモチベーション
テストの整備は、ソフトウェア改善のための貢献ではなく、ソフトウェアの品質を保つための貢献になります。
ユーザーから見て変化が見えない貢献となり、とても地道な作業になります。
そのため、モチベーションが保ちにくく、取り組む人も少ないというところがあります。
そこで、私は一つの指標としてテストのCoverageを用い、それをモチベーションにすることにしました(この手法はよく用いられると思います)。
ということで、Coverageがどの程度なのかを可視化したいというIssueを立てました。
Coverageの可視化(バッジ化)とPull Requestに対するコメント
Issueに対応する形のPull Requestを立てました。
また、同時に機能実装やテスト実装でどの程度増減するのかをコメントしてくれる形のものも追加しました。
これは、Pull Requestがマージされる前に、どの程度テストが実装されているかを確認できる良い指標となると踏んだので、追加したものです。
バッジ化はよかったのですが、Pull Requestに対するコメントに関してが、地獄の始まりでした...
Pull RequestをトリガーとしたActionsはいろいろ制限がかかる
これを失念していました...
何と、リポジトリ所有者以外からのPull Requestには、GitHub ActionsがPull Requestに対してコメントする権限がないのです!
また、その権限を別で持たせたPersonal TokenをActions secretsに登録したとしてもアクセス権限がありません。
自分のリポジトリにPRするのはうまく動いたのに、オリジナルリポジトリへのPRでは動かないといった状況に少し躓きました。
pull_request_target
トリガーで対応できるものの...
pull_request
トリガーがセキュリティ的にアクセスできないものが存在するという中で、これを使うというのはセキュリティ的に穴を作ってしまいかねません。
なので、別の手段を用いる必要がありました。
それがworkflow_run
トリガーです。
workflow_run
トリガーでPull Requestにコメントする
workflow_run
トリガーは、指定したGitHub Actionsのワークフローが開始(正確にはリクエストされた、requested
)もしくは完了(completed
)した際に走るワークフローになります。
このトリガーで起動されたワークフローは、Pull Requestへの書き込み権限や、Secretsなどへのアクセス権限を持ちます。また、workflow_run
がトリガーとなるActionsを書き換えるようなPull Requestが来ても、それはマージされるまで反映されません(pull_request
トリガーによって実行されるワークフローは、新しくPull Requestが作成された際に、実行して結果を知ることができますが、workflow_run
ではそれができません)。よって、セキュリティリスクも防ぐことができ、安全です。
ということで、これを使ってPull Requestにコメントを行うActionsを書いていきましょう。
コメントしたいPull Requestの番号と内容をテキスト化し、artifacts
としてアップロード
残念ながら、workflow_run
だけでは、それまでに行われてきた作業情報を引き継ぐことができません。
というわけで、それまでの作業情報を保存し、artifacts
としてアップロードする必要があります。
今回の場合を例にして、実際に実行可能なGitHub Actionsのワークフローを書いていきます。
重要な点等は、test.yaml
の中にコメントとして書いています。
※実際に使われているワークフローとは差分があります。実際に使われているものはこちらをご覧下さい。
name: test
on:
pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
# 今回は、テストに最低限必要なpytestとcoverallsだけを入れます。
# 本来であれば、requirements.txtとかもインストールしなければなりませんが、
# 今回は省略します。
- name: Install dependencies
run: |
python -m pip install pytest coveralls
# Coverageを取得しながらテストを回す。
- name: Run pytest and get coverage
run: |
coverage run --omit=test/* -m pytest
# 次のPull Requestコメント用ワークフローのために、Coverageレポートを作成します。
# ここで重要なのは、"report/pr_num.txt"です。
# workflow_runで実行されたワークフローは、
# 自分がどのようなタイプのワークフローの後に呼び出されたのかしか記録していないため、
# 単体ではコメントすべきPull Requestはわかりません。
# なので、Pull Requestの番号(github.event.number)を記録しておきます。
- name: Create coverage result
run: |
mkdir report
coverage report > report/report.txt
echo ${{ github.event.number }} > report/pr_num.txt
# workflow_runで呼び出されるワークフローのために、
# 作成したレポートとPull Request番号のファイルをartifactsとしてアップロードします。
- name: Upload coverage result
uses: actions/upload-artifact@v2
with:
name: report
path: report/
これで、コメントしたい情報をまとめてartifacts
にアップロードすることができます。
workflow_run
で実行されるワークフローを作り、Pull Requestにコメントする。
もうここまでくれば後は簡単です。
先ほどのワークフローで作成されたartifacts
を取得し、そのデータをもとにPull Requestにコメントするだけになります。
こちらも、重要な点等のコメントはファイル内に記載しています。
※実際に使われているワークフローとは差分があります。実際に使われているものはこちらをご覧下さい。
name: Pull Request Comment
# トリガーとなるワークフローのnameとtypes(requested/completed)を指定します。
# 今回は、テストが終わってから実行してほしいので、completedを使います。
on:
workflow_run:
workflows:
- test
types:
- completed
jobs:
comment:
runs-on: ubuntu-latest
# このワークフローが呼ばれるまでの情報は何も保持されないと言いましたが、
# 一応このワークフローを呼んだワークフローの実行タイプや成功したか否かなど、
# 一部の情報は取得できるので、これらを実行条件とします。
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
steps:
- name: Download coverage report
uses: actions/github-script@v5.0.0
with:
# GitHub Script(Octokit)を使って、
# artifactsがアップロードされている場所の情報を取得し、
# その後実際に作った情報をダウンロードします。
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
})
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == 'report'
})[0]
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
})
const fs = require('fs')
fs.writeFileSync('${{github.workspace}}/report.zip', Buffer.from(download.data))
# 取得した情報の解凍
- name: Unzip report
run: unzip report.zip
# 取得した情報のコメント
# 今回は整形などせず、コンソール上で得られたそのままをコメントします
# ここで"secrets.GITHUB_TOKEN"が必要となります。
# Coverageの計測は、Pull Requestが更新されるたびに行われますが、
# 一度コメントしたら、Coverage情報の更新は、そのコメントを編集する形にします。
- name: Comment to Pull Requests
uses: actions/github-script@v5.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs')
const report = fs.readFileSync('report.txt', 'utf8').toString()
const issue_number = Number(fs.readFileSync('pr_num.txt'))
const body = `## Coverage Result\n\n<details>\n<summary>Resultを開く</summary>\n\n${report}\n</details>`
let listComments = await github.rest.issues.listComments({
issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
})
listComments = listComments.data.filter((comment) => {
return comment.body.includes('Coverage Result') && comment.user.login.includes('github-actions')
})
if (listComments.length === 0) {
github.rest.issues.createComment({
issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
})
} else {
github.rest.issues.updateComment({
comment_id: listComments[0].id,
owner: context.repo.owner,
repo: context.repo.repo,
body,
})
}
これで、実際にPull RequestにCoverageのデータを掲載することができます。
今回はデータの整形はしていませんが、データを整形していい感じにするとこんな感じになります。
まとめ
Pull RequestにGitHub Actionsからコメントをしたいときは、workflow_run
とartifacts
、後はgithub-script
を使って、いい感じにしよう!
感想
VOICEVOXがOrganizationになってから、Coverallsが勝手にコメントしてくれるようになったので、せっかく頑張ったのにちょっと悲しい...。