550 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			550 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
package handlers
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"mime"
 | 
						|
	"net/http"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/distribution/distribution/v3"
 | 
						|
	dcontext "github.com/distribution/distribution/v3/context"
 | 
						|
	"github.com/distribution/distribution/v3/manifest/manifestlist"
 | 
						|
	"github.com/distribution/distribution/v3/manifest/ocischema"
 | 
						|
	"github.com/distribution/distribution/v3/manifest/schema1"
 | 
						|
	"github.com/distribution/distribution/v3/manifest/schema2"
 | 
						|
	"github.com/distribution/distribution/v3/reference"
 | 
						|
	"github.com/distribution/distribution/v3/registry/api/errcode"
 | 
						|
	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 | 
						|
	"github.com/distribution/distribution/v3/registry/auth"
 | 
						|
	"github.com/distribution/distribution/v3/registry/storage/driver"
 | 
						|
	"github.com/gorilla/handlers"
 | 
						|
	"github.com/opencontainers/go-digest"
 | 
						|
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 | 
						|
)
 | 
						|
 | 
						|
// These constants determine which architecture and OS to choose from a
 | 
						|
// manifest list when downconverting it to a schema1 manifest.
 | 
						|
const (
 | 
						|
	defaultArch         = "amd64"
 | 
						|
	defaultOS           = "linux"
 | 
						|
	maxManifestBodySize = 4 << 20
 | 
						|
	imageClass          = "image"
 | 
						|
)
 | 
						|
 | 
						|
type storageType int
 | 
						|
 | 
						|
const (
 | 
						|
	manifestSchema1     storageType = iota // 0
 | 
						|
	manifestSchema2                        // 1
 | 
						|
	manifestlistSchema                     // 2
 | 
						|
	ociSchema                              // 3
 | 
						|
	ociImageIndexSchema                    // 4
 | 
						|
	numStorageTypes                        // 5
 | 
						|
)
 | 
						|
 | 
						|
// manifestDispatcher takes the request context and builds the
 | 
						|
// appropriate handler for handling manifest requests.
 | 
						|
func manifestDispatcher(ctx *Context, r *http.Request) http.Handler {
 | 
						|
	manifestHandler := &manifestHandler{
 | 
						|
		Context: ctx,
 | 
						|
	}
 | 
						|
	ref := getReference(ctx)
 | 
						|
	dgst, err := digest.Parse(ref)
 | 
						|
	if err != nil {
 | 
						|
		// We just have a tag
 | 
						|
		manifestHandler.Tag = ref
 | 
						|
	} else {
 | 
						|
		manifestHandler.Digest = dgst
 | 
						|
	}
 | 
						|
 | 
						|
	mhandler := handlers.MethodHandler{
 | 
						|
		http.MethodGet:  http.HandlerFunc(manifestHandler.GetManifest),
 | 
						|
		http.MethodHead: http.HandlerFunc(manifestHandler.GetManifest),
 | 
						|
	}
 | 
						|
 | 
						|
	if !ctx.readOnly {
 | 
						|
		mhandler[http.MethodPut] = http.HandlerFunc(manifestHandler.PutManifest)
 | 
						|
		mhandler[http.MethodDelete] = http.HandlerFunc(manifestHandler.DeleteManifest)
 | 
						|
	}
 | 
						|
 | 
						|
	return mhandler
 | 
						|
}
 | 
						|
 | 
						|
// manifestHandler handles http operations on image manifests.
 | 
						|
type manifestHandler struct {
 | 
						|
	*Context
 | 
						|
 | 
						|
	// One of tag or digest gets set, depending on what is present in context.
 | 
						|
	Tag    string
 | 
						|
	Digest digest.Digest
 | 
						|
}
 | 
						|
 | 
						|
// GetManifest fetches the image manifest from the storage backend, if it exists.
 | 
						|
func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) {
 | 
						|
	dcontext.GetLogger(imh).Debug("GetImageManifest")
 | 
						|
	manifests, err := imh.Repository.Manifests(imh)
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	var supports [numStorageTypes]bool
 | 
						|
 | 
						|
	// this parsing of Accept headers is not quite as full-featured as godoc.org's parser, but we don't care about "q=" values
 | 
						|
	// https://github.com/golang/gddo/blob/e91d4165076d7474d20abda83f92d15c7ebc3e81/httputil/header/header.go#L165-L202
 | 
						|
	for _, acceptHeader := range r.Header["Accept"] {
 | 
						|
		// r.Header[...] is a slice in case the request contains the same header more than once
 | 
						|
		// if the header isn't set, we'll get the zero value, which "range" will handle gracefully
 | 
						|
 | 
						|
		// we need to split each header value on "," to get the full list of "Accept" values (per RFC 2616)
 | 
						|
		// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
 | 
						|
		for _, mediaType := range strings.Split(acceptHeader, ",") {
 | 
						|
			if mediaType, _, err = mime.ParseMediaType(mediaType); err != nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if mediaType == schema2.MediaTypeManifest {
 | 
						|
				supports[manifestSchema2] = true
 | 
						|
			}
 | 
						|
			if mediaType == manifestlist.MediaTypeManifestList {
 | 
						|
				supports[manifestlistSchema] = true
 | 
						|
			}
 | 
						|
			if mediaType == v1.MediaTypeImageManifest {
 | 
						|
				supports[ociSchema] = true
 | 
						|
			}
 | 
						|
			if mediaType == v1.MediaTypeImageIndex {
 | 
						|
				supports[ociImageIndexSchema] = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if imh.Tag != "" {
 | 
						|
		tags := imh.Repository.Tags(imh)
 | 
						|
		desc, err := tags.Get(imh, imh.Tag)
 | 
						|
		if err != nil {
 | 
						|
			if _, ok := err.(distribution.ErrTagUnknown); ok {
 | 
						|
				imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
 | 
						|
			} else {
 | 
						|
				imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
			}
 | 
						|
			return
 | 
						|
		}
 | 
						|
		imh.Digest = desc.Digest
 | 
						|
	}
 | 
						|
 | 
						|
	if etagMatch(r, imh.Digest.String()) {
 | 
						|
		w.WriteHeader(http.StatusNotModified)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var options []distribution.ManifestServiceOption
 | 
						|
	if imh.Tag != "" {
 | 
						|
		options = append(options, distribution.WithTag(imh.Tag))
 | 
						|
	}
 | 
						|
	manifest, err := manifests.Get(imh, imh.Digest, options...)
 | 
						|
	if err != nil {
 | 
						|
		if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
 | 
						|
		} else {
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
		}
 | 
						|
		return
 | 
						|
	}
 | 
						|
	// determine the type of the returned manifest
 | 
						|
	manifestType := manifestSchema1
 | 
						|
	schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest)
 | 
						|
	manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList)
 | 
						|
	if isSchema2 {
 | 
						|
		manifestType = manifestSchema2
 | 
						|
	} else if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest {
 | 
						|
		manifestType = ociSchema
 | 
						|
	} else if isManifestList {
 | 
						|
		if manifestList.MediaType == manifestlist.MediaTypeManifestList {
 | 
						|
			manifestType = manifestlistSchema
 | 
						|
		} else if manifestList.MediaType == v1.MediaTypeImageIndex {
 | 
						|
			manifestType = ociImageIndexSchema
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if manifestType == ociSchema && !supports[ociSchema] {
 | 
						|
		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI manifest found, but accept header does not support OCI manifests"))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if manifestType == ociImageIndexSchema && !supports[ociImageIndexSchema] {
 | 
						|
		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI index found, but accept header does not support OCI indexes"))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	// Only rewrite schema2 manifests when they are being fetched by tag.
 | 
						|
	// If they are being fetched by digest, we can't return something not
 | 
						|
	// matching the digest.
 | 
						|
	if imh.Tag != "" && manifestType == manifestSchema2 && !supports[manifestSchema2] {
 | 
						|
		// Rewrite manifest in schema1 format
 | 
						|
		dcontext.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String())
 | 
						|
 | 
						|
		manifest, err = imh.convertSchema2Manifest(schema2Manifest)
 | 
						|
		if err != nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
	} else if imh.Tag != "" && manifestType == manifestlistSchema && !supports[manifestlistSchema] {
 | 
						|
		// Rewrite manifest in schema1 format
 | 
						|
		dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String())
 | 
						|
 | 
						|
		// Find the image manifest corresponding to the default
 | 
						|
		// platform
 | 
						|
		var manifestDigest digest.Digest
 | 
						|
		for _, manifestDescriptor := range manifestList.Manifests {
 | 
						|
			if manifestDescriptor.Platform.Architecture == defaultArch && manifestDescriptor.Platform.OS == defaultOS {
 | 
						|
				manifestDigest = manifestDescriptor.Digest
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if manifestDigest == "" {
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		manifest, err = manifests.Get(imh, manifestDigest)
 | 
						|
		if err != nil {
 | 
						|
			if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
 | 
						|
				imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
 | 
						|
			} else {
 | 
						|
				imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
			}
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// If necessary, convert the image manifest
 | 
						|
		if schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 && !supports[manifestSchema2] {
 | 
						|
			manifest, err = imh.convertSchema2Manifest(schema2Manifest)
 | 
						|
			if err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			imh.Digest = manifestDigest
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	ct, p, err := manifest.Payload()
 | 
						|
	if err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	w.Header().Set("Content-Type", ct)
 | 
						|
	w.Header().Set("Content-Length", fmt.Sprint(len(p)))
 | 
						|
	w.Header().Set("Docker-Content-Digest", imh.Digest.String())
 | 
						|
	w.Header().Set("Etag", fmt.Sprintf(`"%s"`, imh.Digest))
 | 
						|
	w.Write(p)
 | 
						|
}
 | 
						|
 | 
						|
func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.DeserializedManifest) (distribution.Manifest, error) {
 | 
						|
	targetDescriptor := schema2Manifest.Target()
 | 
						|
	blobs := imh.Repository.Blobs(imh)
 | 
						|
	configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
 | 
						|
	if err != nil {
 | 
						|
		if err == distribution.ErrBlobUnknown {
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
 | 
						|
		} else {
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
		}
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	ref := imh.Repository.Named()
 | 
						|
 | 
						|
	if imh.Tag != "" {
 | 
						|
		ref, err = reference.WithTag(ref, imh.Tag)
 | 
						|
		if err != nil {
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail(err))
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON)
 | 
						|
	for _, d := range schema2Manifest.Layers {
 | 
						|
		if err := builder.AppendReference(d); err != nil {
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	manifest, err := builder.Build(imh)
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	imh.Digest = digest.FromBytes(manifest.(*schema1.SignedManifest).Canonical)
 | 
						|
 | 
						|
	return manifest, nil
 | 
						|
}
 | 
						|
 | 
						|
func etagMatch(r *http.Request, etag string) bool {
 | 
						|
	for _, headerVal := range r.Header["If-None-Match"] {
 | 
						|
		if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// PutManifest validates and stores a manifest in the registry.
 | 
						|
func (imh *manifestHandler) PutManifest(w http.ResponseWriter, r *http.Request) {
 | 
						|
	dcontext.GetLogger(imh).Debug("PutImageManifest")
 | 
						|
	manifests, err := imh.Repository.Manifests(imh)
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var jsonBuf bytes.Buffer
 | 
						|
	if err := copyFullPayload(imh, w, r, &jsonBuf, maxManifestBodySize, "image manifest PUT"); err != nil {
 | 
						|
		// copyFullPayload reports the error if necessary
 | 
						|
		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err.Error()))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	mediaType := r.Header.Get("Content-Type")
 | 
						|
	manifest, desc, err := distribution.UnmarshalManifest(mediaType, jsonBuf.Bytes())
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if imh.Digest != "" {
 | 
						|
		if desc.Digest != imh.Digest {
 | 
						|
			dcontext.GetLogger(imh).Errorf("payload digest does not match: %q != %q", desc.Digest, imh.Digest)
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	} else if imh.Tag != "" {
 | 
						|
		imh.Digest = desc.Digest
 | 
						|
	} else {
 | 
						|
		imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail("no tag or digest specified"))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	isAnOCIManifest := mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex
 | 
						|
 | 
						|
	if isAnOCIManifest {
 | 
						|
		dcontext.GetLogger(imh).Debug("Putting an OCI Manifest!")
 | 
						|
	} else {
 | 
						|
		dcontext.GetLogger(imh).Debug("Putting a Docker Manifest!")
 | 
						|
	}
 | 
						|
 | 
						|
	var options []distribution.ManifestServiceOption
 | 
						|
	if imh.Tag != "" {
 | 
						|
		options = append(options, distribution.WithTag(imh.Tag))
 | 
						|
	}
 | 
						|
 | 
						|
	if err := imh.applyResourcePolicy(manifest); err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = manifests.Put(imh, manifest, options...)
 | 
						|
	if err != nil {
 | 
						|
		// TODO(stevvooe): These error handling switches really need to be
 | 
						|
		// handled by an app global mapper.
 | 
						|
		if err == distribution.ErrUnsupported {
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if err == distribution.ErrAccessDenied {
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeDenied)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		switch err := err.(type) {
 | 
						|
		case distribution.ErrManifestVerification:
 | 
						|
			for _, verificationError := range err {
 | 
						|
				switch verificationError := verificationError.(type) {
 | 
						|
				case distribution.ErrManifestBlobUnknown:
 | 
						|
					imh.Errors = append(imh.Errors, v2.ErrorCodeManifestBlobUnknown.WithDetail(verificationError.Digest))
 | 
						|
				case distribution.ErrManifestNameInvalid:
 | 
						|
					imh.Errors = append(imh.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
 | 
						|
				case distribution.ErrManifestUnverified:
 | 
						|
					imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnverified)
 | 
						|
				default:
 | 
						|
					if verificationError == digest.ErrDigestInvalidFormat {
 | 
						|
						imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
 | 
						|
					} else {
 | 
						|
						imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown, verificationError)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		case errcode.Error:
 | 
						|
			imh.Errors = append(imh.Errors, err)
 | 
						|
		default:
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
		}
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Tag this manifest
 | 
						|
	if imh.Tag != "" {
 | 
						|
		tags := imh.Repository.Tags(imh)
 | 
						|
		err = tags.Tag(imh, imh.Tag, desc)
 | 
						|
		if err != nil {
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	// Construct a canonical url for the uploaded manifest.
 | 
						|
	ref, err := reference.WithDigest(imh.Repository.Named(), imh.Digest)
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	location, err := imh.urlBuilder.BuildManifestURL(ref)
 | 
						|
	if err != nil {
 | 
						|
		// NOTE(stevvooe): Given the behavior above, this absurdly unlikely to
 | 
						|
		// happen. We'll log the error here but proceed as if it worked. Worst
 | 
						|
		// case, we set an empty location header.
 | 
						|
		dcontext.GetLogger(imh).Errorf("error building manifest url from digest: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	w.Header().Set("Location", location)
 | 
						|
	w.Header().Set("Docker-Content-Digest", imh.Digest.String())
 | 
						|
	w.WriteHeader(http.StatusCreated)
 | 
						|
 | 
						|
	dcontext.GetLogger(imh).Debug("Succeeded in putting manifest!")
 | 
						|
}
 | 
						|
 | 
						|
// applyResourcePolicy checks whether the resource class matches what has
 | 
						|
// been authorized and allowed by the policy configuration.
 | 
						|
func (imh *manifestHandler) applyResourcePolicy(manifest distribution.Manifest) error {
 | 
						|
	allowedClasses := imh.App.Config.Policy.Repository.Classes
 | 
						|
	if len(allowedClasses) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	var class string
 | 
						|
	switch m := manifest.(type) {
 | 
						|
	case *schema1.SignedManifest:
 | 
						|
		class = imageClass
 | 
						|
	case *schema2.DeserializedManifest:
 | 
						|
		switch m.Config.MediaType {
 | 
						|
		case schema2.MediaTypeImageConfig:
 | 
						|
			class = imageClass
 | 
						|
		case schema2.MediaTypePluginConfig:
 | 
						|
			class = "plugin"
 | 
						|
		default:
 | 
						|
			return errcode.ErrorCodeDenied.WithMessage("unknown manifest class for " + m.Config.MediaType)
 | 
						|
		}
 | 
						|
	case *ocischema.DeserializedManifest:
 | 
						|
		switch m.Config.MediaType {
 | 
						|
		case v1.MediaTypeImageConfig:
 | 
						|
			class = imageClass
 | 
						|
		default:
 | 
						|
			return errcode.ErrorCodeDenied.WithMessage("unknown manifest class for " + m.Config.MediaType)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if class == "" {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Check to see if class is allowed in registry
 | 
						|
	var allowedClass bool
 | 
						|
	for _, c := range allowedClasses {
 | 
						|
		if class == c {
 | 
						|
			allowedClass = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if !allowedClass {
 | 
						|
		return errcode.ErrorCodeDenied.WithMessage(fmt.Sprintf("registry does not allow %s manifest", class))
 | 
						|
	}
 | 
						|
 | 
						|
	resources := auth.AuthorizedResources(imh)
 | 
						|
	n := imh.Repository.Named().Name()
 | 
						|
 | 
						|
	var foundResource bool
 | 
						|
	for _, r := range resources {
 | 
						|
		if r.Name == n {
 | 
						|
			if r.Class == "" {
 | 
						|
				r.Class = imageClass
 | 
						|
			}
 | 
						|
			if r.Class == class {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			foundResource = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// resource was found but no matching class was found
 | 
						|
	if foundResource {
 | 
						|
		return errcode.ErrorCodeDenied.WithMessage(fmt.Sprintf("repository not authorized for %s manifest", class))
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// DeleteManifest removes the manifest with the given digest or the tag with the given name from the registry.
 | 
						|
func (imh *manifestHandler) DeleteManifest(w http.ResponseWriter, r *http.Request) {
 | 
						|
	dcontext.GetLogger(imh).Debug("DeleteImageManifest")
 | 
						|
 | 
						|
	if imh.App.isCache {
 | 
						|
		imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if imh.Tag != "" {
 | 
						|
		dcontext.GetLogger(imh).Debug("DeleteImageTag")
 | 
						|
		tagService := imh.Repository.Tags(imh.Context)
 | 
						|
		if err := tagService.Untag(imh.Context, imh.Tag); err != nil {
 | 
						|
			switch err.(type) {
 | 
						|
			case distribution.ErrTagUnknown, driver.PathNotFoundError:
 | 
						|
				imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
 | 
						|
			default:
 | 
						|
				imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
 | 
						|
			}
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.WriteHeader(http.StatusAccepted)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	manifests, err := imh.Repository.Manifests(imh)
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	err = manifests.Delete(imh, imh.Digest)
 | 
						|
	if err != nil {
 | 
						|
		switch err {
 | 
						|
		case digest.ErrDigestUnsupported:
 | 
						|
		case digest.ErrDigestInvalidFormat:
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
 | 
						|
			return
 | 
						|
		case distribution.ErrBlobUnknown:
 | 
						|
			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown)
 | 
						|
			return
 | 
						|
		case distribution.ErrUnsupported:
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
 | 
						|
			return
 | 
						|
		default:
 | 
						|
			imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	tagService := imh.Repository.Tags(imh)
 | 
						|
	referencedTags, err := tagService.Lookup(imh, distribution.Descriptor{Digest: imh.Digest})
 | 
						|
	if err != nil {
 | 
						|
		imh.Errors = append(imh.Errors, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tag := range referencedTags {
 | 
						|
		if err := tagService.Untag(imh, tag); err != nil {
 | 
						|
			imh.Errors = append(imh.Errors, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	w.WriteHeader(http.StatusAccepted)
 | 
						|
}
 |