見出し画像

Notionデータベースを使ってAssetBundleリリースを自動化した話 Now In REALITY Tech #119

アバターシステムチーム Unityエンジニアかとうです。
今回は、Notionデータベースを使ってAssetBundleのリリースを自動化したお話しです。

従来のAssetBundleリリース

REALITYでは毎週水曜14:00にAssetBundleのリリースを行なっています。AssetBundleには新規のアバターアイテム、ルームの家具、ギフトなどのアセットに加え、リリース済みのアセットの修正など様々なものが含まれます。

リリースプロセスでは、リリースしたいものが全て含まれているかを確認した上でAssetBundleのビルドを行う必要があります。しかし、その確認作業はリリース予定のアセット一覧とGitHub上のPR一覧を見比べて確認するという手間のかかるアナログなものでした。結果、毎週エンジニアが一定の工数をかける必要がありました。

Notionの導入

そんな状況の中、業務にNotionを導入することになりました。NotionにはNotionデータベースという簡易的なデータベースを作成する機能と、Notion上のデータに対して読み込み、書き込みができるAPIが存在します。これを利用して、リリースするものがちゃんと入っているかをチェックし、自動でビルドキックするシステムを構築することにしました。

自動化の設計

設計考え中

自動化はNotionデータベース、Jenkins、Pythonスクリプトを連携することで実現しました。具体的にはNotionデータベースに必要な情報を全て所持し、PythonからNotionデータベースにアクセスして情報を取得した上で、AssetBundleのリリースビルドが可能な状態かを判断。ビルド可能であればAssetBundleのビルドを行う、という仕組みです。(AssetBundleのビルドが行えない状態であればSlackにエラーメッセージを送信します)

また、エンジニアが個別のリリース項目について認識すること自体がコストになっていたため、Notionデータベースへの登録作業はPMに担当してもらい、エンジニアは自動リリース機能のメンテナンスのみを担当する形にしました。これは単にタスクをお願いしたというわけではなく、従来はアセットのリリース時期をPMが管理しているにも関わらず、PRマージをエンジニアが行うことで、エンジニアがリリース制御を行うという不効率なフローになっていました。そこで、PMがリリース制御を直接行えるようにフローを再構築したという形になります。これにより、より正確かつ効率的にリリース制御が行われるようになりました。

ただ、マージの実施についてはエンジニアが実施することになっていたので、エンジニアが認知しないということを実現するためにPMがNotionデータベースに登録した内容に基づきGitHubのPRを自動でマージする機能を実装しました。

各ツールの役割

  • Notionデータベース

    • リリースするAssetBundleのバージョン情報とリリース予定のアセット一覧を保持する。

    • Pythonスクリプトからアクセスされ、リリース予定のAssetBundleバージョンが存在する場合、そのバージョンに含まれる予定のすべてのアセットが揃っているかを確認する。

  • Jenkins

    • Jenkins 自動AssetBundleリリースジョブを月曜正午、火曜正午に定期実行する。

    • PythonスクリプトによるNotionデータベースのチェック結果を読み取り、問題がなければAssetBundleビルドを実行する。

    • Slackに自動リリースの実行結果を通知する。

  • Pythonスクリプト

    • Notionデータベースにアクセスし、リリース予定のAssetBundleバージョンが存在する場合、そのバージョンのリリース項目がすべてマージされているかを確認する。

    • 未マージの項目があり、ステータスが「リリースOK」でPRのURLが登録されている場合、PRを自動的にマージする。

    • 自動マージ後、すべてのリリース候補がマージされている場合、AssetBundleのビルドを実行する。

    • ビルドが完了したら、Notionデータベース上のバージョンを更新し、該当のバージョンを「リリース済み」ステータスにする。

フローチャート

大まかなフロー

NotionDBの構成

AssetBundleバージョン情報

ビルド日程、バージョン情報、ステータスを保持しています。

リリース予定アセット一覧

リリース名、リリース日、バージョン、PRへのリンク、自動マージしてOKかどうかのステータスを保持しています

Jenkinsパイプライン

以下はJenkinsのパイプラインスクリプトです。
※Slackへのメッセージ送信にはSlackNotificationAPIを使用しています。

stage('CheckNotionStatus') {
  steps {
    withCredentials([
      string(credentialsId: utility.getGhToken(), variable: 'git_token'),
      string(credentialsId: utility.getAccessToken(), variable: 'notion_token')
    ]) {
      script {
        // 月曜日の場合チェックモードを有効にする
        checkMode = isMonday() || params.FORCE_CHECK_MODE

        print("checkMode: ${checkMode}")

        // Notionデータベースの情報を用いてリリースのステータスチェックを行うPythonスクリプトを実行
        command = """
          python3 script/ab-python/hoghoge.py \
          --update_version_state_mode '' \
          --version_file_name ${version_file_name} \
          --error_file_name ${error_file_name} \
          --slack_message_file_name ${slack_message_file_name} \
          --error_case ${error_case} \
          --success_case ${success_case}
        """

        def result = sh(script: command, returnStdout: true).trim()
        lastLine = result.split('\n')[-1]

        version = read_text_content(version_file_name)
        slackMessage = read_text_content(slack_message_file_name)
        errorMsg = read_text_content(error_file_name)

        if (lastLine == error_case) {
          slackMessageWithHeader = getNotionCheckFailureMessage(slack_mention_group, errorMsg, checkMode, notion_db_url)
          slackSend(channel: reality_asset_git_operation_ch, color: 'danger', message: slackMessageWithHeader)
          is_error = true
        } else {
          slackMessageWithHeader = getNotionCheckSuccessMessage(slack_mention_group, slackMessage, checkMode)
          slackSend(channel: reality_asset_git_operation_ch, color: 'good', message: slackMessageWithHeader)

          if (!checkMode) {
            if (!params.SKIP_TAG) {
              sh 'git checkout develop'
              sh 'git pull'
              sh "git tag ${version}"
              sh "git push origin ${version}"
            }

            // ビルドジョブをキック
            def buildResult = build(job: 'release_ab_build_job', parameters: [
              string(name: 'tags', value: version),
              string(name: 'minimum_ver', value: version),
              string(name: 'memo', value: version)
            ])

            // ビルド結果を通知
            result = buildResult.result
            print("buildResult: ${result}")
            def slackMessage
            if (result == 'SUCCESS') {
              slackMessage = unity_asset_operation_en + "\n prod ABビルドに成功しました、今週のリリース担当ENは問題ないようであればサポツから公開設定を行なってください: ${result}"
              slackSend(channel: reality_asset_git_operation_ch, color: 'good', message: slackMessage)
            } else {
              slackMessage = unity_asset_operation_en + "\n prod ABビルドに失敗しました: ${result}"
              slackSend(channel: reality_asset_git_operation_ch, color: 'danger', message: slackMessage)
              error("ABビルドに失敗しました: ${result}")
            }
          }
        },,,

Notion連携

Notion上のリリース項目管理データベースとAssetBundleバージョン管理データベースからNotion API経由で情報を取得し、チェックしています。以下のPythonコードを参考にNotionデータベースから情報を取得できます。

# Notionデータベースから情報を取得する関数
def get_notion_data(database_id, param):
    headers = {
        "Authorization": f"Bearer {key_set.get_notion_api_key()}",
        "Notion-Version": "2022-06-28",
        "Content-Type": "application/json"
    }
    # database_idはNotionデータベースのURLに含まれる文字列です
    url = f"https://api.notion.com/v1/databases/{database_id}/query"
    response = requests.post(url, headers = headers, data = param)

    if response.status_code == 200:
        data = response.json()
        if len(data["results"]) > 0:
            return data
    return None

GitHub連携

GitHub API経由でPRのマージ情報を取得し、ghコマンド経由でレビュー情報の取得を行なっています。レビュー情報の取得に関しては、API経由だとレビュワーにアサインされていない人のコメントなどもレビュー情報として取得されるため、「アサインされたレビュワー全員が承認しているかどうか」という情報を取得する用途としては使い勝手が悪かったので、ghコマンド経由で情報を取得するというアプローチを取りました。

ちなみに、REALITYはmasterブランチに向けたマージを行う際に自動テストをパスしている必要があるのですが、必ずしも自動マージの対象ブランチが自動テストを実行済みというわけではありません、そういう場合"Enable Auto merge"を有効にして、自動テスト実施後にマージが行われるようにするのですが、この場合実際にマージが行われるまでラグがあり、マージが本当に成功するかも不明なため、処理フローの最後に5分間待った上で最終的にマージできたかどうかを取得し本当にマージできたかを確認するという処理を行っています。

# GItAPIでマージを試みる マージが実行がされた場合はTrueを返す(実際にマージが完了したかどうかは保証しない)
def merge_pr(repo_owner, repo_name, pr_number):
    api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls/{pr_number}/merge"
    headers = {
        "Authorization": f"Bearer {key_set.git_token()}",
        "Accept": "application/vnd.github.v3+json"
    }
    response = requests.put(api_url, headers=headers)

    if(response.status_code != 200):
       return enable_auto_merge(repo_owner, repo_name, pr_number)

    #マージが実行された
    return True
# GitCLI経由でPRのレビューステータス最新を取得
    command = ['gh', 'pr', 'view', pr_number, '--repo', f'{repo_owner}/{repo_name}', '--json', 'latestReviews']
    result = subprocess.run(command, stdout=subprocess.PIPE, check=True).stdout.decode('utf-8')
    result_json = json.loads(result)
    ## 全員がApprovedになっているかを確認
    for latestReview in result_json["latestReviews"]:
        if latestReview['state'] != 'APPROVED':
            return False
    return True

結果

最適化によりルーチン作業が撲滅されました!

AssetBundleのリリースを自動化した結果、AssetBundleリリースにかかるエンジニアのコストをほぼ0にすることができました。エンジニアの実作業コストと認知コストがなくなり、開発作業など別のタスクにリソースを割けるようになりました。今後も効率化できる作業を見つけたら、どんどん効率化していきたいと思います!