All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.sgtsilvio.oci.registry.OciRegistryHandler.kt Maven / Gradle / Ivy

Go to download

OCI registry Java library that allows serving OCI artifacts to pull operations

There is a newer version: 0.4.1
Show newest version
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