🎉

SlackのGithub Botで自動リリースノートを作った話

2023/12/08に公開

zenn初記事です。よろしくお願いします。

こんな感じで、本番反映が完了したタイミングにSlackチャンネルに変更内容を投下しています。

リリースごとに報連相はツライよ

皆さんは事業部に機能の追加、更新、削除やバグ修正などの連絡をどのように行っているでしょうか。
開発サイクル、デプロイ頻度を高めて生産性を高めるというのが昨今のトレンドですが、その度にビジネスサイドにちゃんと連絡を行っているでしょうか。

実際のデプロイのたびに連絡するのは、それこそ非機能要件のリファクタなど、ビジネスサイドが認知できないものも多々あり、わざわざ連絡したくない雰囲気があると思います。
結局私の経験だと、本番への反映はリスクがある行為でありながらも、めぼしいもののときだけ簡易なテキストで送る、という着地になることが多いです。

しかしこの状態は属人的に運用されるため、以下のデメリットが考えられます。

  • 軽微な変更、またはバグ修正であっても、ビジネスサイドがほしい連絡がされない可能性がある
  • 連絡する場合でも、本番反映から手動送信では数分から数時間のタイムラグが生じる
  • 非機能要件に取り組むメンバーの活躍がビジネスサイドから見えにくい

とはいえ、厳しくルール作りして解消するなら、その負荷はそれなりのものです。開発サイクルの早い良好なチームほど負担は大きくなります。
今回は手間かけずこの状態を解消するためと、加えて以下の目的で、リリースノートの自動化を試みました

  • 「誰が」「どのリリース」に貢献したか小さいものでも見えるようにする
  • 取り組んだ施策の数、バグ修正の数を小さいものも含め、後に振り返れるようにする
  • ビジネスサイド(非エンジニア)が抱く「エンジニアがちゃんと仕事してるのか分からん」問題の軽減
  • プルリクのタイトルが雑になりがち

技術選定でGithub Botを選んだ経緯

プロダクト自体の機能開発ではないため、時間をかけず実装したかったからです。時短するために以下のことを考えました

  • mainブランチのプルリクの自動生成テキストをそのまま使いたい(git-pr-releaseを使っています。)
  • プルリク文中のGithubユーザーメンションを、Slackメンションに変換したい

時短のためプルリクのテキストをそのまま引用するのは良いとして、そうなるとプルリクのメンションがSlackに変換できないと、Slackにリリースノートを投下してもエンジニア以外は誰のことかさっぱり分かりません。

ここを解決できないか考えていたのですが、Github Botでの/github subscribeできる通知メッセージは既にこの問題を解決しています。このbotが投下するメッセージは全てメンション変換済みです。よって、このBotをなんとか流用できないかと考えたわけです。

(※メンション変換は開発者各人が/github signinを実行して連携を完了している必要があります。参考

ここでちょっとしたハックですが、Slackに投下してほしいのは、マージ後にCIが走り本番反映したタイミングであり、プルリクのopen,closeではありません。そこでBotのsubscribe条件式にラベルが使えることに着目して、本番反映したタイミングでghコマンドでラベル付与とコメントを行いました。そうすることで投稿されるタイミングを制御することができます。

手順1/2、Slackチャンネルを整える

プルリクのコメントは本体のスレッドにつくようにします。(現在はデフォでそうなってるようですが一応)

/github settingsしてDisable threading for Pull Request and Issue notifications.をenabledにします。

次にレポジトリをサブスクライブします。

# 実行するのはこれ(org/repoは読み替えてください)
/github subscribe org/repo comments +label:‘released’

# 結果
✅ Subscribed to org/repo. This channel will receive notifications for
issues, pulls, commits, releases, deployments, comments, +label:'released'

# 余計なものが色々ついてきたので、unsubscribeします。
/github unsubscribe org/repo issues pulls commits releases deployments

# こうなったらOKです
This channel will receive notifications from org/repo for: comments, +label:'released'

手順2/2、CIにてプルリクにラベリング、コメントするghコマンドを追加

肝心な行はたったこれだけです。本番反映された後のstepで実行します。

gh pr edit ${{ github.event.pull_request.number }} --add-label "released"
gh pr comment ${{ github.event.pull_request.number }} --body "上記リリースされました!"

Event typeがプルリクであること、パーミッション、PATなども含め少し補足すると、以下のようになります。
(permissionは不要なものあるかもしれません🙏)

name: Release applications

on:
  pull_request:
    branches:
      - main
    types: [closed]

permissions:
  id-token: write
  contents: read
  issues: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Release
        uses: ./.github/actions/release # ここでリリース、反映まで待機
      - name: Notify release note
        run: |
          gh pr edit ${{ github.event.pull_request.number }} --add-label "released"
          gh pr comment ${{ github.event.pull_request.number }} --body "上記リリースされました!"
        env:
          GH_TOKEN: ${{secrets.G8_GH_TOKEN}}

また、CircleCIを使っているプロダクトの場合はプルリクの番号がとれない、ghが無いので、以下のように少しゴリ押しが必要でした。PR_NUMBERが正しいか担保されていませんが、高頻度でマージされることがないので、ヨシとしています。

# org/repoは読み替えてください
orbs:
  gh: circleci/github-cli@2.2.0

jobs:
  release:
    steps:
      - release # ここでリリース、反映まで待機
      - gh/setup
      - run:
          name: Notify release note
          command: |
            PR_NUMBER=$(gh pr list --repo org/repo --state merged --base main --limit 1 --json number --json number | jq -r '.[0].number')
            gh pr edit $PR_NUMBER --repo org/repo --add-label "released"
            gh pr comment $PR_NUMBER --repo org/repo --body "上記リリースされました!"

うまく動けば、本番反映後、指定のチャンネルにプルリクのメッセージが到着するはずです。

おまけ。プルリクのタイトルもCIで統制する

プルリクのタイトルがそのまま事業のメンバー全体に共有されるので、プルリクのタイトルは良い意味で気を遣う必要があります。しかし、ビジネスサイドに分かるように非機能要件のタイトルや、まだ表面的な変化をもたらさない実装までタイトリングを心がけるのは大変です。よって、プルリクのタイトルには以下のプレフィックスを設けるルールとし、「新機能リリース」と「バグ修正リリース」以外はエンジニアさえ分かれば良い、というようにルールを軟化しています。そしてビジネスサイドも基本的に「新機能リリース」と「バグ修正リリース」が先頭にあるタイトルだけ注目すれば良く、双方に負荷が低い状態になります。

  • 新機能リリース → 事業部が認知できる「意図する」挙動をリリースするとき
  • 新機能準備 → 新機能リリースに繋がるが、まだ挙動変更がないとき
  • バグ修正リリース → 事業部が認知できる元々「意図しなかった」挙動が直るとき
  • バグ修正準備 → バグ修正リリースに繋がるが、まだ挙動変更がないとき
  • 非機能要件 → 挙動が変わらず事業部が認知できないないものすべて

そして、毎回このprefixを入力してもらうのも申し訳ないので、この処理を少し自動化しました。これらプレフィックスをlabelで用意しており、レビューに臨みます。そしてマージ時にはCIにてラベルに応じて、プルリクのタイトルの先頭にラベルを付与しています。

ラベル付与を強制するCIはこちらです。

name: Check

on:
  pull_request:
    branches-ignore:
      - main

permissions:
  id-token: write
  contents: write
  pull-requests: read

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Check if PR has required labels
        id: check
        uses: actions/github-script@v5.1.0
        with:
          script: |
            const pr = await github.rest.pulls.get({ // rerunした際に最新のラベル情報が更新されるようにする
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            });
            const currentLabels = pr.data.labels.map((label) => label.name);
            const requiredLabels = ["新機能リリース", "新機能準備", "バグ修正準備", "バグ修正リリース", "非機能要件"];
            const countOfRequiredLabels = requiredLabels.filter((label) => currentLabels.includes(label)).length;
            if(countOfRequiredLabels !== 1) {
              core.setFailed(`PRには次のうち1つのラベルが必要です。付与してから、このjobをrerunしてください。${requiredLabels.map((label) => `"${label}"`).join(", ")}`);
            }

マージ時にプルリクタイトルを更新するCIはこちら

name: modify-title
on:
  pull_request:
    branches:
      - development
    types: [closed]

jobs:
  modify-title:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true
    steps:
      - name: Update PR title
        uses: actions/github-script@v5
        with:
          script: |
            const pr = context.payload.pull_request;
            const requiredLabels = ["新機能リリース", "新機能準備", "バグ修正準備", "バグ修正リリース", "非機能要件"];
            const label = pr.labels.find(label => requiredLabels.includes(label.name)).name;
            if (!pr.title.startsWith(`【${label}】`)) {
              const newTitle = `【${label}】 ${pr.title}`;
              await github.rest.pulls.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.issue.number,
                title: newTitle
              });
            }

副次的に各人やプロダクトが実行できた施策数を見える化もできたので、振り返りに活用していこうと思います。
以上です。

ゲームエイトテックブログ

Discussion