Compare commits

..

2 Commits

Author SHA1 Message Date
woggioni 91f9e9184c added labels to docker images
CI / build (push) Failing after 2m20s
2026-06-13 13:49:24 +08:00
woggioni 4b2f2da35f update to netty 4.2.15
CI / build (push) Successful in 3m52s
2026-06-12 09:43:39 +08:00
14 changed files with 1125 additions and 30 deletions
+6
View File
@@ -28,6 +28,7 @@ jobs:
with: with:
builder: "multiplatform-builder" builder: "multiplatform-builder"
context: "docker/build/docker" context: "docker/build/docker"
build-args: VERSION=${{ steps.retrieve-version.outputs.VERSION }},REVISION=${{ github.sha }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
pull: true pull: true
@@ -41,6 +42,7 @@ jobs:
with: with:
builder: "multiplatform-builder" builder: "multiplatform-builder"
context: "docker/build/docker" context: "docker/build/docker"
build-args: VERSION=${{ steps.retrieve-version.outputs.VERSION }},REVISION=${{ github.sha }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
pull: true pull: true
@@ -54,6 +56,7 @@ jobs:
with: with:
builder: "multiplatform-builder" builder: "multiplatform-builder"
context: "docker/build/docker" context: "docker/build/docker"
build-args: VERSION=${{ steps.retrieve-version.outputs.VERSION }},REVISION=${{ github.sha }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
pull: true pull: true
@@ -67,6 +70,7 @@ jobs:
with: with:
builder: "multiplatform-builder" builder: "multiplatform-builder"
context: "docker/build/docker" context: "docker/build/docker"
build-args: VERSION=${{ steps.retrieve-version.outputs.VERSION }},REVISION=${{ github.sha }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
pull: true pull: true
@@ -80,6 +84,7 @@ jobs:
with: with:
builder: "multiplatform-builder" builder: "multiplatform-builder"
context: "docker/build/docker" context: "docker/build/docker"
build-args: VERSION=${{ steps.retrieve-version.outputs.VERSION }},REVISION=${{ github.sha }}
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
pull: true pull: true
@@ -93,6 +98,7 @@ jobs:
with: with:
builder: "multiplatform-builder" builder: "multiplatform-builder"
context: "docker/build/docker" context: "docker/build/docker"
build-args: VERSION=${{ steps.retrieve-version.outputs.VERSION }},REVISION=${{ github.sha }}
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
pull: true pull: true
-2
View File
@@ -4,8 +4,6 @@
# Ignore Gradle build output directory # Ignore Gradle build output directory
build build
rbcs-cli/native-image/*.json
# Ignore JDTLS files # Ignore JDTLS files
.classpath .classpath
.project .project
+19
View File
@@ -1,15 +1,25 @@
ARG VERSION, REVISION
FROM eclipse-temurin:25-jre-alpine AS base-release FROM eclipse-temurin:25-jre-alpine AS base-release
LABEL org.opencontainers.image.authors="Walter Oggioni <walter.oggioni@agentmail.to>"
LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.revision="${REVISION}"
LABEL org.opencontainers.image.source=https://gitea.woggioni.net/woggioni/rbcs
RUN adduser -D rbcs RUN adduser -D rbcs
USER rbcs USER rbcs
ENV RBCS_CONFIGURATION_DIR="/etc/rbcs" ENV RBCS_CONFIGURATION_DIR="/etc/rbcs"
WORKDIR /var/lib/rbcs WORKDIR /var/lib/rbcs
FROM base-release AS release-vanilla FROM base-release AS release-vanilla
LABEL org.opencontainers.image.title=rbcs
LABEL org.opencontainers.image.description=RBCS vanilla image
ADD rbcs-cli-envelope-*.jar rbcs.jar ADD rbcs-cli-envelope-*.jar rbcs.jar
ADD logback.xml /etc/rbcs/logback.xml ADD logback.xml /etc/rbcs/logback.xml
ENTRYPOINT ["java", "-jar", "/var/lib/rbcs/rbcs.jar"] ENTRYPOINT ["java", "-jar", "/var/lib/rbcs/rbcs.jar"]
FROM base-release AS release-memcache FROM base-release AS release-memcache
LABEL org.opencontainers.image.title=rbcs-memcache
LABEL org.opencontainers.image.description=RBCS image with memcache plugin
ADD --chown=rbcs:rbcs rbcs-cli-envelope-*.jar rbcs.jar ADD --chown=rbcs:rbcs rbcs-cli-envelope-*.jar rbcs.jar
RUN mkdir plugins RUN mkdir plugins
WORKDIR /var/lib/rbcs/plugins WORKDIR /var/lib/rbcs/plugins
@@ -19,6 +29,8 @@ ADD logback.xml /etc/rbcs/logback.xml
ENTRYPOINT ["java", "-jar", "/var/lib/rbcs/rbcs.jar"] ENTRYPOINT ["java", "-jar", "/var/lib/rbcs/rbcs.jar"]
FROM base-release AS release-redis FROM base-release AS release-redis
LABEL org.opencontainers.image.title=rbcs-redis
LABEL org.opencontainers.image.description=RBCS image with redis plugin
ADD --chown=rbcs:rbcs rbcs-cli-envelope-*.jar rbcs.jar ADD --chown=rbcs:rbcs rbcs-cli-envelope-*.jar rbcs.jar
RUN mkdir plugins RUN mkdir plugins
WORKDIR /var/lib/rbcs/plugins WORKDIR /var/lib/rbcs/plugins
@@ -28,6 +40,8 @@ ADD logback.xml /etc/rbcs/logback.xml
ENTRYPOINT ["java", "-jar", "/var/lib/rbcs/rbcs.jar"] ENTRYPOINT ["java", "-jar", "/var/lib/rbcs/rbcs.jar"]
FROM base-release AS release-full FROM base-release AS release-full
LABEL org.opencontainers.image.title=rbcs-full
LABEL org.opencontainers.image.description=RBCS image with all plugins
ADD --chown=rbcs:rbcs rbcs-cli-envelope-*.jar rbcs.jar ADD --chown=rbcs:rbcs rbcs-cli-envelope-*.jar rbcs.jar
RUN mkdir plugins RUN mkdir plugins
WORKDIR /var/lib/rbcs/plugins WORKDIR /var/lib/rbcs/plugins
@@ -45,17 +59,22 @@ RUN adduser -D -u 1000 rbcs -h /var/lib/rbcs
RUN chown rbcs:rbcs /var/tmp/rbcs RUN chown rbcs:rbcs /var/tmp/rbcs
FROM scratch AS release-native FROM scratch AS release-native
LABEL org.opencontainers.image.title=rbcs-native
LABEL org.opencontainers.image.description=RBCS image with a native executable with GraalVM
COPY --from=base-native /etc/passwd /etc/passwd COPY --from=base-native /etc/passwd /etc/passwd
COPY --from=base-native /etc/rbcs /etc/rbcs COPY --from=base-native /etc/rbcs /etc/rbcs
COPY --from=base-native /var/lib/rbcs /var/lib/rbcs COPY --from=base-native /var/lib/rbcs /var/lib/rbcs
COPY --from=base-native /var/tmp/rbcs /var/tmp/rbcs COPY --from=base-native /var/tmp/rbcs /var/tmp/rbcs
ADD rbcs-cli.upx /usr/bin/rbcs-cli ADD rbcs-cli.upx /usr/bin/rbcs-cli
ADD logback.xml /etc/rbcs/logback.xml
USER rbcs USER rbcs
WORKDIR /var/lib/rbcs WORKDIR /var/lib/rbcs
ENV RBCS_CONFIGURATION_DIR="/etc/rbcs" ENV RBCS_CONFIGURATION_DIR="/etc/rbcs"
ENTRYPOINT ["/usr/bin/rbcs-cli", "-XX:MaximumHeapSizePercent=70", "-Dio.netty.tmpdir=/var/tmp/rbcs", "-Dlogback.configurationFile=/etc/rbcs/logback.xml"] ENTRYPOINT ["/usr/bin/rbcs-cli", "-XX:MaximumHeapSizePercent=70", "-Dio.netty.tmpdir=/var/tmp/rbcs", "-Dlogback.configurationFile=/etc/rbcs/logback.xml"]
FROM debian:12-slim AS release-jlink FROM debian:12-slim AS release-jlink
LABEL org.opencontainers.image.title=rbcs-jlink
LABEL org.opencontainers.image.description=RBCS image with a jlink distribution
RUN mkdir -p /usr/share/java/rbcs RUN mkdir -p /usr/share/java/rbcs
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-cli*.tar -C /usr/share/java/rbcs RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-cli*.tar -C /usr/share/java/rbcs
RUN chmod 755 /usr/share/java/rbcs/bin/* RUN chmod 755 /usr/share/java/rbcs/bin/*
+2 -2
View File
@@ -2,9 +2,9 @@ org.gradle.configuration-cache=false
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
rbcs.version = 0.5.0 rbcs.version = 0.5.1
lys.version = 2026.05.27 lys.version = 2026.06.08
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
docker.registry.url=gitea.woggioni.net docker.registry.url=gitea.woggioni.net
+5 -5
View File
@@ -90,7 +90,7 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named(EnvelopePlugin.E
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) { tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(25) languageVersion = JavaLanguageVersion.of(25)
vendor = JvmVendorSpec.GRAAL_VM vendor = JvmVendorSpec.ORACLE
} }
mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration" mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration"
classpath = project.files( classpath = project.files(
@@ -108,10 +108,10 @@ tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfi
nativeImage { nativeImage {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(25) languageVersion = JavaLanguageVersion.of(25)
vendor = JvmVendorSpec.GRAAL_VM vendor = JvmVendorSpec.ORACLE
} }
mainClass = mainClassName mainClass = mainClassName
// mainModule = mainModuleName //mainModule = mainModuleName
useMusl = true useMusl = true
buildStaticImage = true buildStaticImage = true
linkAtBuildTime = false linkAtBuildTime = false
@@ -119,6 +119,7 @@ nativeImage {
compressExecutable = true compressExecutable = true
compressionLevel = 6 compressionLevel = 6
useLZMA = false useLZMA = false
//verbose = true
} }
Provider<UpxTask> upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME, UpxTask) { Provider<UpxTask> upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME, UpxTask) {
@@ -127,7 +128,7 @@ Provider<UpxTask> upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME,
Provider<JlinkTask> jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) { Provider<JlinkTask> jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(25) languageVersion = JavaLanguageVersion.of(25)
vendor = JvmVendorSpec.GRAAL_VM vendor = JvmVendorSpec.ORACLE
} }
mainClass = mainClassName mainClass = mainClassName
@@ -152,7 +153,6 @@ Provider<JlinkTask> jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME,
} }
Provider<Tar> jlinkDistTarTaskProvider = tasks.named(JlinkPlugin.JLINK_DIST_TAR_TASK_NAME, Tar) { Provider<Tar> jlinkDistTarTaskProvider = tasks.named(JlinkPlugin.JLINK_DIST_TAR_TASK_NAME, Tar) {
exclude 'lib/libjvmcicompiler.so'
} }
tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) { tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) {
File diff suppressed because one or more lines are too long
+10 -1
View File
@@ -1,2 +1,11 @@
Args=-O3 -march=x86-64-v2 --gc=serial --initialize-at-run-time=io.netty --enable-url-protocols=jpms -H:+UnlockExperimentalVMOptions -H:+SharedArenaSupport --initialize-at-build-time=net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory,net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory$JpmsHandler Args=-O3 \
-march=x86-64-v3 \
--gc=serial \
--enable-url-protocols=jpms \
--pgo=conf/default.iprof \
--initialize-at-run-time=io.netty \
--initialize-at-build-time=net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory,net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory$JpmsHandler \
--trace-object-instantiation=ch.qos.logback.classic.Logger \
-H:+UnlockExperimentalVMOptions \
-H:+SharedArenaSupport
#-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils #-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.net.ssl.TrustManagerFactory import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedTrustManager
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
import kotlin.random.Random import kotlin.random.Random
import io.netty.util.concurrent.Future as NettyFuture import io.netty.util.concurrent.Future as NettyFuture
@@ -74,13 +75,25 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
) )
profile.tlsTruststore?.let { trustStore -> profile.tlsTruststore?.let { trustStore ->
if (!trustStore.verifyServerCertificate) { if (!trustStore.verifyServerCertificate) {
trustManager(object : X509TrustManager { trustManager(object : X509ExtendedTrustManager() {
override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?) { override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?) {
} }
override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?, socket: java.net.Socket) {
}
override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?, engine: javax.net.ssl.SSLEngine) {
}
override fun checkServerTrusted(certChain: Array<out X509Certificate>, p1: String?) { override fun checkServerTrusted(certChain: Array<out X509Certificate>, p1: String?) {
} }
override fun checkServerTrusted(certChain: Array<out X509Certificate>, p1: String?, socket: java.net.Socket) {
}
override fun checkServerTrusted(certChain: Array<out X509Certificate>, p1: String?, engine: javax.net.ssl.SSLEngine) {
}
override fun getAcceptedIssuers() = null override fun getAcceptedIssuers() = null
}) })
} else { } else {
@@ -19,6 +19,7 @@ import java.security.cert.X509Certificate
import java.util.EnumSet import java.util.EnumSet
import java.util.ServiceLoader import java.util.ServiceLoader
import javax.net.ssl.TrustManagerFactory import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedTrustManager
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
import net.woggioni.jwo.JWO import net.woggioni.jwo.JWO
import net.woggioni.jwo.Tuple2 import net.woggioni.jwo.Tuple2
@@ -124,7 +125,7 @@ object RBCS {
return keystore return keystore
} }
fun getTrustManager(trustStore: KeyStore?, certificateRevocationEnabled: Boolean): X509TrustManager { fun getTrustManager(trustStore: KeyStore?, certificateRevocationEnabled: Boolean): X509ExtendedTrustManager {
return if (trustStore != null) { return if (trustStore != null) {
val certificateFactory = CertificateFactory.getInstance("X.509") val certificateFactory = CertificateFactory.getInstance("X.509")
val validator = CertPathValidator.getInstance("PKIX").apply { val validator = CertPathValidator.getInstance("PKIX").apply {
@@ -136,7 +137,7 @@ object RBCS {
val params = PKIXParameters(trustStore).apply { val params = PKIXParameters(trustStore).apply {
isRevocationEnabled = certificateRevocationEnabled isRevocationEnabled = certificateRevocationEnabled
} }
object : X509TrustManager { object : X509ExtendedTrustManager() {
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) { override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList()) val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
try { try {
@@ -146,10 +147,26 @@ object RBCS {
} }
} }
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, socket: java.net.Socket) {
checkClientTrusted(chain, authType)
}
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, engine: javax.net.ssl.SSLEngine) {
checkClientTrusted(chain, authType)
}
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) { override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
throw NotImplementedError() throw NotImplementedError()
} }
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, socket: java.net.Socket) {
checkServerTrusted(chain, authType)
}
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, engine: javax.net.ssl.SSLEngine) {
checkServerTrusted(chain, authType)
}
private val acceptedIssuers = trustStore.aliases().asSequence() private val acceptedIssuers = trustStore.aliases().asSequence()
.filter(trustStore::isCertificateEntry) .filter(trustStore::isCertificateEntry)
.map(trustStore::getCertificate) .map(trustStore::getCertificate)
@@ -161,8 +178,8 @@ object RBCS {
} }
} else { } else {
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager } trustManagerFactory.trustManagers.asSequence().filter { it is X509ExtendedTrustManager }
.single() as X509TrustManager .single() as X509ExtendedTrustManager
} }
} }
@@ -188,7 +188,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
?: return anonymousUserGroups?.let { AuthenticationResult(null, it) } ?: return anonymousUserGroups?.let { AuthenticationResult(null, it) }
val ldapName = try { val ldapName = try {
LdapName(subjectDn) LdapName(subjectDn)
} catch (e: Exception) { } catch (_: Exception) {
log.debug(ctx) { log.debug(ctx) {
"Invalid subject DN in header $headerName: $subjectDn" "Invalid subject DN in header $headerName: $subjectDn"
} }
@@ -354,7 +354,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
}?.let { }?.let {
pattern.matcher(it.value.toString()) pattern.matcher(it.value.toString())
}?.takeIf(Matcher::matches)?.group(1) }?.takeIf(Matcher::matches)?.group(1)
cfg.users[userName] ?: throw java.lang.RuntimeException("Failed to extract user") cfg.users[userName] ?: throw RuntimeException("Failed to extract user")
} }
} }
@@ -368,7 +368,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
}?.let { }?.let {
pattern.matcher(it.value.toString()) pattern.matcher(it.value.toString())
}?.takeIf(Matcher::matches)?.group(1) }?.takeIf(Matcher::matches)?.group(1)
cfg.groups[groupName] ?: throw java.lang.RuntimeException("Failed to extract group") cfg.groups[groupName] ?: throw RuntimeException("Failed to extract group")
} }
} }
@@ -344,14 +344,14 @@ object Parser {
roles = parseRoles(child) roles = parseRoles(child)
} }
"group-quota" -> { "group-quota" -> {
userQuota = parseQuota(child)
}
"user-quota" -> {
groupQuota = parseQuota(child) groupQuota = parseQuota(child)
} }
"user-quota" -> {
userQuota = parseQuota(child)
} }
} }
groupName to Group(groupName, roles, userQuota, groupQuota) }
groupName to Group(groupName, roles, groupQuota, userQuota)
}.toMap() }.toMap()
val users = knownUsersMap.map { (name, user) -> val users = knownUsersMap.map { (name, user) ->
name to User(name, user.password, userGroups[name]?.mapNotNull { groups[it] }?.toSet() ?: emptySet(), user.quota) name to User(name, user.password, userGroups[name]?.mapNotNull { groups[it] }?.toSet() ?: emptySet(), user.quota)
@@ -23,24 +23,22 @@ class ProxyProtocolHandler(private val trustedProxyIPs : List<Cidr>) : SimpleCha
) { ) {
val sourceAddress = ctx.channel().remoteAddress() val sourceAddress = ctx.channel().remoteAddress()
if (sourceAddress is InetSocketAddress && if (sourceAddress is InetSocketAddress &&
trustedProxyIPs.isEmpty() || (trustedProxyIPs.isEmpty() ||
trustedProxyIPs.any { it.contains((sourceAddress as InetSocketAddress).address) }.also { trustedProxyIPs.any { it.contains(sourceAddress.address) }.also {
if(!it && log.isTraceEnabled) { if(!it) {
log.trace { log.trace {
"Received a proxied connection request from $sourceAddress which is not a trusted proxy address, " + "Received a proxied connection request from $sourceAddress which is not a trusted proxy address, " +
"the proxy server address will be used instead" "the proxy server address will be used instead"
} }
} }
}) { })) {
val proxiedClientAddress = InetSocketAddress( val proxiedClientAddress = InetSocketAddress(
InetAddress.ofLiteral(msg.sourceAddress()), InetAddress.ofLiteral(msg.sourceAddress()),
msg.sourcePort() msg.sourcePort()
) )
if(log.isTraceEnabled) {
log.trace { log.trace {
"Received proxied connection request from $sourceAddress forwarded for $proxiedClientAddress" "Received proxied connection request from $sourceAddress forwarded for $proxiedClientAddress"
} }
}
ctx.channel().attr(RemoteBuildCacheServer.clientIp).set(proxiedClientAddress) ctx.channel().attr(RemoteBuildCacheServer.clientIp).set(proxiedClientAddress)
} }
} }
@@ -171,7 +171,6 @@ class ServerHandler(private val serverPrefix: Path, private val cacheHandlerSupp
ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler) ctx.pipeline().addBefore(ExceptionHandler.NAME, null, cacheHandler)
key.let(::CacheGetRequest) key.let(::CacheGetRequest)
.let(ctx::fireChannelRead) .let(ctx::fireChannelRead)
?: ctx.channel().write(CacheValueNotFoundResponse(key))
} else { } else {
cacheRequestInProgress = false cacheRequestInProgress = false
log.warn(ctx) { log.warn(ctx) {