3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GHECが使えるようになったのでActionsで作業効率化を図った話

Last updated at Posted at 2023-12-06

はじめに

こんにちは!ドワンゴ マネタイズプラットフォーム事業部所属のアズタケです。
主に、ニコニコの新しい購読システム( 定期お支払いの管理 )を開発しています。
この記事は ドワンゴ Advent Calendar 2023 の 3日目の記事です。日が過ぎても空いてたのでどうせなので投稿してみました。
よければ他の記事もご覧ください!

今回紹介するリポジトリ

https://github.com/dwpayment/generate-release-notes
(まだREADMEまともに書いてないのはご愛敬…)
以下みたいなのが表示できるようになります。
chrome_Zr8um0GN3T.png

きっかけ

マネタイズプラットフォーム事業部では、今年からGitHub Enterprise Cloudにプロジェクトを移行していっています。
それに伴い、GitHub Actionsを活用できるようになったので、この機に作業効率化しようと思って挑戦したのが発端でした。
(これまではActionsが無効化されたGitHub Enterprise Serverを用いていました…)
ドワンゴではリリース時に必ず承認権限保持者(大抵マネージャとかリーダーとか)が承認することになっています。特に課金系サービスでは慎重になる必要があり、
そのため投入するリリース物を一覧化するなど、承認者が承認しやすい書類をリリースの度に作る必要がありました。
しかしリリースの度にPR全てを列挙するのって以外に大変で…負担軽減のためにリリースの簡略化することにしました。

GitHub Actions

GitHub ActionsをCI/CDとして活用する記事はよくありますが、実はActionはJavaScriptで組むことが出来ます。
GitHub Actionsのワークフローはシェルコマンドの実行が出来るのでそれで行うことも可能なんですが、普段使い慣れてる言語で書けるのでビジネスロジックの実装にめちゃくちゃ便利でした。
しかもテンプレートをGitHubが公開してくれており、それをベースに作ることが出来ます。dependabotまで構成してくれてるのでマジで便利。

Actionの基本的な書き方

まずAcionの定義である action.yml にどんなパラメータがあるのかなど指定する必要があります。
今回のリリース自動生成Actionだとどの差分を取るか指定するためにbaseブランチとheadブランチの名前を指定するようにしています。

action.yml(抜粋)
inputs:
  base:
    required: true
    description: "base branch name"
  head:
    required: true
    description: "head branch name"
  token:
    description: "GITHUB_TOKEN or a repo scoped PAT."
    default: ${{ github.token }}

action.yml で指定したinputはコード上では以下のように取得することが出来ます。

main.ts
const base = core.getInput("base", { required: true })
const head = core.getInput("head", { required: true })
const token = core.getInput("token", { required: true })

token というパラメータを指定していることに気が付くかもしれませんが、githubではワークフローで ${{ github.token }} とすることでaction用のトークンを取得することが出来ます(GITHUB_TOKEN 環境変数でも出来るらしい)。
デフォルト値としてこれを指定しておくことでActionを呼び出すだけでGitHubのAPIが呼び出せるので便利です。

ここで取得したトークンを使って以下のようにすることでAPIクライアントを取得できます

main.ts
const kit = github.getOctokit(token)

このAPIクライアントを使って以下のようにするだけで超簡単にissueを取得できます。

main.ts
await kit.rest.issues.get({
  repo: {
      owner: "dwpayment",
      repo: "hogerepo",
  },
  issue_number: 11
})

ちなみにGitHub Actionsは実行時に何のissueがトリガーになったとか、今のリポジトリが何かなどコンテキストも一緒に付与してくれており、これはJSで実装したActionでも以下のようにして受け取ることが出来ます。

main.ts
const { repo, issue } = github.context
await kit.rest.issues.get({
  ...repo,
  issue_number: issue.number
})

めちゃくちゃシンプルになりますね!
かなり色んなAPIがそのまま触れるので、これらを使って実装していくことになります。

Gitの差分の取得

都度baseブランチとheadブランチを比較してしまうと、例えばbaseとheadが同じになった時にリリースノートが空になってしまう問題があるのでとりあえず先にbaseブランチのコミットハッシュを取得します。

Actionからシェルスクリプトを実行することも可能で、これらは

import * as exec from "@actions/exec"

に各種関数があります。今回のリポジトリではutility関数を作成して、それを呼び出して実行するようにしています。

rtelease-notes.ts
await execute(`git rev-parse ${base}`)

コミットハッシュを取ったら、git logからマージブランチだけを取り出します。
(正規表現とかはJS側でやればいいんだけど、当初シェルスクリプトだけで作ろうとした名残で全部シェルコマンドになってます…)

release-notes.ts
const numbers = (
await execute(
  `/bin/bash -c "git log --merges ${baseCommitId}..${head} --first-parent --grep='Merge pull request #' --format='%s' | sed -n 's/^.*Merge pull request #\\s*\\([0-9]*\\).*$/\\1/p'"`
)
).split("\n")

これで各種PR番号が取れるので、全てのPRをAPIから引っ張ってきます。

release-notes.ts
const prs = (
await Promise.allSettled(
  numbers
    .map(num => Number(num))
    .map(async num =>
      kit.rest.pulls.get({
        ...repo,
        pull_number: num
      })
    )
)
)
.filter(pr => pr.status === "fulfilled")
.map(pr => {
  if (pr.status === "rejected") throw new Error() // HACK
  return pr.value
})

あとはPRに付いたラベルを見て分類するだけ!

release-notes.ts
const bugs = prs.filter(pr => pr.data.labels.some(l => l.name === "bug"))

コメントを一つだけ付けて以降同じコメントを更新するようにしたい

ここまでで紹介した kit.rest ... を使うことでissueにコメントをぶら下げるだけならとても簡単に出来るようになります。
しかし、Action自体で状態を持たせることは出来ないため、例えばupsert的な動作(無ければ新規投稿し、あれば更新する)をさせるには工夫が必要です。

リリースの予定を立てた後に行う結合的な動作確認中にバグを見つけることは稀にあるのですが、出来ればそういったものもリリースノートに乗っていてほしいわけです。
(実はこれまで、そういったものは漏れていた場合がありました…。)

完全に投稿する内容が固定値であればそれをキーにする手がありますが、表示内容でマッチするの何か気持ち悪いですよね(私だけじゃないよね???)。

実はあまり知られていませんが、IssueコメントにはHTMLコメントを使うことが出来ます
実際にページ上に表示されないため変なシグネチャを表示する必要も無く、とても便利で、私が普段実装する時、これをキーにする場合が多いです。

test-procedure-comment.ts
const signature = "<!-- test procedure DO NOT REMOVE THIS LINE -->"

const commented = comments.find(
c => c.user?.type === "Bot" && c.body?.includes(signature)
)

!commented ならコメント投稿、そうでなければ更新するようにすれば以下のようにブランチに変化があったら自動で更新されるように出来ます。

image.png

まとめ

このActionを実装したことで、これまで1PRのデプロイでも超急いで15分ぐらいはかかっていたリリース作業が、複数PRがあってもボタンポチポチで1分もあれば出来るようになりました。

他にも、これまでは手作業でチケット起票・マージ用のPR作成を行っていたものに関しても自動化しており、これによってリリースサイクルを高めることが出来ています。
このリポジトリではdependabotを構成しているので必然的にPR数も多くなるのですが、負担無く対応できています!

image.png

image.png

マネタイズプラットフォーム事業部ではGitHub Actionsを使って積極的に開発効率化を図っています。
他にもk8sでactions runnerを立てて社内に最適化したランナーの稼働なども行っていますので、機会があったらまた記事にしようと思います。

宣伝

株式会社ドワンゴでは、様々なサービスやコンテンツを一緒に作るメンバーを募集しています!
ドワンゴが興味ある方、応募しようか悩んでいる方がいたら気軽にご応募お待ちしています!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?