Skip to content

Commit 0b3a860

Browse files
committed
refactor(server): simplify GitHub API usage
- Replace manual HTTP client implementation with `kohsuke.github` library. - Simplify `fetchAvailableVersions` logic and remove redundant dependencies. - Refactor tests to use `kotest` with `MockServer` for more concise and structured testing. - Update dependencies and remove unused code.
1 parent 79aa7f2 commit 0b3a860

File tree

9 files changed

+310
-322
lines changed

9 files changed

+310
-322
lines changed

action-updates-checker/src/main/kotlin/io/github/typesafegithub/workflows/updates/Utils.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.typesafegithub.workflows.updates
22

3-
import arrow.core.getOrElse
43
import io.github.typesafegithub.workflows.domain.ActionStep
54
import io.github.typesafegithub.workflows.domain.Workflow
65
import io.github.typesafegithub.workflows.domain.actions.RegularAction
@@ -28,7 +27,7 @@ internal suspend fun Workflow.availableVersionsForEachAction(
2827
groupedSteps.forEach { (action, steps) ->
2928
val availableVersions =
3029
action.fetchAvailableVersionsOrWarn(
31-
githubAuthToken = githubAuthToken,
30+
githubAuthToken = githubAuthToken ?: error("github auth token is required"),
3231
)
3332
val currentVersion = Version(action.actionVersion)
3433
if (availableVersions != null) {
@@ -49,7 +48,7 @@ internal suspend fun Workflow.availableVersionsForEachAction(
4948
}
5049
}
5150

52-
internal suspend fun RegularAction<*>.fetchAvailableVersionsOrWarn(githubAuthToken: String?): List<Version>? =
51+
internal fun RegularAction<*>.fetchAvailableVersionsOrWarn(githubAuthToken: String): List<Version>? =
5352
try {
5453
fetchAvailableVersions(
5554
owner = actionOwner,
@@ -58,7 +57,7 @@ internal suspend fun RegularAction<*>.fetchAvailableVersionsOrWarn(githubAuthTok
5857
).getOrElse {
5958
throw Exception(it)
6059
}
61-
} catch (e: Exception) {
60+
} catch (_: Exception) {
6261
githubError(
6362
"failed to fetch versions for $actionOwner/$actionName, skipping",
6463
)

maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
package io.github.typesafegithub.workflows.mavenbinding
22

3-
import arrow.core.Either
43
import arrow.core.getOrElse
54
import io.github.oshai.kotlinlogging.KotlinLogging.logger
65
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
76
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL
8-
import io.github.typesafegithub.workflows.shared.internal.fetchAvailableVersions
97
import io.github.typesafegithub.workflows.shared.internal.model.Version
108
import java.time.format.DateTimeFormatter
9+
import io.github.typesafegithub.workflows.shared.internal.fetchAvailableVersions as defaultFetchAvailableVersions
1110

1211
private val logger = logger { }
1312

1413
internal suspend fun ActionCoords.buildMavenMetadataFile(
1514
githubAuthToken: String,
16-
fetchAvailableVersions: suspend (
15+
fetchAvailableVersions: (
1716
owner: String,
1817
name: String,
19-
githubAuthToken: String?,
20-
) -> Either<String, List<Version>> = ::fetchAvailableVersions,
18+
githubAuthToken: String,
19+
) -> Result<List<Version>> = ::defaultFetchAvailableVersions,
2120
): String? {
2221
val availableVersions =
2322
fetchAvailableVersions(owner, name, githubAuthToken)

maven-binding-builder/src/test/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuildingTest.kt

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.github.typesafegithub.workflows.mavenbinding
22

3-
import arrow.core.Either
4-
import arrow.core.right
53
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
64
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion
75
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL
@@ -22,17 +20,19 @@ class MavenMetadataBuildingTest :
2220

2321
test("various kinds of versions available") {
2422
// Given
25-
val fetchAvailableVersions: suspend (String, String, String?) -> Either<String, List<Version>> = { _, _, _ ->
26-
listOf(
27-
Version(version = "v3-beta", dateProvider = { ZonedDateTime.parse("2024-07-01T00:00:00Z") }),
28-
Version(version = "v2", dateProvider = { ZonedDateTime.parse("2024-05-01T00:00:00Z") }),
29-
Version(version = "v1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
30-
Version(version = "v1.1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
31-
Version(version = "v1.1.0", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
32-
Version(version = "v1.0.1", dateProvider = { ZonedDateTime.parse("2024-03-05T00:00:00Z") }),
33-
Version(version = "v1.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
34-
Version(version = "v1.0.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
35-
).right()
23+
val fetchAvailableVersions: (String, String, String?) -> Result<List<Version>> = { _, _, _ ->
24+
Result.success(
25+
listOf(
26+
Version(version = "v3-beta", dateProvider = { ZonedDateTime.parse("2024-07-01T00:00:00Z") }),
27+
Version(version = "v2", dateProvider = { ZonedDateTime.parse("2024-05-01T00:00:00Z") }),
28+
Version(version = "v1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
29+
Version(version = "v1.1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
30+
Version(version = "v1.1.0", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
31+
Version(version = "v1.0.1", dateProvider = { ZonedDateTime.parse("2024-03-05T00:00:00Z") }),
32+
Version(version = "v1.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
33+
Version(version = "v1.0.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
34+
),
35+
)
3636
}
3737

3838
val xml =
@@ -62,14 +62,16 @@ class MavenMetadataBuildingTest :
6262

6363
test("no major versions") {
6464
// Given
65-
val fetchAvailableVersions: suspend (String, String, String?) -> Either<String, List<Version>> = { _, _, _ ->
66-
listOf(
67-
Version(version = "v1.1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
68-
Version(version = "v1.1.0", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
69-
Version(version = "v1.0.1", dateProvider = { ZonedDateTime.parse("2024-03-05T00:00:00Z") }),
70-
Version(version = "v1.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
71-
Version(version = "v1.0.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
72-
).right()
65+
val fetchAvailableVersions: (String, String, String?) -> Result<List<Version>> = { _, _, _ ->
66+
Result.success(
67+
listOf(
68+
Version(version = "v1.1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
69+
Version(version = "v1.1.0", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
70+
Version(version = "v1.0.1", dateProvider = { ZonedDateTime.parse("2024-03-05T00:00:00Z") }),
71+
Version(version = "v1.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
72+
Version(version = "v1.0.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
73+
),
74+
)
7375
}
7476

7577
val xml =
@@ -83,8 +85,8 @@ class MavenMetadataBuildingTest :
8385

8486
test("no versions available") {
8587
// Given
86-
val fetchAvailableVersions: suspend (String, String, String?) -> Either<String, List<Version>> = { _, _, _ ->
87-
emptyList<Version>().right()
88+
val fetchAvailableVersions: (String, String, String?) -> Result<List<Version>> = { _, _, _ ->
89+
Result.success(emptyList())
8890
}
8991

9092
val xml =
@@ -99,17 +101,19 @@ class MavenMetadataBuildingTest :
99101
(SignificantVersion.entries - FULL).forEach { significantVersion ->
100102
test("significant version $significantVersion requested") {
101103
// Given
102-
val fetchAvailableVersions: suspend (String, String, String?) -> Either<String, List<Version>> = { owner, name, _ ->
103-
listOf(
104-
Version(version = "v3-beta", dateProvider = { ZonedDateTime.parse("2024-07-01T00:00:00Z") }),
105-
Version(version = "v2", dateProvider = { ZonedDateTime.parse("2024-05-01T00:00:00Z") }),
106-
Version(version = "v1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
107-
Version(version = "v1.1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
108-
Version(version = "v1.1.0", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
109-
Version(version = "v1.0.1", dateProvider = { ZonedDateTime.parse("2024-03-05T00:00:00Z") }),
110-
Version(version = "v1.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
111-
Version(version = "v1.0.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
112-
).right()
104+
val fetchAvailableVersions: (String, String, String?) -> Result<List<Version>> = { owner, name, _ ->
105+
Result.success(
106+
listOf(
107+
Version(version = "v3-beta", dateProvider = { ZonedDateTime.parse("2024-07-01T00:00:00Z") }),
108+
Version(version = "v2", dateProvider = { ZonedDateTime.parse("2024-05-01T00:00:00Z") }),
109+
Version(version = "v1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
110+
Version(version = "v1.1", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
111+
Version(version = "v1.1.0", dateProvider = { ZonedDateTime.parse("2024-03-07T00:00:00Z") }),
112+
Version(version = "v1.0.1", dateProvider = { ZonedDateTime.parse("2024-03-05T00:00:00Z") }),
113+
Version(version = "v1.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
114+
Version(version = "v1.0.0", dateProvider = { ZonedDateTime.parse("2024-03-01T00:00:00Z") }),
115+
),
116+
)
113117
}
114118

115119
val xml =

shared-internal/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ dependencies {
3131
// Here's a ticket to remember to remove this workaround: https://github.com/typesafegithub/github-workflows-kt/issues/1832
3232
runtimeOnly("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0")
3333

34-
testImplementation("io.ktor:ktor-client-mock:3.1.2")
34+
testImplementation("io.kotest.extensions:kotest-extensions-mockserver:1.3.0")
35+
testImplementation("org.slf4j:slf4j-simple:2.0.12")
3536
}
Lines changed: 14 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,22 @@
11
package io.github.typesafegithub.workflows.shared.internal
22

3-
import arrow.core.Either
4-
import arrow.core.raise.either
5-
import arrow.core.raise.ensure
6-
import io.github.oshai.kotlinlogging.KotlinLogging.logger
73
import io.github.typesafegithub.workflows.shared.internal.model.Version
8-
import io.ktor.client.HttpClient
9-
import io.ktor.client.call.body
10-
import io.ktor.client.engine.HttpClientEngineFactory
11-
import io.ktor.client.engine.cio.CIO
12-
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
13-
import io.ktor.client.plugins.logging.LogLevel.ALL
14-
import io.ktor.client.plugins.logging.Logger
15-
import io.ktor.client.plugins.logging.Logging
16-
import io.ktor.client.request.bearerAuth
17-
import io.ktor.client.request.get
18-
import io.ktor.client.statement.bodyAsText
19-
import io.ktor.http.isSuccess
20-
import io.ktor.serialization.kotlinx.json.json
21-
import kotlinx.serialization.Serializable
22-
import kotlinx.serialization.json.Json
23-
import java.time.ZonedDateTime
4+
import org.kohsuke.github.GHRef
5+
import org.kohsuke.github.GitHubBuilder
246

25-
private val logger = logger { }
26-
27-
suspend fun fetchAvailableVersions(
7+
fun fetchAvailableVersions(
288
owner: String,
299
name: String,
30-
githubAuthToken: String?,
31-
httpClientEngineFactory: HttpClientEngineFactory<*> = CIO,
32-
): Either<String, List<Version>> =
33-
either {
34-
buildHttpClient(engineFactory = httpClientEngineFactory).use { httpClient ->
35-
return listOf(
36-
apiTagsUrl(owner = owner, name = name),
37-
apiBranchesUrl(owner = owner, name = name),
38-
).flatMap { url -> fetchGithubRefs(url, githubAuthToken, httpClient).bind() }
39-
.versions(githubAuthToken, httpClientEngineFactory)
40-
}
41-
}
42-
43-
private fun List<GithubRef>.versions(
44-
githubAuthToken: String?,
45-
httpClientEngineFactory: HttpClientEngineFactory<*>,
46-
): Either<String, List<Version>> =
47-
either {
48-
this@versions.map { githubRef ->
49-
val version = githubRef.ref.substringAfterLast("/")
50-
Version(version) {
51-
val response =
52-
buildHttpClient(engineFactory = httpClientEngineFactory).use { httpClient ->
53-
httpClient
54-
.get(urlString = githubRef.`object`.url) {
55-
if (githubAuthToken != null) {
56-
bearerAuth(githubAuthToken)
57-
}
58-
}
59-
}
60-
val releaseDate =
61-
when (githubRef.`object`.type) {
62-
"tag" -> response.body<Tag>().tagger
63-
"commit" -> response.body<Commit>().author
64-
else -> error("Unexpected target object type ${githubRef.`object`.type}")
65-
}.date
66-
ZonedDateTime.parse(releaseDate)
67-
}
68-
}
69-
}
70-
71-
private suspend fun fetchGithubRefs(
72-
url: String,
73-
githubAuthToken: String?,
74-
httpClient: HttpClient,
75-
): Either<String, List<GithubRef>> =
76-
either {
77-
val response =
78-
httpClient
79-
.get(urlString = url) {
80-
if (githubAuthToken != null) {
81-
bearerAuth(githubAuthToken)
82-
}
83-
}
84-
ensure(response.status.isSuccess()) {
85-
"Unexpected response when fetching refs from $url. " +
86-
"Status: ${response.status}, response: ${response.bodyAsText()}"
87-
}
88-
response.body()
10+
githubAuthToken: String,
11+
githubEndpoint: String = "https://api.github.com",
12+
): Result<List<Version>> =
13+
runCatching {
14+
val github = GitHubBuilder().withEndpoint(githubEndpoint).withOAuthToken(githubAuthToken).build()
15+
val repository = github.getRepository("$owner/$name")
16+
val apiTags = repository.getRefs("tags").refsStartingWithV().map { Version(it) }
17+
val apiHeads = repository.getRefs("heads").refsStartingWithV().map { Version(it) }
18+
19+
apiTags + apiHeads
8920
}
9021

91-
private fun apiTagsUrl(
92-
owner: String,
93-
name: String,
94-
): String = "https://api.github.com/repos/$owner/$name/git/matching-refs/tags/v"
95-
96-
private fun apiBranchesUrl(
97-
owner: String,
98-
name: String,
99-
): String = "https://api.github.com/repos/$owner/$name/git/matching-refs/heads/v"
100-
101-
@Serializable
102-
private data class GithubRef(
103-
val ref: String,
104-
val `object`: Object,
105-
)
106-
107-
@Serializable
108-
private data class Object(
109-
val type: String,
110-
val url: String,
111-
)
112-
113-
@Serializable
114-
private data class Tag(
115-
val tagger: Person,
116-
)
117-
118-
@Serializable
119-
private data class Commit(
120-
val author: Person,
121-
)
122-
123-
@Serializable
124-
private data class Person(
125-
val date: String,
126-
)
127-
128-
private fun buildHttpClient(engineFactory: HttpClientEngineFactory<*>) =
129-
HttpClient(engineFactory) {
130-
val klogger = logger
131-
install(Logging) {
132-
logger =
133-
object : Logger {
134-
override fun log(message: String) {
135-
klogger.trace { message }
136-
}
137-
}
138-
level = ALL
139-
}
140-
install(ContentNegotiation) {
141-
json(
142-
Json {
143-
ignoreUnknownKeys = true
144-
},
145-
)
146-
}
147-
}
22+
private fun Array<GHRef>.refsStartingWithV() = map { it.ref.substringAfterLast('/') }.filter { it.startsWith("v") }

0 commit comments

Comments
 (0)