Skip to content

Commit e4349d1

Browse files
LeoColmankrzema12
andauthored
refactor(server): split ktor config into separate files (#1816)
This PR prepares the ground to add metrics by refactoring the code to enhance metric-attachment-points. --------- Co-authored-by: Piotr Krzemiński <[email protected]>
1 parent bb0a28f commit e4349d1

File tree

6 files changed

+183
-173
lines changed

6 files changed

+183
-173
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.github.typesafegithub.workflows.jitbindingserver
2+
3+
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4+
import io.ktor.http.Parameters
5+
6+
fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords {
7+
val owner = this["owner"]!!
8+
val nameAndPath = this["name"]!!.split("__")
9+
val name = nameAndPath.first()
10+
val path = nameAndPath.drop(1).joinToString("/").takeUnless { it.isBlank() }
11+
val version = if (extractVersion) this["version"]!! else "irrelevant"
12+
13+
return ActionCoords(owner, name, version, path)
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package io.github.typesafegithub.workflows.jitbindingserver
2+
3+
import 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.buildVersionArtifacts
11+
import io.ktor.http.ContentType
12+
import io.ktor.http.HttpStatusCode
13+
import io.ktor.server.application.ApplicationCall
14+
import io.ktor.server.response.respondBytes
15+
import io.ktor.server.response.respondText
16+
import io.ktor.server.routing.Route
17+
import io.ktor.server.routing.Routing
18+
import io.ktor.server.routing.get
19+
import io.ktor.server.routing.head
20+
import io.ktor.server.routing.route
21+
import kotlin.time.Duration.Companion.hours
22+
23+
private val logger = logger { }
24+
25+
typealias ArtifactResult = Result<Map<String, Artifact>>
26+
27+
private val bindingsCache = Cache.Builder<ActionCoords, ArtifactResult>().expireAfterWrite(1.hours).build()
28+
29+
fun Routing.artifactRoutes() {
30+
route("{owner}/{name}/{version}/{file}") {
31+
artifact(refresh = false)
32+
}
33+
34+
route("/refresh/{owner}/{name}/{version}/{file}") {
35+
artifact(refresh = true)
36+
}
37+
}
38+
39+
private fun Route.artifact(refresh: Boolean = false) {
40+
headArtifact(refresh)
41+
getArtifact(refresh)
42+
}
43+
44+
private fun Route.headArtifact(refresh: Boolean) {
45+
head {
46+
val bindingArtifacts = call.toBindingArtifacts(refresh) ?: return@head call.respondNotFound()
47+
48+
val file = call.parameters["file"] ?: return@head call.respondNotFound()
49+
50+
if (file in bindingArtifacts) {
51+
call.respondText("Exists", status = HttpStatusCode.OK)
52+
} else {
53+
call.respondNotFound()
54+
}
55+
}
56+
}
57+
58+
private fun Route.getArtifact(refresh: Boolean) {
59+
get {
60+
val bindingArtifacts = call.toBindingArtifacts(refresh) ?: return@get call.respondNotFound()
61+
62+
if (refresh && !deliverOnRefreshRoute) return@get call.respondText("OK")
63+
64+
val file = call.parameters["file"] ?: return@get call.respondNotFound()
65+
66+
val artifact = bindingArtifacts[file] ?: return@get call.respondNotFound()
67+
68+
when (artifact) {
69+
is TextArtifact -> call.respondText(artifact.data())
70+
is JarArtifact -> call.respondBytes(artifact.data(), ContentType.parse("application/java-archive"))
71+
else -> call.respondNotFound()
72+
}
73+
}
74+
}
75+
76+
private suspend fun ApplicationCall.toBindingArtifacts(refresh: Boolean): Map<String, Artifact>? {
77+
val actionCoords = parameters.extractActionCoords(extractVersion = true)
78+
79+
logger.info { "➡️ Requesting ${actionCoords.prettyPrint}" }
80+
return if (refresh) {
81+
actionCoords.buildVersionArtifacts().also {
82+
bindingsCache.put(actionCoords, runCatching { it!! })
83+
}
84+
} else {
85+
bindingsCache.get(actionCoords) { runCatching { actionCoords.buildVersionArtifacts()!! } }.getOrNull()
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.typesafegithub.workflows.jitbindingserver
2+
3+
import io.ktor.server.response.respondText
4+
import io.ktor.server.routing.Routing
5+
import io.ktor.server.routing.get
6+
7+
fun Routing.internalRoutes() {
8+
get("/status") {
9+
call.respondText("OK")
10+
}
11+
}
Lines changed: 7 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,12 @@
11
package io.github.typesafegithub.workflows.jitbindingserver
22

33
import 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
154
import io.ktor.http.HttpStatusCode
165
import io.ktor.server.application.ApplicationCall
17-
import io.ktor.server.application.install
186
import io.ktor.server.engine.embeddedServer
197
import 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
258
import 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
309
import io.ktor.server.routing.routing
31-
import io.opentelemetry.instrumentation.ktor.v3_0.server.KtorServerTracing
32-
import kotlin.time.Duration.Companion.hours
3310

3411
private 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)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.github.typesafegithub.workflows.jitbindingserver
2+
3+
import io.github.typesafegithub.workflows.mavenbinding.buildPackageArtifacts
4+
import io.github.typesafegithub.workflows.shared.internal.getGithubToken
5+
import io.ktor.http.HttpStatusCode
6+
import io.ktor.server.response.respondText
7+
import io.ktor.server.routing.Route
8+
import io.ktor.server.routing.Routing
9+
import io.ktor.server.routing.get
10+
import io.ktor.server.routing.route
11+
12+
fun Routing.metadataRoutes() {
13+
route("{owner}/{name}/{file}") {
14+
metadata()
15+
}
16+
17+
route("/refresh/{owner}/{name}/{file}") {
18+
metadata(refresh = true)
19+
}
20+
}
21+
22+
private fun Route.metadata(refresh: Boolean = false) {
23+
get {
24+
if (refresh && !deliverOnRefreshRoute) return@get call.respondNotFound()
25+
26+
val file = call.parameters["file"] ?: return@get call.respondNotFound()
27+
val actionCoords = call.parameters.extractActionCoords(extractVersion = false)
28+
29+
val bindingArtifacts = actionCoords.buildPackageArtifacts(githubToken = getGithubToken())
30+
if (file in bindingArtifacts) {
31+
when (val artifact = bindingArtifacts[file]) {
32+
is String -> call.respondText(artifact)
33+
else -> call.respondText(text = "Not found", status = HttpStatusCode.NotFound)
34+
}
35+
} else {
36+
call.respondText(text = "Not found", status = HttpStatusCode.NotFound)
37+
}
38+
}
39+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.github.typesafegithub.workflows.jitbindingserver
2+
3+
import io.ktor.http.HttpHeaders
4+
import io.ktor.server.application.Application
5+
import io.ktor.server.application.install
6+
import io.ktor.server.plugins.callid.CallId
7+
import io.ktor.server.plugins.callid.callIdMdc
8+
import io.ktor.server.plugins.callid.generate
9+
import io.ktor.server.plugins.calllogging.CallLogging
10+
import io.opentelemetry.instrumentation.ktor.v3_0.server.KtorServerTracing
11+
12+
fun Application.installPlugins() {
13+
install(CallId) {
14+
generate(15, "abcdefghijklmnopqrstuvwxyz0123456789")
15+
replyToHeader(HttpHeaders.XRequestId)
16+
}
17+
18+
install(CallLogging) {
19+
callIdMdc("request-id")
20+
}
21+
22+
install(KtorServerTracing) {
23+
setOpenTelemetry(buildOpenTelemetryConfig("github-actions-bindings"))
24+
}
25+
}

0 commit comments

Comments
 (0)