リリースノートの作成を自動化しました

GitHub Actionsを活用して自動でリリースノートがNotion上に集約される仕組みを作りました

こんにちは!エンジニアの大森です。 今回、社内のほぼ全てのプロダクトの社内向けのリリースノートの作成をGitHub Actionsを活用して自動化し、Notion上に集約できる仕組みを整えました。この記事ではその背景や実現方法について紹介します。

背景

弊社プロダクトにはネイティブアプリ「ポケットサイン」に加え、その中で提供されるミニアプリと呼ばれるサービスがあります。2024年12月現在ミニアプリの数は10個ほどに増え、全てのプロダクトのリリース状況を把握することが難しくなってきました。

そこでリリースノートを集約する仕組みを整えるプロジェクトを開始することとなりました。

目的と方針

今回のプロジェクトの目的は各プロダクトのリリース状況及び内容を開発チームだけではなく社内全体、特に営業やマーケティング、カスタマーサクセスのメンバーが把握できるようにすることです。

これを実現するため、リリースノートはNotionデータベースに集約した上で作成を自動化することとしました。

リリースノート作成の自動化の実現方法

リリースノートが作成されるまでの流れは以下となります。

利用技術

GitHub Actions

弊社ではソースコード管理にGitHubを用いています。

GitHub ActionsはGitHubリポジトリにおけるイベントをトリガーとして何かしらの操作を実行することができるCI/CDで、今回のリリースノート作成のワークフローはGitHub上での「リリース」の作成をトリガーとして発火させています。

また、トリガーとなったイベントの情報はワークフロー内で利用することが出来ます。今回の取り組みではこの機能を活用し、後述の通りGitHubのリリースの内容をリリースノートに取り込んでいます。

Notion API

リリースノートの作成を自動化するにあたりNotion APIを用いることとしました。これによりリリース固有の内容と共通テンプレートを組み合わせてNotionデータベースのアイテムを作成するといった複雑な処理を実現することが可能になります。

なおNotionデータベースとは同一のプロパティを持つNotionページの集合です。この特徴はリリースノートのフォーマットを統一する上で適していました。

やったこと

リリースノートテンプレートの作成

リリースノートテンプレートとは、Notionで作成されるリリースノートの共通部分を記載したNotionテンプレートです。

リリースノートの構成は前半に各リリース固有の内容、後半にどのリリースでも共通の内容としました。リリースノートテンプレートは後半の共通部分の内容を記載したものになります。

リリースノートテンプレートの一部

このようにリリースノートテンプレートはNotionテンプレートとして作成しているのですが、Notionテンプレートとしての機能を使っているわけではありません。

上記の通りリリース固有の内容とテンプレートの内容を組み合わせて新しいページを作る必要がありましたが、Notionテンプレートではそこまで複雑な操作は実現出来ませんでした。

そこでリリースノートテンプレートの内容をNotion APIを用いて取得し、リリース固有の内容と組み合わせてリリースノートの本文を作成する材料として利用しています。

共通のアクションの作成

GitHub上のリリース内容を引数として受け取り、Notion APIで取得したリリースノートテンプレートと組み合わせてリリースノートの内容を作成し、Notion APIを叩いてリリースノートを作成するアクションを用意しました。

このアクションは複合アクション(Composite Action)と呼ばれるもので、別リポジトリに定義されています。これをそれぞれのプロダクトのリポジトリから呼び出す形で共通化しています。そのため、これを参照する全てのリポジトリでリリースノートの内容や構成を変更したい場合でも修正するアクションは一つで済みます。

name: Create Release Note
description: 'Create Release Note to Notion'

inputs:
  notion-integration-secret:
    description: Notion Integration Secret to access Notion API.
    required: true
  tag_name:
    description: 'The tag name of the release'
    required: true
  repository:
    description: 'The repository name of the release'
    required: true
  content:
    description: 'The content of the release'
    required: true
  release_url:
    description: 'The release URL of the release'
    required: true

runs:
  using: composite
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-go@v4
    - name: Generate Release Note to Notion
      run: go run ${{ github.action_path }}/main.go
      shell: bash
      env:
        RELEASE_TRIGGER_CLIENT_SECRET: ${{ inputs.notion-integration-secret }}
        RELEASE_TRIGGER_TAG_NAME: ${{ inputs.tag_name }}           # ex) v1.0.0
        RELEASE_TRIGGER_REPOSITORY_NAME: ${{ inputs.repository }}  # ex) pocketsign/bousai
        RELEASE_TRIGGER_CONTENT: ${{ inputs.content }}             # ex) ## What's Changed\n- Add feature A by @userA in ...
        RELEASE_TRIGGER_RELEASE_URL: ${{ inputs.release_url }}     # ex) https://github.com/pocketsign/bousai/releases/v1.0.0

共通アクションのサンプルコード。実際の処理は以下のmain.goで記述。

func main() {
    repoName := strings.Split(os.Getenv("RELEASE_TRIGGER_REPOSITORY_NAME"), "/")[1]
    tagName := os.Getenv("RELEASE_TRIGGER_TAG_NAME")
    content := os.Getenv("RELEASE_TRIGGER_CONTENT")
    releaseUrl := os.Getenv("RELEASE_TRIGGER_RELEASE_URL")
    clientSecret := os.Getenv("RELEASE_TRIGGER_CLIENT_SECRET") // Notion API client secret

    var templateIdMap = map[string]string{
      // NotionテンプレートのID
        "default":   "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        // プロダクト専用のテンプレートもここに並べる
    }

    templateId, ok := templateIdMap[repoName]
    if !ok {
        templateId = templateIdMap["default"]
    }

  // Notion APIを叩き、テンプレートを取得する
    productTemplate := retrieveProductSpecificBlocks(templateId, clientSecret)

  // 取得したテンプレートとGitHubのリリースの内容を組み合わせてリクエストBodyを作成する
    body := createBody(content, tagName, repoName, releaseUrl, productTemplate)
    fmt.Println(body)

    req, err := http.NewRequest("POST", "https://api.notion.com/v1/pages", strings.NewReader(body))
    if err != nil {
        fmt.Println(err)
        panic(err)
    }

    req.Header.Add("Authorization", "Bearer "+clientSecret)
    req.Header.Add("content-Type", "application/json")
    req.Header.Add("Notion-Version", "2022-06-28")

    client := &http.Client{}

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        panic(err)
    }
    fmt.Println(resp)
    if resp.StatusCode != 200 {
        panic(errors.New("Failed to create Notion page"))
    }
}

上記のアクション内で呼び出されているmain.goの一部。

共通アクションを呼び出すワークフローを各リポジトリに設置

GitHub上のリリースをトリガーとして発火されるワークフローです。このワークフローは先述の共通のアクションを呼び出して実行し、引数としてトリガーの元となったリリースの内容を渡します。

これはリリースノートを作成したい各リポジトリに設置する必要がありますが、その内容はリポジトリの内容に依存せず全く同じになります。

name: Trigger create release note workflow

on:
  release:
    types:
      - published

permissions:
  checks: write
  contents: read

jobs:
  dispatch-workflow:
    runs-on: ubuntu-latest
    steps:
      - uses: pocketsign/actions/create-release-note@main
        with:
          notion-integration-secret: ${{ secrets.NOTION_INTEGRATION_SECRET }}
          tag_name: ${{ github.event.release.tag_name }}
          repository: ${{ github.repository }}
          content: ${{ github.event.release.body }}
          release_url: ${{ github.event.release.html_url }}

リポジトリで設置するワークフロー。usesで先述の共通アクションを呼び出している。

Notion インテグレーションシークレット(API Key)の用意

Notion APIを叩くためのインテグレーションシークレット(以下 API Key)を発行します。

まずは新しいインテグレーションの作成ページからNotionデータベースを設置するワークスペースを選択してインテグレーションを作成し、その後発行されるAPI Keyを控えます。

作成されたインテグレーションのページ。内部インテグレーションシークレットというのがAPI Key。

作成されたAPI KeyはGitHub Organization Secretとして管理しています。各リポジトリから参照する上ではRepository Secretでも十分ですが、Organization内の全てのリポジトリで共通で用いることを考慮するとOrganization Secretで管理することが適しており、管理コストも小さく済みます。

このようにリリースノート作成の自動化にあたって諸々準備しましたが、各リポジトリで対応すべき内容はワークフローの設置のみです。しかもその内容はリポジトリ毎に変更する必要がないため非常に低コストで運用に載せることが可能です。

実際の運用の様子

生成されたリリースノート

目次だけピックアップすると以下のような内容になります。

リリース固有部分

ページタイトル

GitHub リリースのタグがページタイトルとなります。

弊社ではSemantic Versioningを採用しており、タイトルから大まかな影響の大きさを把握することが出来ます。

今回の例ではv1.0.0です。

プロダクト

リリースを打ったGitHubリポジトリ名がプロダクト名となります。

今回はsampleです。

リリース日

リリース予定日(実態に乖離があった場合は適宜修正)を記載します。

GitHub上のリリースを打つ日と本番のリリースを行う日は異なる場合があるため、ここは手入力する運用としています。

What’s Changed

これはGitHubでリリースを作成する際に自動的に生成される内容を転記したものです。

PR毎にタイトルと発行者、PRへのリンクが記載されます。

リリース共通部分

ここには本番環境リリース予定日、ユーザー影響箇所、リリース作業、リリース手順などが記載されています。ここはフォーマットに従ってリリース担当者が手入力していく部分となるので詳細は割愛します。

運用による効果

この取り組みの目的はリリース内容や状況を社内全体で把握しやすくすることでした。

まずリリース情報を一つのNotionデータベース上に集約することで開発チーム外のメンバーであっても参照すべき場所が分かりやすくなりました。

また、どのプロダクトでも同一のテンプレートに従っているためその内容の把握も容易になりました。

これは自動化による作成先やフォーマットの統一、及びその低コスト化により実現出来たと考えています。

まとめ

この記事ではGitHub ActionsやNotion APIを活用してリリースノートの作成を自動化することでリリースノートの集約を行なった話をまとめました。

今後も運用を続けながらフィードバックを受けて改善していきます。