Fix production health build identification
CI / verify (push) Has been cancelled
CI / publish (push) Has been cancelled

Mount the versioned health route instead of the legacy shadowing handler so deployments can verify the exact running image.
This commit is contained in:
Rocky
2026-08-21 23:15:59 +08:00
parent 0d236f57fb
commit f8fa93dc48
2 changed files with 22 additions and 23 deletions
@@ -108,6 +108,7 @@ import com.osglab.account.features.gateway.services.GatewayBearerIdentity
import com.osglab.account.features.gateway.services.GatewayGrantService import com.osglab.account.features.gateway.services.GatewayGrantService
import com.osglab.account.features.gateway.services.GatewayReconciliationService import com.osglab.account.features.gateway.services.GatewayReconciliationService
import com.osglab.account.features.gateway.services.GatewayService import com.osglab.account.features.gateway.services.GatewayService
import com.osglab.account.features.health.healthRoutes
import com.osglab.account.features.integrity.AppAttestCrypto import com.osglab.account.features.integrity.AppAttestCrypto
import com.osglab.account.features.integrity.AppAttestRepository import com.osglab.account.features.integrity.AppAttestRepository
import com.osglab.account.features.integrity.AppAttestService import com.osglab.account.features.integrity.AppAttestService
@@ -152,8 +153,6 @@ import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation
import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.websocket.WebSockets as ClientWebSockets import io.ktor.client.plugins.websocket.WebSockets as ClientWebSockets
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application import io.ktor.server.application.Application
import io.ktor.server.application.ApplicationStopped import io.ktor.server.application.ApplicationStopped
@@ -169,9 +168,6 @@ import io.ktor.server.plugins.ratelimit.RateLimitName
import io.ktor.server.plugins.ratelimit.rateLimit import io.ktor.server.plugins.ratelimit.rateLimit
import io.ktor.server.request.httpMethod import io.ktor.server.request.httpMethod
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.Route
import io.ktor.server.routing.routing import io.ktor.server.routing.routing
import io.ktor.server.websocket.WebSockets import io.ktor.server.websocket.WebSockets
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -379,22 +375,6 @@ fun Application.module() {
} }
} }
fun Route.healthRoutes(databaseFactory: DatabaseFactory? = null) {
get("/health") { call.respondText("""{"status":"UP"}""", ContentType.Application.Json) }
get("/health/live") { call.respondText("""{"status":"UP"}""", ContentType.Application.Json) }
get("/health/ready") {
if (databaseFactory?.isReady() == true) {
call.respondText("""{"status":"UP"}""", ContentType.Application.Json)
} else {
call.respondText(
"""{"status":"DOWN"}""",
ContentType.Application.Json,
HttpStatusCode.ServiceUnavailable,
)
}
}
}
fun accountServerModule(config: AppConfig): Module = module { fun accountServerModule(config: AppConfig): Module = module {
single { config } single { config }
single { DatabaseFactory(config.database) } single { DatabaseFactory(config.database) }
@@ -1,16 +1,23 @@
package com.osglab.account package com.osglab.account
import com.osglab.account.config.AppConfig import com.osglab.account.config.AppConfig
import com.osglab.account.config.DatabaseFactory
import com.osglab.account.features.credits.repositories.BillingTransactionRunner import com.osglab.account.features.credits.repositories.BillingTransactionRunner
import com.osglab.account.features.credits.repositories.BillingUnitOfWork import com.osglab.account.features.credits.repositories.BillingUnitOfWork
import com.osglab.account.features.credits.services.CreditOperations import com.osglab.account.features.credits.services.CreditOperations
import com.osglab.account.features.credits.services.CreditService import com.osglab.account.features.credits.services.CreditService
import com.osglab.account.features.health.healthRoutes
import com.osglab.account.features.referrals.services.ReferralOperations import com.osglab.account.features.referrals.services.ReferralOperations
import com.osglab.account.features.referrals.services.ReferralService import com.osglab.account.features.referrals.services.ReferralService
import io.mockk.mockk
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.install
import io.ktor.server.config.MapApplicationConfig import io.ktor.server.config.MapApplicationConfig
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.routing.routing import io.ktor.server.routing.routing
import io.ktor.server.testing.testApplication import io.ktor.server.testing.testApplication
import org.koin.dsl.koinApplication import org.koin.dsl.koinApplication
@@ -21,11 +28,23 @@ import kotlin.test.Test
class ApplicationTest { class ApplicationTest {
@Test @Test
fun `liveness endpoint remains independent of external services`() = testApplication { fun `liveness endpoint remains independent of external services`() = testApplication {
val buildSha = "a".repeat(40)
application { application {
routing { healthRoutes() } install(ContentNegotiation) {
json()
}
routing {
healthRoutes(
databaseFactory = mockk<DatabaseFactory>(relaxed = true),
buildSha = buildSha,
)
}
} }
client.get("/health/live").status shouldBe HttpStatusCode.OK client.get("/health/live").apply {
status shouldBe HttpStatusCode.OK
bodyAsText() shouldBe """{"status":"UP","buildSha":"$buildSha"}"""
}
} }
@Test @Test