1
1
package io.github.typesafegithub.workflows.jitbindingserver
2
2
3
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.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
15
4
import io.ktor.http.HttpStatusCode
16
5
import io.ktor.server.application.ApplicationCall
17
- import io.ktor.server.application.install
18
6
import io.ktor.server.engine.embeddedServer
19
7
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
25
8
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
30
9
import io.ktor.server.routing.routing
31
- import io.opentelemetry.instrumentation.ktor.v3_0.server.KtorServerTracing
32
- import kotlin.time.Duration.Companion.hours
33
10
34
11
private val logger =
35
12
System
@@ -47,161 +24,18 @@ fun main() {
47
24
Thread .setDefaultUncaughtExceptionHandler { thread, throwable ->
48
25
logger.error(throwable) { " Uncaught exception in thread $thread " }
49
26
}
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
-
58
27
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()
85
29
86
- route(" {owner}/{name}/{file}" ) {
87
- metadata(refresh = true )
88
- }
89
- }
30
+ routing {
31
+ internalRoutes()
90
32
91
- get(" /status" ) {
92
- call.respondText(" OK" )
93
- }
33
+ artifactRoutes()
34
+ metadataRoutes()
94
35
}
95
36
}.start(wait = true )
96
37
}
97
38
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()
206
40
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