Skip to content

Commit 07ed8d9

Browse files
authored
refactor: replace custom HTTP client with GitHub API library when fetching installation token (#1889)
Part of #1884. Simplified access token handling by leveraging `org.kohsuke:github-api` library, removing custom HTTP client and serialization logic.
1 parent ad385d8 commit 07ed8d9

File tree

3 files changed

+23
-66
lines changed

3 files changed

+23
-66
lines changed

shared-internal/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ dependencies {
2020
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.2")
2121
implementation("io.github.oshai:kotlin-logging:7.0.6")
2222
implementation("com.auth0:java-jwt:4.5.0")
23-
23+
implementation("org.kohsuke:github-api:1.327")
24+
2425
// It's a workaround for a problem with Kotlin Scripting, and how it resolves
2526
// conflicting versions: https://youtrack.jetbrains.com/issue/KT-69145
2627
// As of adding it, ktor-serialization-kotlinx-json depends on kotlinx-io-core:0.4.0,

shared-internal/src/main/kotlin/io/github/typesafegithub/workflows/shared/internal/GitHubApp.kt

Lines changed: 13 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,80 +3,38 @@ package io.github.typesafegithub.workflows.shared.internal
33
import com.auth0.jwt.JWT
44
import com.auth0.jwt.algorithms.Algorithm
55
import io.github.oshai.kotlinlogging.KotlinLogging.logger
6-
import io.ktor.client.HttpClient
7-
import io.ktor.client.call.body
8-
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
9-
import io.ktor.client.plugins.logging.LogLevel.ALL
10-
import io.ktor.client.plugins.logging.Logger
11-
import io.ktor.client.plugins.logging.Logging
12-
import io.ktor.client.request.header
13-
import io.ktor.client.request.post
14-
import io.ktor.serialization.kotlinx.json.json
15-
import kotlinx.serialization.Serializable
16-
import kotlinx.serialization.Transient
17-
import kotlinx.serialization.json.Json
6+
import org.kohsuke.github.GHAppInstallationToken
7+
import org.kohsuke.github.GitHubBuilder
188
import java.security.KeyFactory
199
import java.security.interfaces.RSAPrivateKey
2010
import java.security.spec.PKCS8EncodedKeySpec
2111
import java.time.Instant
22-
import java.time.ZonedDateTime
2312
import java.util.Base64
2413

2514
private val logger = logger { }
2615

16+
private var cachedAccessToken: GHAppInstallationToken? = null
17+
2718
/**
2819
* Returns an installation access token for the GitHub app, usable with API call to GitHub.
2920
* If `null` is returned, it means that the environment wasn't configured to generate the token.
3021
*/
3122
@Suppress("ReturnCount")
32-
suspend fun getInstallationAccessToken(): String? {
23+
fun getInstallationAccessToken(): String? {
3324
if (cachedAccessToken?.isExpired() == false) return cachedAccessToken!!.token
3425
val jwtToken = generateJWTToken() ?: return null
35-
val appInstallationId = System.getenv("APP_INSTALLATION_ID") ?: return null
36-
cachedAccessToken =
37-
httpClient
38-
.post("https://api.github.com/app/installations/$appInstallationId/access_tokens") {
39-
header("Accept", "application/vnd.github+json")
40-
header("Authorization", "Bearer $jwtToken")
41-
header("X-GitHub-Api-Version", "2022-11-28")
42-
}.body()
43-
return cachedAccessToken!!.token
44-
}
45-
46-
private var cachedAccessToken: Token? = null
26+
val installationId = System.getenv("APP_INSTALLATION_ID") ?: return null
4727

48-
@Suppress("ConstructorParameterNaming")
49-
@Serializable
50-
private data class Token(
51-
val token: String,
52-
val expires_at: String,
53-
val permissions: Map<String, String>,
54-
val repository_selection: String,
55-
) {
56-
@Transient
57-
private val expiresAtDateTime = ZonedDateTime.parse(expires_at)
28+
val gitHubApp = GitHubBuilder().withJwtToken(jwtToken).build().app
5829

59-
fun isExpired() = ZonedDateTime.now().isAfter(expiresAtDateTime)
30+
cachedAccessToken =
31+
gitHubApp.getInstallationById(installationId.toLong()).createToken().create().also {
32+
logger.info { "Fetched new GitHub App installation access token for repository ${it.repositorySelection}" }
33+
}
34+
return cachedAccessToken!!.token
6035
}
6136

62-
private val httpClient =
63-
HttpClient {
64-
val klogger = logger
65-
install(Logging) {
66-
logger =
67-
object : Logger {
68-
override fun log(message: String) {
69-
klogger.trace { message }
70-
}
71-
}
72-
level = ALL
73-
}
74-
install(ContentNegotiation) {
75-
json(
76-
Json { ignoreUnknownKeys = false },
77-
)
78-
}
79-
}
37+
private fun GHAppInstallationToken.isExpired() = Instant.now() >= expiresAt.toInstant()
8038

8139
@Suppress("ReturnCount", "MagicNumber")
8240
private fun generateJWTToken(): String? {

shared-internal/src/main/kotlin/io/github/typesafegithub/workflows/shared/internal/GithubUtils.kt

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.github.typesafegithub.workflows.shared.internal
22

33
import io.github.oshai.kotlinlogging.KotlinLogging.logger
4-
import kotlinx.coroutines.runBlocking
54

65
private val logger = logger { }
76

@@ -11,15 +10,14 @@ private val logger = logger { }
1110
* The token may be of various kind, e.g. a Personal Access Token, or an
1211
* Application Installation Token.
1312
*/
14-
fun getGithubAuthTokenOrNull(): String? =
15-
runBlocking {
16-
runCatching { getInstallationAccessToken() }
17-
.onFailure {
18-
logger.warn(it) {
19-
"Failed to get GitHub App Installation token, falling back to GITHUB_TOKEN."
20-
}
21-
}.getOrNull() ?: System.getenv("GITHUB_TOKEN")
22-
}.also { if (it == null) logger.warn { ERROR_NO_CONFIGURATION } }
13+
fun getGithubAuthTokenOrNull(): String? {
14+
val installationAccessToken =
15+
runCatching {
16+
getInstallationAccessToken()
17+
}.onFailure { logger.warn(it) { "Failed to get GitHub App Installation token." } }.getOrNull()
18+
19+
return installationAccessToken ?: System.getenv("GITHUB_TOKEN")
20+
}
2321

2422
/**
2523
* Returns a token that should be used to make authorized calls to GitHub,

0 commit comments

Comments
 (0)