11package io.github.typesafegithub.workflows.jitbindingserver
22
33import io.github.oshai.kotlinlogging.KotlinLogging.logger
4- import io.github.reactivecircus.cache4k.Cache
5- import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
6- import io.github.typesafegithub.workflows.actionbindinggenerator.domain.prettyPrint
7- import io.github.typesafegithub.workflows.mavenbinding.Artifact
8- import io.github.typesafegithub.workflows.mavenbinding.JarArtifact
9- import io.github.typesafegithub.workflows.mavenbinding.TextArtifact
10- import io.github.typesafegithub.workflows.mavenbinding.buildPackageArtifacts
11- import io.github.typesafegithub.workflows.mavenbinding.buildVersionArtifacts
12- import io.github.typesafegithub.workflows.shared.internal.getGithubToken
13- import io.ktor.http.ContentType
14- import io.ktor.http.HttpHeaders.XRequestId
154import io.ktor.http.HttpStatusCode
165import io.ktor.server.application.ApplicationCall
17- import io.ktor.server.application.install
186import io.ktor.server.engine.embeddedServer
197import io.ktor.server.netty.Netty
20- import io.ktor.server.plugins.callid.CallId
21- import io.ktor.server.plugins.callid.callIdMdc
22- import io.ktor.server.plugins.callid.generate
23- import io.ktor.server.plugins.calllogging.CallLogging
24- import io.ktor.server.response.respondBytes
258import io.ktor.server.response.respondText
26- import io.ktor.server.routing.Route
27- import io.ktor.server.routing.get
28- import io.ktor.server.routing.head
29- import io.ktor.server.routing.route
309import io.ktor.server.routing.routing
31- import io.opentelemetry.instrumentation.ktor.v3_0.server.KtorServerTracing
32- import kotlin.time.Duration.Companion.hours
3310
3411private val logger =
3512 System
@@ -47,161 +24,18 @@ fun main() {
4724 Thread .setDefaultUncaughtExceptionHandler { thread, throwable ->
4825 logger.error(throwable) { " Uncaught exception in thread $thread " }
4926 }
50-
51- val bindingsCache =
52- Cache
53- .Builder <ActionCoords , Result <Map <String , Artifact >>>()
54- .expireAfterWrite(1 .hours)
55- .build()
56- val openTelemetry = buildOpenTelemetryConfig(serviceName = " github-actions-bindings" )
57-
5827 embeddedServer(Netty , port = 8080 ) {
59- install(CallId ) {
60- generate(
61- length = 15 ,
62- dictionary = " abcdefghijklmnopqrstuvwxyz0123456789" ,
63- )
64- replyToHeader(XRequestId )
65- }
66- install(CallLogging ) {
67- callIdMdc(" request-id" )
68- }
69- install(KtorServerTracing ) {
70- setOpenTelemetry(openTelemetry)
71- }
72- routing {
73- route(" {owner}/{name}/{version}/{file}" ) {
74- artifact(bindingsCache)
75- }
76-
77- route(" {owner}/{name}/{file}" ) {
78- metadata()
79- }
80-
81- route(" /refresh" ) {
82- route(" {owner}/{name}/{version}/{file}" ) {
83- artifact(bindingsCache, refresh = true )
84- }
28+ installPlugins()
8529
86- route(" {owner}/{name}/{file}" ) {
87- metadata(refresh = true )
88- }
89- }
30+ routing {
31+ internalRoutes()
9032
91- get(" /status" ) {
92- call.respondText(" OK" )
93- }
33+ artifactRoutes()
34+ metadataRoutes()
9435 }
9536 }.start(wait = true )
9637}
9738
98- private fun Route.metadata (refresh : Boolean = false) {
99- get {
100- if (refresh && ! deliverOnRefreshRoute) {
101- call.respondText(text = " Not found" , status = HttpStatusCode .NotFound )
102- return @get
103- }
104-
105- val owner = call.parameters[" owner" ]!!
106- val nameAndPath = call.parameters[" name" ]!! .split(" __" )
107- val name = nameAndPath.first()
108- val file = call.parameters[" file" ]!!
109- val actionCoords =
110- ActionCoords (
111- owner = owner,
112- name = name,
113- version = " irrelevant" ,
114- path = nameAndPath.drop(1 ).joinToString(" /" ).takeUnless { it.isBlank() },
115- )
116- val bindingArtifacts = actionCoords.buildPackageArtifacts(githubToken = getGithubToken())
117- if (file in bindingArtifacts) {
118- when (val artifact = bindingArtifacts[file]) {
119- is String -> call.respondText(artifact)
120- else -> call.respondText(text = " Not found" , status = HttpStatusCode .NotFound )
121- }
122- } else {
123- call.respondText(text = " Not found" , status = HttpStatusCode .NotFound )
124- }
125- }
126- }
127-
128- private fun Route.artifact (
129- bindingsCache : Cache <ActionCoords , Result <Map <String , Artifact >>>,
130- refresh : Boolean = false,
131- ) {
132- get {
133- val bindingArtifacts = call.toBindingArtifacts(bindingsCache, refresh)
134- if (bindingArtifacts == null ) {
135- call.respondText(" Not found" , status = HttpStatusCode .NotFound )
136- return @get
137- } else if (refresh && ! deliverOnRefreshRoute) {
138- call.respondText(text = " OK" )
139- return @get
140- }
141-
142- val file = call.parameters[" file" ]!!
143- if (file in bindingArtifacts) {
144- when (val artifact = bindingArtifacts[file]) {
145- is TextArtifact -> call.respondText(text = artifact.data())
146- is JarArtifact ->
147- call.respondBytes(
148- bytes = artifact.data(),
149- contentType = ContentType .parse(" application/java-archive" ),
150- )
151-
152- else -> call.respondText(text = " Not found" , status = HttpStatusCode .NotFound )
153- }
154- } else {
155- call.respondText(text = " Not found" , status = HttpStatusCode .NotFound )
156- }
157- }
158-
159- head {
160- val bindingArtifacts = call.toBindingArtifacts(bindingsCache, refresh)
161- val file = call.parameters[" file" ]!!
162- if (bindingArtifacts == null ) {
163- call.respondText(" Not found" , status = HttpStatusCode .NotFound )
164- return @head
165- }
166- if (file in bindingArtifacts) {
167- call.respondText(" Exists" , status = HttpStatusCode .OK )
168- } else {
169- call.respondText(text = " Not found" , status = HttpStatusCode .NotFound )
170- }
171- }
172- }
173-
174- private suspend fun ApplicationCall.toBindingArtifacts (
175- bindingsCache : Cache <ActionCoords , Result <Map <String , Artifact >>>,
176- refresh : Boolean ,
177- ): Map <String , Artifact >? {
178- val owner = parameters[" owner" ]!!
179- val nameAndPath = parameters[" name" ]!! .split(" __" )
180- val name = nameAndPath.first()
181- val version = parameters[" version" ]!!
182- val actionCoords =
183- ActionCoords (
184- owner = owner,
185- name = name,
186- version = version,
187- path = nameAndPath.drop(1 ).joinToString(" /" ).takeUnless { it.isBlank() },
188- )
189- logger.info { " ➡️ Requesting ${actionCoords.prettyPrint} " }
190- val bindingArtifacts =
191- if (refresh) {
192- actionCoords.buildVersionArtifacts().also {
193- bindingsCache.put(actionCoords, Result .of(it))
194- }
195- } else {
196- bindingsCache
197- .get(actionCoords) { Result .of(actionCoords.buildVersionArtifacts()) }
198- .getOrNull()
199- }
200- return bindingArtifacts
201- }
202-
203- private fun Result.Companion.failure (): Result <Nothing > = failure(object : Throwable () {})
204-
205- private fun <T > Result.Companion.of (value : T ? ): Result <T > = value?.let { success(it) } ? : failure()
39+ val deliverOnRefreshRoute = System .getenv(" GWKT_DELIVER_ON_REFRESH" ).toBoolean()
20640
207- private val deliverOnRefreshRoute = System .getenv( " GWKT_DELIVER_ON_REFRESH " ).toBoolean( )
41+ suspend fun ApplicationCall. respondNotFound () = respondText( " Not found " , status = HttpStatusCode . NotFound )
0 commit comments