working commit
This commit is contained in:
+25
-61
@@ -6,25 +6,6 @@ import (
|
||||
"mstore/app/router"
|
||||
)
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
|
||||
//
|
||||
// Open Container Initiative Distribution Specification
|
||||
//
|
||||
// Existing Manifests
|
||||
//
|
||||
// The image manifest can be checked for existence with the following url:
|
||||
//
|
||||
// HEAD /v2/<name>/manifests/<reference>
|
||||
//
|
||||
// The name and reference parameter identify the image and are required. The reference may include a tag or digest.
|
||||
//
|
||||
// A 404 Not Found response will be returned if the image is unknown to the registry.
|
||||
// If the image exists and the response is successful the response will be as follows:
|
||||
//
|
||||
// 200 OK
|
||||
// Content-Length: <length of manifest>
|
||||
// Docker-Content-Digest: <digest>
|
||||
|
||||
func (hand *Handler) ManifestExists(rctx *router.Context) {
|
||||
name, _ := rctx.GetSubpath("name")
|
||||
reference, _ := rctx.GetSubpath("reference")
|
||||
@@ -44,49 +25,7 @@ func (hand *Handler) ManifestExists(rctx *router.Context) {
|
||||
rctx.SetStatus(code)
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
|
||||
//
|
||||
// Pushing Manifests
|
||||
//
|
||||
// To push a manifest, perform a PUT request to a path in the following format,
|
||||
// and with the following headers and body: /v2/<name>/manifests/<reference>
|
||||
//
|
||||
// Clients SHOULD set the Content-Type header to the type of the manifest being pushed.
|
||||
// The client SHOULD NOT include parameters on the Content-Type header (see RFC7231).
|
||||
// The registry SHOULD ignore parameters on the Content-Type header.
|
||||
//
|
||||
// All manifests SHOULD include a mediaType field declaring the type of the manifest being pushed.
|
||||
// If a manifest includes a mediaType field, clients MUST set the Content-Type header to
|
||||
// the value specified by the mediaType field.
|
||||
//
|
||||
// Content-Type: application/vnd.oci.image.manifest.v1+json
|
||||
//
|
||||
// <name> is the namespace of the repository, and the <reference> MUST be either a) a digest or b) a tag.
|
||||
//
|
||||
// The uploaded manifest MUST reference any blobs that make up the object.
|
||||
// However, the list of blobs MAY be empty.
|
||||
//
|
||||
// The registry MUST store the manifest in the exact byte representation provided by the client.
|
||||
// Upon a successful upload, the registry MUST return response code 201 Created,
|
||||
// and MUST have the following header:
|
||||
//
|
||||
// Location: <location>
|
||||
//
|
||||
// The <location> is a pullable manifest URL. The Docker-Content-Digest header returns
|
||||
// the canonical digest of the uploaded blob, and MUST be equal to the client provided digest.
|
||||
// Clients MAY ignore the value but if it is used, the client SHOULD verify
|
||||
// the value against the uploaded blob data.
|
||||
//
|
||||
// An attempt to pull a nonexistent repository MUST return response code 404 Not Found.
|
||||
//
|
||||
// A registry SHOULD enforce some limit on the maximum manifest size that it can accept.
|
||||
// A registry that enforces this limit SHOULD respond to a request to push a manifest over this
|
||||
// limit with a response code 413 Payload Too Large.
|
||||
// Client and registry implementations SHOULD expect to be able to support manifest
|
||||
// pushes of at least 4 megabytes.
|
||||
|
||||
// PUT /v2/<name>/manifests/<reference> 201 404
|
||||
|
||||
func (hand *Handler) PutManifest(rctx *router.Context) {
|
||||
|
||||
//hand.DumpHeaders("PutManifest headers", rctx)
|
||||
@@ -113,3 +52,28 @@ func (hand *Handler) PutManifest(rctx *router.Context) {
|
||||
}
|
||||
rctx.SetStatus(code)
|
||||
}
|
||||
|
||||
// GET /v2/<name>/manifests/<reference> 200 404
|
||||
func (hand *Handler) GetManifest(rctx *router.Context) {
|
||||
name, _ := rctx.GetSubpath("name")
|
||||
reference, _ := rctx.GetSubpath("reference")
|
||||
|
||||
params := &operator.GetManifestParams{
|
||||
Name: name,
|
||||
Reference: reference,
|
||||
}
|
||||
ctx := rctx.GetContext()
|
||||
res, code, err := hand.oper.GetManifest(ctx, params)
|
||||
if err != nil {
|
||||
hand.logg.Errorf("GetManifest error: %v", err)
|
||||
rctx.SetStatus(code)
|
||||
return
|
||||
}
|
||||
|
||||
rctx.SetHeader("Content-Length", res.ContentLength)
|
||||
rctx.SetHeader("Content-Type", res.ContentType)
|
||||
rctx.SetHeader("Docker-Content-Digest", res.DockerContentDigest)
|
||||
rctx.SetStatus(code)
|
||||
|
||||
rctx.SendBytes([]byte(res.Payload))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,20 @@ import (
|
||||
)
|
||||
|
||||
const sha256prefix = "sha256:"
|
||||
const sha512prefix = "sha512:"
|
||||
|
||||
func normalizeSHADigest(digest string) string {
|
||||
if stringLikeSHA256Digest(digest) && !strings.HasPrefix(digest, sha256prefix) {
|
||||
digest = sha256prefix + digest
|
||||
} else if stringLikeSHA512Digest(digest) && !strings.HasPrefix(digest, sha512prefix) {
|
||||
digest = sha512prefix + digest
|
||||
}
|
||||
return digest
|
||||
}
|
||||
|
||||
func stringLikeSHADigest(some string) bool {
|
||||
return stringLikeSHA256Digest(some) || stringLikeSHA512Digest(some)
|
||||
}
|
||||
|
||||
func stringLikeSHA256Digest(some string) bool {
|
||||
if strings.HasPrefix(some, sha256prefix) {
|
||||
@@ -20,3 +34,17 @@ func stringLikeSHA256Digest(some string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringLikeSHA512Digest(some string) bool {
|
||||
if strings.HasPrefix(some, sha512prefix) {
|
||||
some = strings.TrimPrefix(some, sha512prefix)
|
||||
}
|
||||
_, err := hex.DecodeString(some)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if len(some) == 128 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
|
||||
"mstore/app/descr"
|
||||
"mstore/pkg/auxoci"
|
||||
|
||||
ocidigest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -69,7 +67,7 @@ func (oper *Operator) ManifestExists(ctx context.Context, params *ManifestExists
|
||||
}
|
||||
}
|
||||
|
||||
digest := ocidigest.SHA256.FromString(manifest.Payload)
|
||||
digest := auxoci.SHA256DigestFromString(manifest.Payload)
|
||||
payloadSize := len(manifest.Payload)
|
||||
res.ContentLength = strconv.FormatInt(int64(payloadSize), 10)
|
||||
res.ContentType = manifest.ContentType
|
||||
@@ -227,3 +225,52 @@ func (oper *Operator) PutManifest(ctx context.Context, params *PutManifestParams
|
||||
return res, http.StatusCreated, err
|
||||
|
||||
}
|
||||
|
||||
type GetManifestParams struct {
|
||||
Name string
|
||||
Reference string
|
||||
}
|
||||
type GetManifestResult struct {
|
||||
ContentLength string
|
||||
ContentType string
|
||||
DockerContentDigest string
|
||||
Payload string
|
||||
}
|
||||
|
||||
func (oper *Operator) GetManifest(ctx context.Context, params *GetManifestParams) (*GetManifestResult, int, error) {
|
||||
var err error
|
||||
res := &GetManifestResult{}
|
||||
oper.logg.Debugf("Get manifest %s:%s", params.Name, params.Reference)
|
||||
|
||||
manifestDescr := descr.Manifest{}
|
||||
var exists bool
|
||||
if stringLikeSHADigest(params.Reference) {
|
||||
digest := normalizeSHADigest(params.Reference)
|
||||
oper.logg.Debugf("Get manifest %s with digest %s", params.Name, params.Reference)
|
||||
exists, manifestDescr, err = oper.mdb.GetManifestByDigest(ctx, params.Name, digest)
|
||||
if err != nil {
|
||||
return res, http.StatusInternalServerError, err
|
||||
}
|
||||
if !exists {
|
||||
return res, http.StatusNotFound, err
|
||||
}
|
||||
} else {
|
||||
oper.logg.Debugf("Get manifest %s with tag %s", params.Name, params.Reference)
|
||||
exists, manifestDescr, err = oper.mdb.GetManifestByReference(ctx, params.Name, params.Reference)
|
||||
if err != nil {
|
||||
return res, http.StatusInternalServerError, err
|
||||
}
|
||||
if !exists {
|
||||
return res, http.StatusNotFound, err
|
||||
}
|
||||
}
|
||||
|
||||
manifestDigest := auxoci.SHA256DigestFromString(manifestDescr.Payload)
|
||||
res.DockerContentDigest = manifestDigest.String()
|
||||
|
||||
res.ContentLength = strconv.FormatInt(int64(len(manifestDescr.Payload)), 10)
|
||||
res.ContentType = manifestDescr.ContentType
|
||||
res.Payload = manifestDescr.Payload
|
||||
|
||||
return res, http.StatusOK, err
|
||||
}
|
||||
|
||||
@@ -66,3 +66,7 @@ func (rctx *Context) SendText(payload string) {
|
||||
rctx.Writer.Header().Set("Content-Type", "text/plain")
|
||||
rctx.Writer.Write([]byte(payload))
|
||||
}
|
||||
|
||||
func (rctx *Context) SendBytes(payload []byte) {
|
||||
rctx.Writer.Write(payload)
|
||||
}
|
||||
|
||||
@@ -95,10 +95,68 @@ func (svc *Service) Build() error {
|
||||
// and with the following headers and body: /v2/<name>/manifests/<reference>
|
||||
|
||||
const reference = `{reference:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}}`
|
||||
|
||||
svc.rout.Head(`/v2/{name}/manifests/{reference}`, svc.hand.ManifestExists)
|
||||
|
||||
svc.rout.Put(`/v2/{name}/manifests/{reference}`, svc.hand.PutManifest)
|
||||
|
||||
// Pulling manifests
|
||||
//
|
||||
// To pull a manifest, perform a GET request to a URL in the following form:
|
||||
// /v2/<name>/manifests/<reference> end-3
|
||||
//
|
||||
// <name> refers to the namespace of the repository. <reference> MUST be either
|
||||
// (a) the digest of the manifest or (b) a tag. The <reference> MUST NOT be in
|
||||
// any other format. Throughout this document, <name> MUST match the following
|
||||
// regular expression:
|
||||
//
|
||||
// [a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*
|
||||
//
|
||||
// Implementers note: Many clients impose a limit of 255 characters on the length of
|
||||
// the concatenation of the registry hostname (and optional port), /, and <name> value.
|
||||
// If the registry name is registry.example.org:5000, those clients would be limited
|
||||
// to a <name> of 229 characters (255 minus 25 for the registry hostname and port
|
||||
// and minus 1 for a / separator). For compatibility with those clients, registries should
|
||||
// avoid values of <name> that would cause this limit to be exceeded.
|
||||
//
|
||||
// Throughout this document, <reference> as a tag MUST be at most 128 characters
|
||||
// in length and MUST match the following regular expression:
|
||||
//
|
||||
// [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}
|
||||
//
|
||||
// The client SHOULD include an Accept header indicating which manifest content types
|
||||
// it supports. In a successful response, the Content-Type header will indicate
|
||||
// the type of the returned manifest. The registry SHOULD NOT include parameters
|
||||
// on the Content-Type header. The client SHOULD ignore parameters on the Content-Type
|
||||
// header.
|
||||
// The Content-Type header SHOULD match what the client pushed as the manifest's
|
||||
// Content-Type. If the manifest has a mediaType field, clients SHOULD reject
|
||||
// unless the mediaType field's value matches the type specified by the Content-Type header.
|
||||
//
|
||||
// For more information on the use of Accept headers and content negotiation,
|
||||
// please see Content Negotiation and RFC7231.
|
||||
//
|
||||
// A GET request to an existing manifest URL MUST provide the expected manifest,
|
||||
// with a response code that MUST be 200 OK. A successful response SHOULD contain
|
||||
// the digest of the uploaded blob in the header Docker-Content-Digest.
|
||||
//
|
||||
// The Docker-Content-Digest header, if present on the response,
|
||||
// returns the canonical digest of the uploaded blob which MAY differ
|
||||
// from the provided digest. If the digest does differ,
|
||||
// it MAY be the case that the hashing algorithms used do not match.
|
||||
//
|
||||
// See Content Digests apdx-3 for information on how to detect the hashing
|
||||
// algorithm in use. Most clients MAY ignore the value, but if it is used,
|
||||
// the client MUST verify the value matches the returned manifest.
|
||||
//
|
||||
// If the <reference> part of a manifest request is a digest, clients SHOULD verify
|
||||
// the returned manifest matches this digest.
|
||||
//
|
||||
// If the manifest is not found in the repository, the response code
|
||||
// MUST be 404 Not Found.
|
||||
|
||||
svc.rout.Get(`/v2/{name}/manifests/{reference}`, svc.hand.GetManifest)
|
||||
|
||||
// Pulling blobs
|
||||
//
|
||||
// To pull a blob, perform a GET request to a URL in the following form:
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
ocidigest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
@@ -16,3 +17,7 @@ func ParseOCIManifest(rawManifest []byte) (*ocispec.Manifest, error) {
|
||||
}
|
||||
return manifest, err
|
||||
}
|
||||
|
||||
func SHA256DigestFromString(payload string) ocidigest.Digest {
|
||||
return ocidigest.SHA256.FromString(payload)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user