-
-
Notifications
You must be signed in to change notification settings - Fork 25
chore(server): use lib to call GitHub to list available versions #1920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4a918ee
4fb842d
b25aabf
e0a4bf3
f2f9d8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,143 +2,30 @@ package io.github.typesafegithub.workflows.shared.internal | |
|
||
import arrow.core.Either | ||
import arrow.core.raise.either | ||
import arrow.core.raise.ensure | ||
import io.github.oshai.kotlinlogging.KotlinLogging.logger | ||
import io.github.typesafegithub.workflows.shared.internal.model.Version | ||
import io.ktor.client.HttpClient | ||
import io.ktor.client.call.body | ||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation | ||
import io.ktor.client.plugins.logging.LogLevel.ALL | ||
import io.ktor.client.plugins.logging.Logger | ||
import io.ktor.client.plugins.logging.Logging | ||
import io.ktor.client.request.bearerAuth | ||
import io.ktor.client.request.get | ||
import io.ktor.client.statement.bodyAsText | ||
import io.ktor.http.isSuccess | ||
import io.ktor.serialization.kotlinx.json.json | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.json.Json | ||
import java.time.ZonedDateTime | ||
import org.kohsuke.github.GHRef | ||
import org.kohsuke.github.GitHubBuilder | ||
|
||
private val logger = logger { } | ||
|
||
suspend fun fetchAvailableVersions( | ||
fun fetchAvailableVersions( | ||
owner: String, | ||
name: String, | ||
githubAuthToken: String?, | ||
githubEndpoint: String = "https://api.github.com", | ||
): Either<String, List<Version>> = | ||
either { | ||
buildHttpClient().use { httpClient -> | ||
return listOf( | ||
apiTagsUrl(githubEndpoint = githubEndpoint, owner = owner, name = name), | ||
apiBranchesUrl(githubEndpoint = githubEndpoint, owner = owner, name = name), | ||
).flatMap { url -> fetchGithubRefs(url, githubAuthToken, httpClient).bind() } | ||
.versions(githubAuthToken) | ||
} | ||
} | ||
|
||
private fun List<GithubRef>.versions(githubAuthToken: String?): Either<String, List<Version>> = | ||
either { | ||
this@versions.map { githubRef -> | ||
val version = githubRef.ref.substringAfterLast("/") | ||
Version(version) { | ||
val response = | ||
buildHttpClient().use { httpClient -> | ||
httpClient | ||
.get(urlString = githubRef.`object`.url) { | ||
if (githubAuthToken != null) { | ||
bearerAuth(githubAuthToken) | ||
} | ||
} | ||
} | ||
val releaseDate = | ||
when (githubRef.`object`.type) { | ||
"tag" -> response.body<Tag>().tagger | ||
"commit" -> response.body<Commit>().author | ||
else -> error("Unexpected target object type ${githubRef.`object`.type}") | ||
}.date | ||
ZonedDateTime.parse(releaseDate) | ||
} | ||
} | ||
} | ||
|
||
private suspend fun fetchGithubRefs( | ||
url: String, | ||
githubAuthToken: String?, | ||
httpClient: HttpClient, | ||
): Either<String, List<GithubRef>> = | ||
either { | ||
val response = | ||
httpClient | ||
.get(urlString = url) { | ||
val github = | ||
GitHubBuilder() | ||
.withEndpoint(githubEndpoint) | ||
.also { | ||
if (githubAuthToken != null) { | ||
bearerAuth(githubAuthToken) | ||
it.withOAuthToken(githubAuthToken) | ||
} | ||
} | ||
ensure(response.status.isSuccess()) { | ||
"Unexpected response when fetching refs from $url. " + | ||
"Status: ${response.status}, response: ${response.bodyAsText()}" | ||
} | ||
response.body() | ||
} | ||
}.build() | ||
val repository = github.getRepository("$owner/$name") | ||
val apiTags = repository.getRefs("tags").refsStartingWithV().map { Version(it) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FTR, it turned out that the lib doesn't support |
||
val apiHeads = repository.getRefs("heads").refsStartingWithV().map { Version(it) } | ||
|
||
private fun apiTagsUrl( | ||
githubEndpoint: String, | ||
owner: String, | ||
name: String, | ||
): String = "$githubEndpoint/repos/$owner/$name/git/matching-refs/tags/v" | ||
|
||
private fun apiBranchesUrl( | ||
githubEndpoint: String, | ||
owner: String, | ||
name: String, | ||
): String = "$githubEndpoint/repos/$owner/$name/git/matching-refs/heads/v" | ||
|
||
@Serializable | ||
private data class GithubRef( | ||
val ref: String, | ||
val `object`: Object, | ||
) | ||
|
||
@Serializable | ||
private data class Object( | ||
val type: String, | ||
val url: String, | ||
) | ||
|
||
@Serializable | ||
private data class Tag( | ||
val tagger: Person, | ||
) | ||
|
||
@Serializable | ||
private data class Commit( | ||
val author: Person, | ||
) | ||
|
||
@Serializable | ||
private data class Person( | ||
val date: String, | ||
) | ||
|
||
private fun buildHttpClient() = | ||
HttpClient { | ||
val klogger = logger | ||
install(Logging) { | ||
logger = | ||
object : Logger { | ||
override fun log(message: String) { | ||
klogger.trace { message } | ||
} | ||
} | ||
level = ALL | ||
} | ||
install(ContentNegotiation) { | ||
json( | ||
Json { | ||
ignoreUnknownKeys = true | ||
}, | ||
) | ||
} | ||
apiTags + apiHeads | ||
} | ||
|
||
private fun Array<GHRef>.refsStartingWithV() = map { it.ref.substringAfterLast('/') }.filter { it.startsWith("v") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FTR, it turned out that merely getting a reference to a repo makes another API call. I don't like it. I'll see how easy it is to disable this behavior in the lib.