はじめに
フューチャーアドベントカレンダー2023の1日目です。
OSSで有名なリポジトリ見ると、簡潔・明瞭で魅力的に整理されたREADMEが目に飛び込んでくることが多いです。matiassingers/awesome-readme はそういった美しいREADME.mdを集めたリポジトリです。どこか芸術めいたREADME.mdが放つ魔力に取り憑かれた界隈もあるようです。少し分かる気がします。
それと比較すると、普段私のメイン業務で用いているリポジトリ(もちろんプライベート)のREADMEはそれなりに作業時間を投資してメンテナンスしているものの、見ていて楽しいものではありませんでした。
理由の1つはバッジが無いことです。バッチとは次の画像のようなソフトウェアのポリシーや状態を示すラベルのようなものです。画像はbadges/shields からの引用です。
こうしたバッジの有無で普段のプロダクトの品質・生産性が大きく変わることは無いものの、新規参画者やステークホルダーが初めてリポジトリを見た時に、開発チームが自分たちの成果物に、関心が強く高い基準を設けていることを、視覚的に訴えることは決して悪いアプローチではないと思います。
何よりちょっとバッジが入ると見た目がCoolになります!楽しさ大事!思い入れがこれで少しでもできればラッキー!
ということで、プライベートリポジトリでバッジを追加する手順をまとめます。私は現在、GoでWebバックエンド開発を、CIはCircleCIを通して開発しているので、それらが中心です。
バッジの基本
Publicリポジトリであれば、比較的容易に多くのバッジを追加できます。逆に付けすぎると意図と意義が薄れるので、選抜するくらいの気持ちが必要です。
よくあるバッジのパターンは、shields.io というサービスを利用してCIの結果をYAMLに出力し、そこからsvgを生成して貼るといったパターンでしょう。 もちろん他にも様々な方式があります。
例えば、GoのWebアプリケーションフレームワークのEchoのREADMEには、 というバッジが貼られています。
これは次のようにリンクされています(見やすいように改行を入れています)。
[![GitHub Workflow Status (with event)]
(https://img.shields.io/github/actions/workflow/status/labstack/echo/echo.yml?style=flat-square)
](https://github.com/labstack/echo/actions)
このように、Publicリポジトリであれば外部サービスの力を借りて比較的容易にバッジを追加できます。素敵ですね。
バッジの種類
repo-badges というリポジトリに載っている通り、バッジの種類はたくさんあります。
- (npmなどの)ダウンロード数
- ビルド OK/NG
- Snyk などのセキュリティ
- チャット先(discordとかgitterなど)
- リリース日
- コントリビューション数
- ライセンス(クローズな時点で)
プライベートリポジトリに全てを載せても意味が無いので、載せて嬉しい情報を選抜する必要があります。
プライベートリポジトリへの適用
個人的にあっても良いかなと思ったのは次の4種類のバッチです。
- CI(CircleCI)の成功/失敗
- コード品質(GoReportCard)
- テストのコードカバレッジ
- 脆弱性チェック
プライベートリポジトリですと、ダウロード数などは無意味ですし、ライセンスも提示しないのが普通でしょう(All rights reservedが基本でしょう)。DiscordやSlackなどの相談先も、まぁ共通のアーキチームが作成したライブラリの相談先を、特定のチャットルームに誘導するのはありかもしれませんが、not for meでした。GoDocも共通で用いる処理をパッケージを切り出して運用しているようなケースであれば、生成しても良い気がしますが、Webバックエンドアプリ開発を主軸とする私にとってはピンと来なかったので調査対象外にしました。
1. CIの成功/失敗
CircleCIの以下のようなバッジを追加します。これがあると直近実行したCIが成功したかどうかすぐ分かり便利です。バッチのリンク先は、CircleCIのイメージであるcimg-goです。
プライベートリポジトリの場合は、CIが成功したかどうかも含めて認証が通っていないユーザーに対して参照を許したくないです。ではどうすべきかということですが、実は公式が手法を提供していて、Publicリポジトリ並みに容易に実現できます。
この手順はシンプルで、CircleCI側でアクセストークンを払い出してくれるので、そのトークン付きでCircleCIにリクエストを飛ばせばバッジを生成してくれます。アクセストークンはCIのステータスを参照するスコープに絞れば、リスクは極小化できるでしょう。
公式にプライベートリポジトリのバッジの作成の章 に書かれている通りですが、 circle-token=<YOUR_STATUS_API_TOKEN>
を追加するところがPublicリポジトリとの差分です。
[![CircleCI](https://circleci.com/<VCS>/<ORG_NAME>/<PROJECT_NAME>.svg?style=svg&circle-token=<YOUR_STATUS_API_TOKEN>)
](<LINK>)
こちらは比較的容易に実現可能ですので、CircleCIを利用されている方はバッジデビューしてはどうでしょうか。
2. GoReportCard
Go Report Cardというサービスがあります。ここにリポジトリのURLを入力すると、最高のA+から、A~Fまでのグレードで評価してくれます。以下はEchoのバッジで、「A+」の評価です。素敵ですね。
プライベートリポジトリにあっても、go reportのバッジが「A+」グレードにあると少し安心する気がします。調べてみると意外と「A+」評価なリポジトリが多く(というかそれ以外見ないような..)、xxxセレクション金賞的な空気もありますが、プライベートリポジトリの場合はどの程度品質が担保されているか最初は見えないので、最低限のラインは守れている保証にはなります。
さて、Go Report Cardですが、内部的にはGo Meta Linterというgolangci-lintの前身であるツールを利用していて、少し古い印象があります。もちろん、プライベートリポジトリではそのままでは利用できないので工夫が必要です。
go reportには、CLIツールが用意されていたのでこれを利用します。goreportcard-cliが依存している各種ツールもインストールしていきます。
# goreportcard-cli
go install github.com/gojp/goreportcard/cmd/goreportcard-cli@latest
# gometalinter
curl -L https://git.io/vp6lP | sh
手元の適当なリポジトリで動かしてみます。ineffassign, misspellが大きいリポジトリだと動かないと言われていますが...、A+グレードが取れたようです。 LICENSEは0%ですが、プライベートリポジトリにLICENSEファイルは配置したくないので、致し方なしかと思っています。
$ goreportcard-cli
2023/11/12 11:41:36 disabling ineffassign on large repo...
2023/11/12 11:41:36 disabling misspell on large repo...
Grade ........... A+ 93.7%
Files ................ 550
Issues ................. 7
gofmt ................ 99%
go_vet ............... 99%
ineffassign ......... 100%
gocyclo ............. 100%
license ............... 0%
misspell ............ 100%
さて、この goreportcard-cli
ですが、バッジを作る機能はないので、自分で作り込む必要があります。おそらくWebツール側の実装である handlers/badge.go を見ると、算出した結果を元に、shields.io に問い合わせてsvgを作成していました。
func badgeURL(grade check.Grade, style string) string {
var color string
switch grade {
case check.GradeAPlus:
color = "brightgreen"
case check.GradeA:
color = "green"
case check.GradeB:
color = "yellowgreen"
case check.GradeC:
color = "yellow"
case check.GradeD:
color = "orange"
case check.GradeE:
fallthrough
case check.GradeF:
color = "red"
}
return fmt.Sprintf("https://img.shields.io/badge/go%%20report-%s-%s.svg?style=%s", grade, color, style)
}
つまり、グレードがAだと https://img.shields.io/badge/go%20report-A-green.svg?style=flat
のURLで、 のバッジを取得できます。この判定ロジックを模して、
goreportcard-cli
の結果を元に、SVGを取得するツールはスクラッチで開発する必要がありますが、それほど難しくは無いでしょう(この記事では割愛します)。
実行場所ですが、簡易的には、CIでgo-report.svg を作成し、リポジトリのどこかにコミット。READEMEからそれを参照する方式が自然だと思います。ただし、メインブランチにそのsvgをコミットすると余計な履歴が積み上がるので、例えば、badge という名称で別のブランチを作成し、そのブランチにあるSVGファイルを参照する方式とします。
履歴が無しのbadge ブランチは、以下のように --orphan
オプションで作成します。
git checkout --orphan badge
ここにCIで作ったSVGファイルをコミットします。参照時はブランチ名を参照するため、以下のようなパスをREADMEに記載します。これにより、本流のブランチの履歴を汚さず、肥大化もさせずバッジを参照できるでしょう。
![](https://github.com/<Org>/<User>/blob/badge/go-report.svg?raw=true)
3. テストのコードカバレッジ
プライベートリポジトリに対して、コードカバレッジとバッジを表示に関しては、codeclimate、codecovなど複数のサービスが対応しています。それら素晴らしいサービスを利用したいところですが、プライベートリポジトリへの対応は有償プランに入る必要があり(codeclimateの例)、まずはカバレッジだけ様子見したいというケースには少し敷居が高く感じます(個人的感想です)。
ここでは、CIでカバレッジ計測+バッジ生成する方向で対応しようと考えます。カバレッジすの数値だけだとさすがに味気がないので、リンク先にコードカバレッジの詳細を表現するレポートファイルを探せるようにします。プライベートリポジトリの場合、HTMLをどこでホストするかは悩みどころです。今回はCircleCIではJUnit形式にファイルを出力すれば、レポートとして表示する機能があるため、これを利用します。
GoでJUnit形式のファイルを出力するためには、 https://github.com/gotestyourself/gotestsum を使うと楽です。CircleCIのcimg/goのベースイメージにも、予め組み込まれており、CI上では個別にインストールすることなく利用できます。
ローカルで試す場合は以下でインストールします。
go install gotest.tools/gotestsum@v1.11.0
JUnit形式で出力するには以下のように実行します。gotestsumは内部的には go test -json
をラップしているということで、go test のオプションも適時追加できます。-covermode
や -coverprofile
は後続のバッジ生成で使用するため、出力しています。
gotestsum --junitfile report.xml -- -covermode=count -coverprofile=cover.out
CircleCIのCollect test dataにかかれている通り、この出力したreport.xmlを、CircleCI上で参照できるように store_test_results
で追加します。
steps:
- run:
name: Run go test
command: gotestsum --junitfile report.xml -- -covermode=count -coverprofile=cover.out
- store_test_results:
path: ./reports/
こうしておけば、バッジ上からはCircleCIのジョブダッシュボードにリンクしておき、詳細は各CIのカバレッジファイルを確認すると行ったフローに落とせます(直接リンクを貼りたいところですが、毎回パスを書き換えるのも大変なので、これくらいで落とすのが良いのではないかと思っています)。
続いて、肝心のバッジの作成です。gobadgeというツールを用います。
go install github.com/AlexBeauchemin/gobadge@v0.3.0
gobadgeにはSVGを生成するオプションがまだ無く、READMEを直接書き換えるCoolな設計なようなので、temp.mdのような別のファイルにURLを書いてもらって、その値を用いてSVGを取得します。
# gobadgeが対応している形式に変換
go tool cover -func=cover.out -o=cover_summary.out
touch temp.md
gobadge -filename=cover_summary.out -target=temp.md
curl -sS $(sed -n 2p temp.md | grep -o 'https://[^)]*') > coverage.svg
出力したcoverage.svg は先述通り、badge リポジトリにコミットするとします。
READMEには以下のように記載します。
[![](https://github.com/<Org>/<User>/blob/badge/coverage.svg?raw=true
](https://app.circleci.com/pipelines/github/<Your Org>/<Your Repo>)
これでカバレッジがバッジで表現され、確認時はCIのレポートを参照できる様になりました。
4. 脆弱性チェック
Snyk (スニーク)という脆弱性管理サービスは、リポジトリのコードベースを参照して、脆弱性が含まれたライブラリなどを使っていないかチェックしてくれます。バッジは以下のようなものです。これがプライベートリポジトリに貼られていたら、セキュリティ意識が高そうなイメージを新規参画者が持ってくれそうです。
しかし、プライベートリポジトリには対応していないようです。残念..。
Private repos are not supported for badges.
プライベートリポジトリはバッジには対応していません
- Badge Support for Repositories
回避策としては、Go製のsnykバッチ生成ツールがあるようです。
プライベートリポジトリをSnykに連携していることが前提で、Snykのアクセストークンなど環境変数に設定し、サーバを起動すると http://localhost:8080/badge/{username}/{repo_name}/
によってバッジを生成するとのこと。これもGo Report Cardと同じ用にCIで設定すると良さそうです。
さいごに
全部まとめて貼るとこんな感じなりました。
プライベートリポジトリに対しても、少し見た目が楽しくなりますね。あまり増えたらダサいという言説もあるようですので、バッジは3,4つくらいが個人的にはちょうど良さそうに思えました。チームとして継続的にウォッチしたいものだけ貼っておくと良さそうです。
次は @bigface00 さんの 「Unreal Engine5で極めるゲーム開発の書評」です。