Retty Tech Blog

実名口コミグルメサービスRettyのエンジニアによるTech Blogです。プロダクト開発にまつわるナレッジをアウトプットして、世の中がHappyになっていくようなコンテンツを発信します。

GitHub Actions + Kotlin Scriptでアプリリリースの定形作業を減らす

アプリチームの松田です。
アプリのリリース時には様々な作業を行いますが、面倒な作業があったのでGitHub Actionsを使用して、一部を自動化しました。
AndroidとアプリバックエンドにKotlinを使用しているので、Kotlin Scriptを使用しました。

リリース作業の流れ

  1. developからバージョン番号で release/x.x.x のようなブランチを作成する
  2. masterとdevelopに向けてPull Reqeustを作成する
  3. マージされたPull Request一覧をIssueに書き出し、検証依頼を出す

マージされたPull Requestを一覧にするのが面倒だったのでこちらを自動化しました。

このように一覧になります。
f:id:rettydev:20220303134157p:plain

実装

YAML

Java11からの機能を使用するので、それ以上を指定します。( java.net.http.HttpClient )
デフォルトで11ですが、以前にGitHub Actions側の間違いで、数日間デフォルトがJava8に戻った事があったので、ちゃんと指定します。
https://github.com/actions/virtual-environments/pull/4903

コメント用URL等は環境変数に入っていないので、ここで環境変数に入れます。

name: master merge pr list

on:
  pull_request:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 0
    - uses: actions/setup-java@v2
      with:
        distribution: 'temurin'
        java-version: '17'
    - name: Run Script
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        COMMENT_URL: ${{ github.event.pull_request.comments_url }}
      run: |
        git checkout $GITHUB_HEAD_REF
        ./.github/workflows/master-merge-pr-list.main.kts

Kotlin Script

GitHub ActionsではデフォルトでKotlin Scriptが使用できるので、 #!/usr/bin/env kotlin を先頭に記述します。

処理は以下のような流れとなっています。

  • gitコマンドを実行してmasterからPull RequestまでのマージPRを取得する
  • GitHubのマージ時のコメントからPull RequestのIDを取得する
  • GitHubAPIを使用してPull Requestにコメントをする

.github/workflows/master-merge-pr-list.main.kts

#!/usr/bin/env kotlin
@file:DependsOn("com.google.code.gson:gson:2.8.9")
@file:OptIn(ExperimentalStdlibApi::class)

import com.google.gson.Gson
import java.io.File
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

val commentUrl = System.getenv("COMMENT_URL")
val githubToken = System.getenv("GITHUB_TOKEN")
val githubOwner = System.getenv("GITHUB_REPOSITORY_OWNER")
val githubServerUrl = System.getenv("GITHUB_SERVER_URL")
val githubRepository = System.getenv("GITHUB_REPOSITORY")

val currentBranchName = System.getenv("GITHUB_HEAD_REF")
val command = "git log --pretty=format:‘%s’ --first-parent --merges origin/master..${currentBranchName}"
println(command)
val numbers = Runtime.getRuntime().exec(
    command,
    null,
    File(".")
).let { process ->
    process.errorStream.use { stream ->
        stream.bufferedReader().use { reader ->
            reader.lineSequence()
                .onEach { System.err.println(it) }
                .toList()
        }
    }

    process.inputStream.use { stream ->
        stream.bufferedReader().use { reader ->
            val regex = """Merge pull request #(\d.+?) """.toRegex()
            val notRegex = """Merge pull request #(\d.+?) from ${githubOwner}/release""".toRegex()
            reader.lineSequence()
                .onEach { println(it) }
                .filterNot {
                    notRegex.find(it) != null
                }
                .mapNotNull {
                    regex.find(it)
                }
                .mapNotNull {
                    it.groupValues.getOrNull(1)
                }.toList()
        }
    }
}

data class Comment(val body: String)

val urls = numbers.map { number ->
    "${githubServerUrl}/${githubRepository}/pull/$number"
}
val comment = Comment(
    buildList {
        add("# Pull Request一覧")
        addAll(urls)
        add("```")
        addAll(urls)
        add("```")
    }.joinToString("\n")
)

val json = Gson().toJson(comment)
println(json)

val request = HttpRequest.newBuilder()
    .uri(URI.create(commentUrl))
    .header("Content-Type", "application/json")
    .header("Authorization", "token $githubToken")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build()

val response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString())

println(response)

おわりに

今はPull Requestの一覧を出すだけですが、リリース時の検証用Issueの作成まで自動化するのも良いかなと思っています。

また、このコードはiOSAndroidリポジトリで使用されているのですが、両方に同じコードが入っています。これらのコードは同じ場所で管理したいと思っています。以下の機能が実装されれば、privateリポジトリでMarketplaceのような機能が使用できるようになるようなのでこの機能の実装に期待しています。
https://github.com/github/roadmap/issues/74