io.github.sgtsilvio.oci.registry.OciRegistryHandler.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oci-registry Show documentation
Show all versions of oci-registry Show documentation
OCI registry Java library that allows serving OCI artifacts to pull operations
package io.github.sgtsilvio.oci.registry
import io.netty.handler.codec.http.HttpHeaderNames
import io.netty.handler.codec.http.HttpHeaderValues
import io.netty.handler.codec.http.HttpMethod.*
import org.json.JSONObject
import org.reactivestreams.Publisher
import reactor.core.publisher.Mono
import reactor.netty.http.server.HttpServerRequest
import reactor.netty.http.server.HttpServerResponse
import java.util.function.BiFunction
import kotlin.io.path.fileSize
import kotlin.io.path.readBytes
/*
https://github.com/opencontainers/distribution-spec/blob/main/spec.md
https://docs.docker.com/registry/spec/api/
end-1 GET /v2/ 200 ✔
end-2 GET /v2//blobs/ 200/404 ✔
end-2 HEAD /v2//blobs/ 200/404 ✔
end-10 DELETE /v2//blobs/ 405 ✔
end-3 GET /v2//manifests/ 200/404 ✔
end-3 HEAD /v2//manifests/ 200/404 ✔
end-7 PUT /v2//manifests/ 405 ✔
end-9 DELETE /v2//manifests/ 405 ✔
end-4a POST /v2//blobs/uploads/ 405 ✔
end-4b POST /v2//blobs/uploads/?digest= 405 ✔
end-11 POST /v2//blobs/uploads/?mount=&from= 405 ✔
end-13 GET /v2//blobs/uploads/ 405 ✔
end-5 PATCH /v2//blobs/uploads/ 405 ✔
DELETE /v2//blobs/uploads/ 405 ✔
end-6 PUT /v2//blobs/uploads/?digest= 405 ✔
end-8a GET /v2//tags/list 405 ✔
end-8b GET /v2//tags/list?n=&last= 405 ✔
end-12a GET /v2//referrers/
end-12b GET /v2//referrers/?artifactType=
GET /v2/_catalog 405 ✔
GET /v2/_catalog?n=&last= 405 ✔
*/
/**
* @author Silvio Giebl
*/
class OciRegistryHandler(private val storage: OciRegistryStorage) :
BiFunction> {
override fun apply(request: HttpServerRequest, response: HttpServerResponse): Publisher {
val segments = request.fullPath().substring(1).split('/')
return when {
segments[0] == "v2" -> handleV2(request, segments.drop(1), response)
else -> response.sendNotFound()
}
}
private fun handleV2(
request: HttpServerRequest,
segments: List,
response: HttpServerResponse,
): Publisher = when {
segments.isEmpty() || segments[0].isEmpty() -> when (request.method()) {
GET, HEAD -> response.header("Docker-Distribution-API-Version", "registry/2.0").send()
else -> response.status(405).send()
}
(segments.size == 1) && (segments[0] == "_catalog") -> when (request.method()) {
GET -> response.status(405).send()
else -> response.status(405).send()
}
segments.size < 3 -> response.sendNotFound()
else -> when (segments[segments.lastIndex - 1]) {
"tags" -> if (segments[segments.lastIndex] == "list") {
when (request.method()) {
GET -> response.status(405).send()
else -> response.status(405).send()
}
} else response.sendNotFound()
"manifests" -> when (request.method()) {
GET -> getOrHeadManifest(segments, true, response)
HEAD -> getOrHeadManifest(segments, false, response)
PUT, DELETE -> response.status(405).send()
else -> response.status(405).send()
}
"blobs" -> when (request.method()) {
GET -> getOrHeadBlob(segments, true, response)
HEAD -> getOrHeadBlob(segments, false, response)
DELETE -> response.status(405).send()
else -> response.status(405).send()
}
"uploads" -> when (request.method()) {
POST, GET, PATCH, PUT, DELETE -> response.status(405).send()
else -> response.status(405).send()
}
else -> response.sendNotFound()
}
}
private fun getOrHeadManifest(
segments: List,
isGET: Boolean,
response: HttpServerResponse,
): Publisher {
val name = decodeName(segments, segments.lastIndex - 1)
val reference = segments[segments.lastIndex]
val manifestFile = if (':' in reference) {
storage.getManifest(name, reference.toOciDigest())
} else {
storage.getManifest(name, reference)
} ?: return response.sendNotFound()
val manifestBytes = manifestFile.readBytes()
response.header(HttpHeaderNames.CONTENT_TYPE, JSONObject(manifestBytes.decodeToString()).getString("mediaType"))
response.header(HttpHeaderNames.CONTENT_LENGTH, manifestBytes.size.toString())
return if (isGET) response.sendByteArray(Mono.just(manifestBytes)) else response.send()
}
private fun getOrHeadBlob(segments: List, isGET: Boolean, response: HttpServerResponse): Publisher {
val name = decodeName(segments, segments.lastIndex - 1)
val digest = segments[segments.lastIndex].toOciDigest()
val blobFile = storage.getBlob(name, digest) ?: return response.sendNotFound()
response.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM)
response.header(HttpHeaderNames.CONTENT_LENGTH, blobFile.fileSize().toString())
return if (isGET) response.sendFile(blobFile) else response.send()
}
private fun decodeName(segments: List, toIndex: Int) = segments.subList(0, toIndex).joinToString("/")
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy