Repository: regclient/regclient
Pull request #768 (2024-06-23 14:26:58)

Refactor pulling a platform specific manifest
<!--

Commits must be signed indicating your agreement to the [DCO](https://developercertificate.org/).
See [DCO missing](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) for steps to fix a missing signoff.

-->

### Fixes issue

<!-- If this is a bug fix, include "fixes #xxxx", or "closes #xxxx" -->

### Describe the change

The refactors the pulling of manifests by platform into the regclient library instead of duplicated with different implementations in various locations.
<!-- Include the type of change: bug fix, new feature, breaking change, documentation update -->
<!-- Describe what was changed, why the change was made, and how it was implemented -->

### How to verify it

No user visible changes should be encountered.
<!-- Include steps that can be taken to verify the change -->

### Changelog text

- Chore: Refactor pulling manifests by platform.
<!-- If the release changelog should have an entry for this, include it here -->

### Please verify and check that the pull request fulfills the following requirements

<!-- Mark the following with an [X] to verify they are included -->

- [X] Tests have been added or not applicable
- [X] Documentation has been added, updated, or not applicable
- [X] Changes have been rebased to main
- [X] Multiple commits to the same code have been squashed

<!-- markdownlint-disable-file MD041 -->



Patch:
diff --git a/cmd/regctl/artifact.go b/cmd/regctl/artifact.go
index 5c6aee8..c739fae 100644
--- a/cmd/regctl/artifact.go
+++ b/cmd/regctl/artifact.go
@@ -610,7 +610,6 @@ func (artifactOpts *artifactCmd) runArtifactList(cmd *cobra.Command, args []stri
 
 func (artifactOpts *artifactCmd) runArtifactPut(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
-	mOpts := []manifest.Opts{}
 	hasConfig := false
 	var r, rArt, rSubject ref.Ref
 	var err error
@@ -720,28 +719,20 @@ func (artifactOpts *artifactCmd) runArtifactPut(cmd *cobra.Command, args []strin
 
 	var subjectDesc *descriptor.Descriptor
 	if rSubject.IsSet() {
-		smh, err := rc.ManifestHead(ctx, rSubject, regclient.WithManifestRequireDigest())
-		if err != nil {
-			return fmt.Errorf("unable to find subject manifest: %w", err)
-		}
-		if smh.IsList() && artifactOpts.platform != "" {
-			sml, err := rc.ManifestGet(ctx, rSubject)
-			if err != nil {
-				return fmt.Errorf("unable to get subject manifest: %w", err)
-			}
-			plat, err := platform.Parse(artifactOpts.platform)
-			if err != nil {
-				return fmt.Errorf("failed to parse platform: %w", err)
-			}
-			d, err := manifest.GetPlatformDesc(sml, &plat)
+		mOpts := []regclient.ManifestOpts{regclient.WithManifestRequireDigest()}
+		if artifactOpts.platform != "" {
+			p, err := platform.Parse(artifactOpts.platform)
 			if err != nil {
-				return fmt.Errorf("failed to get platform descriptor: %w", err)
+				return fmt.Errorf("failed to parse platform %s: %w", artifactOpts.platform, err)
 			}
-			subjectDesc = &descriptor.Descriptor{MediaType: d.MediaType, Digest: d.Digest, Size: d.Size}
-		} else {
-			d := smh.GetDescriptor()
-			subjectDesc = &descriptor.Descriptor{MediaType: d.MediaType, Digest: d.Digest, Size: d.Size}
+			mOpts = append(mOpts, regclient.WithManifestPlatform(p))
 		}
+		smh, err := rc.ManifestHead(ctx, rSubject, mOpts...)
+		if err != nil {
+			return fmt.Errorf("unable to find subject manifest: %w", err)
+		}
+		d := smh.GetDescriptor()
+		subjectDesc = &descriptor.Descriptor{MediaType: d.MediaType, Digest: d.Digest, Size: d.Size}
 	}
 
 	// read config, or initialize to an empty json config
@@ -873,6 +864,7 @@ func (artifactOpts *artifactCmd) runArtifactPut(cmd *cobra.Command, args []strin
 		blobs = append(blobs, d)
 	}
 
+	mOpts := []manifest.Opts{}
 	switch artifactOpts.artifactMT {
 	case mediatype.OCI1Artifact:
 		m := v1.ArtifactManifest{
diff --git a/cmd/regctl/image.go b/cmd/regctl/image.go
index ba42a55..87efbb6 100644
--- a/cmd/regctl/image.go
+++ b/cmd/regctl/image.go
@@ -1040,17 +1040,11 @@ func (imageOpts *imageCmd) runImageCopy(cmd *cobra.Command, args []string) error
 		if err != nil {
 			return err
 		}
-		m, err := rc.ManifestGet(ctx, rSrc)
+		m, err := rc.ManifestGet(ctx, rSrc, regclient.WithManifestPlatform(p))
 		if err != nil {
 			return err
 		}
-		if m.IsList() {
-			d, err := manifest.GetPlatformDesc(m, &p)
-			if err != nil {
-				return err
-			}
-			rSrc.Digest = d.Digest.String()
-		}
+		rSrc = rSrc.SetDigest(m.GetDescriptor().Digest.String())
 	}
 	log.WithFields(logrus.Fields{
 		"source":      rSrc.CommonName(),
@@ -1405,17 +1399,11 @@ func (imageOpts *imageCmd) runImageExport(cmd *cobra.Command, args []string) err
 		if err != nil {
 			return err
 		}
-		m, err := rc.ManifestGet(ctx, r)
+		m, err := rc.ManifestGet(ctx, r, regclient.WithManifestPlatform(p))
 		if err != nil {
 			return err
 		}
-		if m.IsList() {
-			d, err := manifest.GetPlatformDesc(m, &p)
-			if err != nil {
-				return err
-			}
-			r.Digest = d.Digest.String()
-		}
+		r = r.SetDigest(m.GetDescriptor().Digest.String())
 	}
 	if imageOpts.exportCompress {
 		opts = append(opts, regclient.ImageWithExportCompress())
@@ -1449,40 +1437,19 @@ func (imageOpts *imageCmd) runImageGetFile(cmd *cobra.Command, args []string) er
 		"filename": filename,
 	}).Debug("Get file")
 
-	// make it recursive for index of index scenarios
-	m, err := rc.ManifestGet(ctx, r)
+	if imageOpts.platform == "" {
+		imageOpts.platform = "local"
+	}
+	p, err := platform.Parse(imageOpts.platform)
 	if err != nil {
-		return err
+		log.WithFields(logrus.Fields{
+			"platform": imageOpts.platform,
+			"err":      err,
+		}).Warn("Could not parse platform")
 	}
-	if m.IsList() {
-		if imageOpts.platform == "" {
-			imageOpts.platform = "local"
-		}
-		plat, err := platform.Parse(imageOpts.platform)
-		if err != nil {
-			log.WithFields(logrus.Fields{
-				"platform": imageOpts.platform,
-				"err":      err,
-			}).Warn("Could not parse platform")
-		}
-		desc, err := manifest.GetPlatformDesc(m, &plat)
-		if err != nil {
-			pl, _ := manifest.GetPlatformList(m)
-			var ps []string
-			for _, p := range pl {
-				ps = append(ps, p.String())
-			}
-			log.WithFields(logrus.Fields{
-				"platform":  plat,
-				"err":       err,
-				"platforms": strings.Join(ps, ", "),
-			}).Warn("Platform could not be found in manifest list")
-			return err
-		}
-		m, err = rc.ManifestGet(ctx, r, regclient.WithManifestDesc(*desc))
-		if err != nil {
-			return fmt.Errorf("failed to pull platform specific digest: %w", err)
-		}
+	m, err := rc.ManifestGet(ctx, r, regclient.WithManifestPlatform(p))
+	if err != nil {
+		return err
 	}
 	// go through layers in reverse
 	mi, ok := m.(manifest.Imager)
diff --git a/cmd/regctl/manifest.go b/cmd/regctl/manifest.go
index a51e1b3..b431557 100644
--- a/cmd/regctl/manifest.go
+++ b/cmd/regctl/manifest.go
@@ -1,11 +1,9 @@
 package main
 
 import (
-	"context"
 	"encoding/json"
 	"fmt"
 	"io"
-	"os"
 	"strings"
 
 	"github.com/sirupsen/logrus"
@@ -156,9 +154,9 @@ regctl manifest put \
 	_ = manifestHeadCmd.RegisterFlagCompletionFunc("platform", completeArgPlatform)
 	_ = manifestHeadCmd.Flags().MarkHidden("list")
 
-	manifestGetCmd.Flags().BoolVarP(&manifestOpts.list, "list", "", true, "Output manifest list if available (enabled by default)")
+	manifestGetCmd.Flags().BoolVarP(&manifestOpts.list, "list", "", true, "Deprecated: Output manifest list if available")
 	manifestGetCmd.Flags().StringVarP(&manifestOpts.platform, "platform", "p", "", "Specify platform (e.g. linux/amd64 or local)")
-	manifestGetCmd.Flags().BoolVarP(&manifestOpts.requireList, "require-list", "", false, "Fail if manifest list is not received")
+	manifestGetCmd.Flags().BoolVarP(&manifestOpts.requireList, "require-list", "", false, "Deprecated: Fail if manifest list is not received")
 	manifestGetCmd.Flags().StringVarP(&manifestOpts.formatGet, "format", "", "{{printPretty .}}", "Format output with go template syntax (use \"raw-body\" for the original manifest)")
 	_ = manifestGetCmd.RegisterFlagCompletionFunc("platform", completeArgPlatform)
 	_ = manifestGetCmd.RegisterFlagCompletionFunc("format", completeArgNone)
@@ -268,10 +266,11 @@ func (manifestOpts *manifestCmd) runManifestDiff(cmd *cobra.Command, args []stri
 
 func (manifestOpts *manifestCmd) runManifestHead(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
-	if manifestOpts.platform != "" && !flagChanged(cmd, "list") {
-		manifestOpts.list = false
-	} else if !manifestOpts.list && !flagChanged(cmd, "list") {
-		manifestOpts.list = true
+	if flagChanged(cmd, "list") {
+		log.Info("list option has been deprecated, manifest list is output by default until a platform is specified")
+	}
+	if manifestOpts.platform != "" && manifestOpts.requireList {
+		return fmt.Errorf("cannot request a platform and require-list simultaneously")
 	}
 
 	r, err := ref.New(args[0])
@@ -279,6 +278,7 @@ func (manifestOpts *manifestCmd) runManifestHead(cmd *cobra.Command, args []stri
 		return err
 	}
 	rc := manifestOpts.rootOpts.newRegClient()
+	defer rc.Close(ctx, r)
 
 	log.WithFields(logrus.Fields{
 		"host": r.Registry,
@@ -290,35 +290,19 @@ func (manifestOpts *manifestCmd) runManifestHead(cmd *cobra.Command, args []stri
 	if manifestOpts.requireDigest || (!flagChanged(cmd, "require-digest") && !flagChanged(cmd, "format")) {
 		mOpts = append(mOpts, regclient.WithManifestRequireDigest())
 	}
+	if manifestOpts.platform != "" {
+		p, err := platform.Parse(manifestOpts.platform)
+		if err != nil {
+			return fmt.Errorf("failed to parse platform %s: %w", manifestOpts.platform, err)
+		}
+		mOpts = append(mOpts, regclient.WithManifestPlatform(p))
+	}
 
-	// attempt to request only the headers, avoids Docker Hub rate limits
 	m, err := rc.ManifestHead(ctx, r, mOpts...)
 	if err != nil {
 		return err
 	}
 
-	// add warning if not list and list required or platform requested
-	if !m.IsList() && manifestOpts.requireList {
-		log.Warn("Manifest list unavailable")
-		return ErrNotFound
-	}
-	if !m.IsList() && manifestOpts.platform != "" {
-		log.Info("Manifest list unavailable, ignoring platform flag")
-	}
-
-	// retrieve the specified platform from the manifest list
-	for m.IsList() && !manifestOpts.list && !manifestOpts.requireList {
-		desc, err := getPlatformDesc(ctx, rc, m, manifestOpts.platform)
-		if err != nil {
-			return fmt.Errorf("failed retrieving platform specific digest: %w", err)
-		}
-		r.Digest = desc.Digest.String()
-		m, err = rc.ManifestHead(ctx, r, mOpts...)
-		if err != nil {
-			return fmt.Errorf("failed retrieving platform specific digest: %w", err)
-		}
-	}
-
 	switch manifestOpts.formatHead {
 	case "", "digest":
 		manifestOpts.formatHead = "{{ printf \"%s\\n\" .GetDescriptor.Digest }}"
@@ -330,10 +314,11 @@ func (manifestOpts *manifestCmd) runManifestHead(cmd *cobra.Command, args []stri
 
 func (manifestOpts *manifestCmd) runManifestGet(cmd *cobra.Command, args []string) error {
 	ctx := cmd.Context()
-	if manifestOpts.platform != "" && !flagChanged(cmd, "list") {
-		manifestOpts.list = false
-	} else if !manifestOpts.list && !flagChanged(cmd, "list") {
-		manifestOpts.list = true
+	if flagChanged(cmd, "list") {
+		log.Info("list option has been deprecated, manifest list is output by default until a platform is specified")
+	}
+	if manifestOpts.platform != "" && manifestOpts.requireList {
+		return fmt.Errorf("cannot request a platform and require-list simultaneously")
 	}
 
 	r, err := ref.New(args[0])
@@ -343,7 +328,22 @@ func (manifestOpts *manifestCmd) runManifestGet(cmd *cobra.Command, args []strin
 	rc := manifestOpts.rootOpts.newRegClient()
 	defer rc.Close(ctx, r)
 
-	m, err := getManifest(ctx, rc, r, manifestOpts.platform, manifestOpts.list, manifestOpts.requireList)
+	log.WithFields(logrus.Fields{
+		"host": r.Registry,
+		"repo": r.Repository,
+		"tag":  r.Tag,
+	}).Debug("Manifest get")
+
+	mOpts := []regclient.ManifestOpts{}
+	if manifestOpts.platform != "" {
+		p, err := platform.Parse(manifestOpts.platform)
+		if err != nil {
+			return fmt.Errorf("failed to parse platform %s: %w", manifestOpts.platform, err)
+		}
+		mOpts = append(mOpts, regclient.WithManifestPlatform(p))
+	}
+
+	m, err := rc.ManifestGet(ctx, r, mOpts...)
 	if err != nil {
 		return err
 	}
@@ -368,7 +368,7 @@ func (manifestOpts *manifestCmd) runManifestPut(cmd *cobra.Command, args []strin
 	rc := manifestOpts.rootOpts.newRegClient()
 	defer rc.Close(ctx, r)
 
-	raw, err := io.ReadAll(os.Stdin)
+	raw, err := io.ReadAll(cmd.InOrStdin())
 	if err != nil {
 		return err
 	}
@@ -405,79 +405,3 @@ func (manifestOpts *manifestCmd) runManifestPut(cmd *cobra.Command, args []strin
 	}
 	return template.Writer(cmd.OutOrStdout(), manifestOpts.formatPut, result)
 }
-
-func getManifest(ctx context.Context, rc *regclient.RegClient, r ref.Ref, pStr string, list, reqList bool) (manifest.Manifest, error) {
-	m, err := rc.ManifestGet(context.Background(), r)
-	if err != nil {
-		return m, err
-	}
-
-	// add warning if not list and list required or platform requested
-	if !m.IsList() && reqList {
-		log.Warn("Manifest list unavailable")
-		return m, ErrNotFound
-	}
-	if !m.IsList() && pStr != "" {
-		log.Info("Manifest list unavailable, ignoring platform flag")
-	}
-
-	// retrieve the specified platform from the manifest list
-	if m.IsList() && !list && !reqList {
-		desc, err := getPlatformDesc(ctx, rc, m, pStr)
-		if err != nil {
-			return m, fmt.Errorf("failed to lookup platform specific digest: %w", err)
-		}
-		m, err = rc.ManifestGet(ctx, r, regclient.WithManifestDesc(*desc))
-		if err != nil {
-			return m, fmt.Errorf("failed to pull platform specific digest: %w", err)
-		}
-	}
-	return m, nil
-}
-
-func getPlatformDesc(ctx context.Context, rc *regclient.RegClient, m manifest.Manifest, pStr string) (*descriptor.Descriptor, error) {
-	var desc *descriptor.Descriptor
-	var err error
-	if !m.IsList() {
-		return desc, fmt.Errorf("%w: manifest is not a list", ErrInvalidInput)
-	}
-	if !m.IsSet() {
-		m, err = rc.ManifestGet(ctx, m.GetRef())
-		if err != nil {
-			return desc, fmt.Errorf("unable to retrieve manifest list: %w", err)
-		}
-	}
-
-	var plat platform.Platform
-	if pStr != "" && pStr != "local" {
-		plat, err = platform.Parse(pStr)
-		if err != nil {
-			log.WithFields(logrus.Fields{
-				"platform": pStr,
-				"err":      err,
-			}).Warn("Could not parse platform")
-		}
-	}
-	if plat.OS == "" {
-		plat = platform.Local()
-	}
-	desc, err = manifest.GetPlatformDesc(m, &plat)
-	if err != nil {
-		pl, _ := manifest.GetPlatformList(m)
-		var ps []string
-		for _, p := range pl {
-			ps = append(ps, p.String())
-		}
-		log.WithFields(logrus.Fields{
-			"platform":  plat,
-			"err":       err,
-			"platforms": strings.Join(ps, ", "),
-		}).Warn("Platform could not be found in manifest list")
-		return desc, ErrNotFound
-	}
-	log.WithFields(logrus.Fields{
-		"platform": plat,
-		"digest":   desc.Digest.String(),
-	}).Debug("Found platform specific digest in manifest list")
-	return desc, nil
-}
diff --git a/image.go b/image.go
index 1dc12a4..49c7c93 100644
--- a/image.go
+++ b/image.go
@@ -446,7 +446,11 @@ func (rc *RegClient) ImageConfig(ctx context.Context, r ref.Ref, opts ...ImageOp
 	for _, optFn := range opts {
 		optFn(&opt)
 	}
-	m, err := rc.ManifestGet(ctx, r)
+	p, err := platform.Parse(opt.platform)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse platform %s: %w", opt.platform, err)
+	}
+	m, err := rc.ManifestGet(ctx, r, WithManifestPlatform(p))
 	if err != nil {
 		return nil, fmt.Errorf("failed to get manifest: %w", err)
 	}
@@ -459,10 +463,6 @@ func (rc *RegClient) ImageConfig(ctx context.Context, r ref.Ref, opts ...ImageOp
 		if err != nil {
 			return nil, fmt.Errorf("failed to get manifest list: %w", err)
 		}
-		p, err := platform.Parse(opt.platform)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse platform %s: %w", opt.platform, err)
-		}
 		d, err := descriptor.DescriptorListSearch(ml, descriptor.MatchOpt{Platform: &p})
 		if err != nil {
 			return nil, fmt.Errorf("failed to find platform in manifest list: %w", err)
diff --git a/manifest.go b/manifest.go
index 2aa767e..12d6eac 100644
--- a/manifest.go
+++ b/manifest.go
@@ -8,11 +8,13 @@ import (
 	"github.com/regclient/regclient/types/descriptor"
 	"github.com/regclient/regclient/types/errs"
 	"github.com/regclient/regclient/types/manifest"
+	"github.com/regclient/regclient/types/platform"
 	"github.com/regclient/regclient/types/ref"
 )
 
 type manifestOpt struct {
 	d             descriptor.Descriptor
+	platform      *platform.Platform
 	schemeOpts    []scheme.ManifestOpts
 	requireDigest bool
 }
@@ -51,6 +53,15 @@ func WithManifestDesc(d descriptor.Descriptor) ManifestOpts {
 	}
 }
 
+// WithManifestPlatform resolves the platform specific manifest on Get and Head requests.
+// This causes an additional GET query to a registry when an Index or Manifest List is encountered.
+// This option is ignored if the retrieved manifest is not an Index or Manifest List.
+func WithManifestPlatform(p platform.Platform) ManifestOpts {
+	return func(opts *manifestOpt) {
+		opts.platform = &p
+	}
+}
+
 // WithManifestRequireDigest falls back from a HEAD to a GET request when digest headers aren't received.
 func WithManifestRequireDigest() ManifestOpts {
 	return func(opts *manifestOpt) {
@@ -100,7 +111,26 @@ func (rc *RegClient) ManifestGet(ctx context.Context, r ref.Ref, opts ...Manifes
 	if err != nil {
 		return nil, err
 	}
-	return schemeAPI.ManifestGet(ctx, r)
+	m, err := schemeAPI.ManifestGet(ctx, r)
+	if err != nil {
+		return m, err
+	}
+	if opt.platform != nil && !m.IsList() {
+		rc.log.Debugf("ignoring platform option %s, %s is not an index", opt.platform.String(), r.CommonName())
+	}
+	// this will loop to handle a nested index
+	for opt.platform != nil && m.IsList() {
+		d, err := manifest.GetPlatformDesc(m, opt.platform)
+		if err != nil {
+			return m, err
+		}
+		r = r.SetDigest(d.Digest.String())
+		m, err = schemeAPI.ManifestGet(ctx, r)
+		if err != nil {
+			return m, err
+		}
+	}
+	return m, err
 }
 
 // ManifestHead queries for the existence of a manifest and returns metadata (digest, media-type, size).
@@ -120,6 +150,24 @@ func (rc *RegClient) ManifestHead(ctx context.Context, r ref.Ref, opts ...Manife
 	if err != nil {
 		return m, err
 	}
+	if opt.platform != nil && !m.IsList() {
+		rc.log.Debugf("ignoring platform option %s, %s is not an index", opt.platform.String(), r.CommonName())
+	}
+	// this will loop to handle a nested index
+	for opt.platform != nil && m.IsList() {
+		if !m.IsSet() {
+			m, err = schemeAPI.ManifestGet(ctx, r)
+		}
+		d, err := manifest.GetPlatformDesc(m, opt.platform)
+		if err != nil {
+			return m, err
+		}
+		r = r.SetDigest(d.Digest.String())
+		m, err = schemeAPI.ManifestHead(ctx, r)
+		if err != nil {
+			return m, err
+		}
+	}
 	if opt.requireDigest && m.GetDescriptor().Digest.String() == "" {
 		m, err = schemeAPI.ManifestGet(ctx, r)
 	}
diff --git a/mod/config.go b/mod/config.go
index f8e690c..91bee71 100644
--- a/mod/config.go
+++ b/mod/config.go
@@ -9,8 +9,6 @@ import (
 	"time"
 
 	"github.com/regclient/regclient"
-	"github.com/regclient/regclient/types/descriptor"
-	"github.com/regclient/regclient/types/manifest"
 	"github.com/regclient/regclient/types/platform"
 	"github.com/regclient/regclient/types/ref"
 )
@@ -150,34 +148,7 @@ func WithConfigTimestamp(optTime OptTime) Opts {
 			}
 			// offset startHistory from base image history
 			if !optTime.BaseRef.IsZero() {
-				var d descriptor.Descriptor
-				for {
-					mOpts := []regclient.ManifestOpts{}
-					if d.Digest != "" {
-						mOpts = append(mOpts, regclient.WithManifestDesc(d))
-					}
-					m, err := rc.ManifestGet(c, optTime.BaseRef, mOpts...)
-					if err != nil {
-						return fmt.Errorf("unable to get base image: %w", err)
-					}
-					if mi, ok := m.(manifest.Imager); ok {
-						cd, err := mi.GetConfig()
-						if err != nil {
-							return fmt.Errorf("unable to get base image config descriptor: %w", err)
-						}
-						d = cd
-						break
-					} else if _, ok := m.(manifest.Indexer); ok {
-						pd, err := manifest.GetPlatformDesc(m, &oc.Platform)
-						if err != nil {
-							return fmt.Errorf("unable to get base image platform %s: %w", oc.Platform.String(), err)
-						}
-						d = *pd
-					} else {
-						return fmt.Errorf("unsupported base image manifest")
-					}
-				}
-				baseConfig, err := rc.BlobGetOCIConfig(c, optTime.BaseRef, d)
+				baseConfig, err := rc.ImageConfig(c, optTime.BaseRef, regclient.ImageWithPlatform(oc.Platform.String()))
 				if err != nil {
 					return fmt.Errorf("failed to get base image config: %w", err)
 				}
diff --git a/scheme/ocidir/referrer.go b/scheme/ocidir/referrer.go
index a1cfe42..a93862b 100644
--- a/scheme/ocidir/referrer.go
+++ b/scheme/ocidir/referrer.go
@@ -28,28 +28,29 @@ func (o *OCIDir) referrerList(ctx context.Context, r ref.Ref, opts ...scheme.Ref
 		opt(&config)
 	}
 	rl := referrer.ReferrerList{
-		Subject: r,
-		Tags:    []string{},
+		Tags: []string{},
 	}
 	// select a platform from a manifest list
 	if config.Platform != "" {
+		p, err := platform.Parse(config.Platform)
+		if err != nil {
+			return rl, err
+		}
 		m, err := o.manifestGet(ctx, r)
 		if err != nil {
 			return rl, err
 		}
-		if m.IsList() {
-			plat, err := platform.Parse(config.Platform)
+		for m.IsList() {
+			d, err := manifest.GetPlatformDesc(m, &p)
 			if err != nil {
 				return rl, err
 			}
-			d, err := manifest.GetPlatformDesc(m, &plat)
+			m, err = o.manifestGet(ctx, r.SetDigest(d.Digest.String()))
 			if err != nil {
 				return rl, err
 			}
-			r.Digest = d.Digest.String()
-		} else {
-			r.Digest = m.GetDescriptor().Digest.String()
 		}
+		r = r.SetDigest(m.GetDescriptor().Digest.String())
 	}
 	// if ref is a tag, run a head request for the digest
 	if r.Digest == "" {
@@ -57,8 +58,9 @@ func (o *OCIDir) referrerList(ctx context.Context, r ref.Ref, opts ...scheme.Ref
 		if err != nil {
 			return rl, err
 		}
-		r.Digest = m.GetDescriptor().Digest.String()
+		r = r.SetDigest(m.GetDescriptor().Digest.String())
 	}
+	rl.Subject = r
 
 	// pull referrer list by tag
 	rlTag, err := referrer.FallbackTag(r)
diff --git a/scheme/reg/referrer.go b/scheme/reg/referrer.go
index 1603308..5ab832d 100644
--- a/scheme/reg/referrer.go
+++ b/scheme/reg/referrer.go
@@ -28,32 +28,45 @@ func (reg *Reg) ReferrerList(ctx context.Context, r ref.Ref, opts ...scheme.Refe
 		opt(&config)
 	}
 	rl := referrer.ReferrerList{
-		Subject: r,
-		Tags:    []string{},
+		Tags: []string{},
 	}
 	// select a platform from a manifest list
 	if config.Platform != "" {
+		p, err := platform.Parse(config.Platform)
+		if err != nil {
+			return rl, err
+		}
 		m, err := reg.ManifestHead(ctx, r)
 		if err != nil {
 			return rl, err
 		}
-		if m.IsList() {
+		if m.GetDescriptor().Digest.String() == "" {
 			m, err = reg.ManifestGet(ctx, r)
 			if err != nil {
 				return rl, err
 			}
-			plat, err := platform.Parse(config.Platform)
+		}
+		for m.IsList() {
+			m, err = reg.ManifestGet(ctx, r)
 			if err != nil {
 				return rl, err
 			}
-			d, err := manifest.GetPlatformDesc(m, &plat)
+			d, err := manifest.GetPlatformDesc(m, &p)
 			if err != nil {
 				return rl, err
 			}
-			r.Digest = d.Digest.String()
-		} else {
-			r.Digest = m.GetDescriptor().Digest.String()
+			m, err = reg.ManifestHead(ctx, r.SetDigest(d.Digest.String()))
+			if err != nil {
+				return rl, err
+			}
+			if m.GetDescriptor().Digest.String() == "" {
+				m, err = reg.ManifestGet(ctx, r.SetDigest(d.Digest.String()))
+				if err != nil {
+					return rl, err
+				}
+			}
 		}
+		r = r.SetDigest(m.GetDescriptor().Digest.String())
 	}
 	// if ref is a tag, run a head request for the digest
 	if r.Digest == "" {
@@ -61,8 +74,9 @@ func (reg *Reg) ReferrerList(ctx context.Context, r ref.Ref, opts ...scheme.Refe
 		if err != nil {
 			return rl, err
 		}
-		r.Digest = m.GetDescriptor().Digest.String()
+		r = r.SetDigest(m.GetDescriptor().Digest.String())
 	}
+	rl.Subject = r
 
 	found := false
 	// try cache
diff --git a/types/manifest/manifest.go b/types/manifest/manifest.go
index aae2cc4..8810427 100644
--- a/types/manifest/manifest.go
+++ b/types/manifest/manifest.go
@@ -182,13 +182,17 @@ func GetMediaType(m Manifest) string {
 
 // GetPlatformDesc returns the descriptor for a specific platform from an index.
 func GetPlatformDesc(m Manifest, p *platform.Platform) (*descriptor.Descriptor, error) {
-	dl, err := m.GetManifestList()
-	if err != nil {
-		return nil, err
-	}
 	if p == nil {
 		return nil, fmt.Errorf("invalid input, platform is nil%.0w", errs.ErrNotFound)
 	}
+	mi, ok := m.(Indexer)
+	if !ok {
+		return nil, fmt.Errorf("unsupported manifest type: %s", m.GetDescriptor().MediaType)
+	}
+	dl, err := mi.GetManifestList()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get manifest list: %w", err)
+	}
 	d, err := descriptor.DescriptorListSearch(dl, descriptor.MatchOpt{Platform: p})
 	if err != nil {
 		return nil, fmt.Errorf("platform not found: %s%.0w", *p, err)
@@ -198,17 +202,15 @@ func GetPlatformDesc(m Manifest, p *platform.Platform) (*descriptor.Descriptor,
 
 // GetPlatformList returns the list of platforms from an index.
 func GetPlatformList(m Manifest) ([]*platform.Platform, error) {
-	dl, err := m.GetManifestList()
-	if err != nil {
-		return nil, err
+	mi, ok := m.(Indexer)
+	if !ok {
+		return nil, fmt.Errorf("unsupported manifest type: %s", m.GetDescriptor().MediaType)
 	}
-	var l []*platform.Platform
-	for _, d := range dl {
-		if d.Platform != nil {
-			l = append(l, d.Platform)
-		}
+	dl, err := mi.GetManifestList()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get manifest list: %w", err)
 	}
-	return l, nil
+	return getPlatformList(dl)
 }
 
 // GetRateLimit returns the current rate limit seen in headers.


Test patch:
diff --git a/cmd/regctl/image_test.go b/cmd/regctl/image_test.go
index 9bd6d74..dd44bd2 100644
--- a/cmd/regctl/image_test.go
+++ b/cmd/regctl/image_test.go
@@ -3,10 +3,85 @@ package main
 import (
 	"errors"
 	"fmt"
+	"net/http/httptest"
+	"net/url"
+	"path/filepath"
 	"strings"
 	"testing"
+
+	"github.com/olareg/olareg"
+	oConfig "github.com/olareg/olareg/config"
 )
 
+func TestImageCopy(t *testing.T) {
+	tempDir := t.TempDir()
+	srcRef := "ocidir://../../testdata/testrepo:v2"
+	boolT := true
+	regHandler := olareg.New(oConfig.Config{
+		Storage: oConfig.ConfigStorage{
+			StoreType: oConfig.StoreMem,
+			RootDir:   "../../testdata",
+		},
+		API: oConfig.ConfigAPI{
+			DeleteEnabled: &boolT,
+		},
+	})
+	ts := httptest.NewServer(regHandler)
+	tsURL, _ := url.Parse(ts.URL)
+	tsHost := tsURL.Host
+	t.Cleanup(func() {
+		ts.Close()
+		_ = regHandler.Close()
+	})
+	t.Setenv(ConfigEnv, filepath.Join(tempDir, "config.json"))
+	_, err := cobraTest(t, nil, "registry", "set", tsHost, "--tls", "disabled")
+	if err != nil {
+		t.Fatalf("failed to disable TLS for internal registry")
+	}
+	tt := []struct {
+		name        string
+		args        []string
+		expectErr   error
+		expectOut   string
+		outContains bool
+	}{
+		{
+			name:      "ocidir-to-ocidir",
+			args:      []string{"image", "copy", srcRef, "ocidir://" + tempDir + "testrepo:v2"},
+			expectOut: "ocidir://" + tempDir + "testrepo:v2",
+		},
+		{
+			name:      "ocidir-to-reg",
+			args:      []string{"image", "copy", srcRef, tsHost + "/newrepo:v2"},
+			expectOut: tsHost + "/newrepo:v2",
+		},
+		{
+			name:      "reg-to-reg-platform",
+			args:      []string{"image", "copy", "--platform", "linux/amd64", tsHost + "/testrepo:v3", tsHost + "/newrepo:v3"},
+			expectOut: tsHost + "/newrepo:v3",
+		},
+	}
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			out, err := cobraTest(t, nil, tc.args...)
+			if tc.expectErr != nil {
+				if err == nil {
+					t.Errorf("did not receive expected error: %v", tc.expectErr)
+				} else if !errors.Is(err, tc.expectErr) && err.Error() != tc.expectErr.Error() {
+					t.Errorf("unexpected error, received %v, expected %v", err, tc.expectErr)
+				}
+				return
+			}
+			if err != nil {
+				t.Fatalf("returned unexpected error: %v", err)
+			}
+			if (!tc.outContains && out != tc.expectOut) || (tc.outContains && !strings.Contains(out, tc.expectOut)) {
+				t.Errorf("unexpected output, expected %s, received %s", tc.expectOut, out)
+			}
+		})
+	}
+}
+
 func TestImageCreate(t *testing.T) {
 	tmpDir := t.TempDir()
 	imageRef := fmt.Sprintf("ocidir://%s/repo:scratch", tmpDir)
diff --git a/cmd/regctl/manifest_test.go b/cmd/regctl/manifest_test.go
index 61d3d0f..42604fd 100644
--- a/cmd/regctl/manifest_test.go
+++ b/cmd/regctl/manifest_test.go
@@ -47,7 +47,7 @@ func TestManifestHead(t *testing.T) {
 		{
 			name:      "Platform unknown",
 			args:      []string{"manifest", "head", "ocidir://../../testdata/testrepo:v1", "--platform", "linux/unknown"},
-			expectErr: ErrNotFound,
+			expectErr: errs.ErrNotFound,
 		},
 	}
 	for _, tc := range tt {
diff --git a/manifest_test.go b/manifest_test.go
index 3e87b5d..53ac88b 100644
--- a/manifest_test.go
+++ b/manifest_test.go
@@ -13,6 +13,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/olareg/olareg"
+	oConfig "github.com/olareg/olareg/config"
 	"github.com/opencontainers/go-digest"
 	"github.com/sirupsen/logrus"
 
@@ -23,14 +25,15 @@ import (
 	"github.com/regclient/regclient/types/errs"
 	"github.com/regclient/regclient/types/manifest"
 	"github.com/regclient/regclient/types/mediatype"
+	"github.com/regclient/regclient/types/platform"
 	"github.com/regclient/regclient/types/ref"
 )
 
 func TestManifest(t *testing.T) {
 	t.Parallel()
-	repoPath := "/proj"
-	getTag := "get"
-	headTag := "head"
+	repoPath := "testrepo"
+	goodTag := "v1"
+	deleteTag := "v3"
 	noheadTag := "nohead"
 	nodigestTag := "nodigest"
 	missingTag := "missing"
@@ -63,7 +66,7 @@ func TestManifest(t *testing.T) {
 			ReqEntry: reqresp.ReqEntry{
 				Name:   "Get",
 				Method: "GET",
-				Path:   "/v2" + repoPath + "/manifests/" + getTag,
+				Path:   "/v2/" + repoPath + "/manifests/" + goodTag,
 			},
 			RespEntry: reqresp.RespEntry{
 				Status: http.StatusOK,
@@ -79,7 +82,7 @@ func TestManifest(t *testing.T) {
 			ReqEntry: reqresp.ReqEntry{
 				Name:   "Digest",
 				Method: "GET",
-				Path:   "/v2" + repoPath + "/manifests/" + mDigest.String(),
+				Path:   "/v2/" + repoPath + "/manifests/" + mDigest.String(),
 			},
 			RespEntry: reqresp.RespEntry{
 				Status: http.StatusOK,
@@ -95,7 +98,7 @@ func TestManifest(t *testing.T) {
 			ReqEntry: reqresp.ReqEntry{
 				Name:   "Head",
 				Method: "HEAD",
-				Path:   "/v2" + repoPath + "/manifests/" + headTag,
+				Path:   "/v2/" + repoPath + "/manifests/" + goodTag,
 			},
 			RespEntry: reqresp.RespEntry{
 				Status: http.StatusOK,
@@ -110,7 +113,7 @@ func TestManifest(t *testing.T) {
 			ReqEntry: reqresp.ReqEntry{
 				Name:   "Get nodigest",
 				Method: "GET",
-				Path:   "/v2" + repoPath + "/manifests/" + nodigestTag,
+				Path:   "/v2/" + repoPath + "/manifests/" + nodigestTag,
 			},
 			RespEntry: reqresp.RespEntry{
 				Status: http.StatusOK,
@@ -126,7 +129,7 @@ func TestManifest(t *testing.T) {
 			ReqEntry: reqresp.ReqEntry{
 				Name:   "Head nodigest",
 				Method: "HEAD",
-				Path:   "/v2" + repoPath + "/manifests/" + nodigestTag,
+				Path:   "/v2/" + repoPath + "/manifests/" + nodigestTag,
 			},
 			RespEntry: reqresp.RespEntry{
 				Status: http.StatusOK,
@@ -140,7 +143,7 @@ func TestManifest(t *testing.T) {
 			ReqEntry: reqresp.ReqEntry{
 				Name:   "Get nohead",
 				Method: "GET",
-				Path:   "/v2" + repoPath + "/manifests/" + noheadTag,
+				Path:   "/v2/" + repoPath + "/manifests/" + noheadTag,
 			},
 			RespEntry: reqresp.RespEntry{
 				Status: http.StatusOK,
@@ -152,50 +155,53 @@ func TestManifest(t *testing.T) {
 				Body: mBody,
 			},
 		},
-		{
-			ReqEntry: reqresp.ReqEntry{
-				Name:   "Missing",
-				Method: "GET",
-				Path:   "/v2" + repoPath + "/manifests/" + missingTag,
-			},
-			RespEntry: reqresp.RespEntry{
-				Status: http.StatusNotFound,
-			},
-		},
-		{
-			ReqEntry: reqresp.ReqEntry{
-				Name:   "Missing",
-				Method: "GET",
-				Path:   "/v2" + repoPath + "/manifests/" + missingDigest.String(),
-			},
-			RespEntry: reqresp.RespEntry{
-				Status: http.StatusNotFound,
-			},
-		},
 	}
 	rrs = append(rrs, reqresp.BaseEntries...)
-	// create a server
-	ts := httptest.NewServer(reqresp.NewHandler(t, rrs))
-	defer ts.Close()
+	// create servers
+	boolT := true
+	tsInternal := httptest.NewServer(reqresp.NewHandler(t, rrs))
+	olaregHandler := olareg.New(oConfig.Config{
+		Storage: oConfig.ConfigStorage{
+			StoreType: oConfig.StoreMem,
+			RootDir:   "./testdata",
+		},
+		API: oConfig.ConfigAPI{
+			DeleteEnabled: &boolT,
+		},
+	})
+	tsOlareg := httptest.NewServer(olaregHandler)
+	tsOlaregURL, _ := url.Parse(tsOlareg.URL)
+	tsOlaregHost := tsOlaregURL.Host
+	t.Cleanup(func() {
+		tsInternal.Close()
+		tsOlareg.Close()
+		_ = olaregHandler.Close()
+	})
 	// setup the regclient
-	tsURL, _ := url.Parse(ts.URL)
-	tsHost := tsURL.Host
+	tsInternalURL, _ := url.Parse(tsInternal.URL)
+	tsInternalHost := tsInternalURL.Host
 	rcHosts := []config.Host{
 		{
-			Name:      tsHost,
-			Hostname:  tsHost,
+			Name:      tsOlaregHost,
+			Hostname:  tsOlaregHost,
+			TLS:       config.TLSDisabled,
+			ReqPerSec: 100,
+		},
+		{
+			Name:      "missing." + tsOlaregHost,
+			Hostname:  tsOlaregHost,
 			TLS:       config.TLSDisabled,
 			ReqPerSec: 100,
 		},
 		{
-			Name:      "missing." + tsHost,
-			Hostname:  tsHost,
+			Name:      tsInternalHost,
+			Hostname:  tsInternalHost,
 			TLS:       config.TLSDisabled,
 			ReqPerSec: 100,
 		},
 		{
-			Name:     "nohead." + tsHost,
-			Hostname: tsHost,
+			Name:     "nohead." + tsInternalHost,
+			Hostname: tsInternalHost,
 			TLS:      config.TLSDisabled,
 			APIOpts: map[string]string{
 				"disableHead": "true",
@@ -217,96 +223,164 @@ func TestManifest(t *testing.T) {
 		WithRetryDelay(delayInit, delayMax),
 	)
 	t.Run("Get", func(t *testing.T) {
-		getRef, err := ref.New(tsURL.Host + repoPath + ":" + getTag)
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + goodTag)
 		if err != nil {
-			t.Fatalf("Failed creating getRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
-		mGet, err := rc.ManifestGet(ctx, getRef)
+		m, err := rc.ManifestGet(ctx, r)
 		if err != nil {
 			t.Fatalf("Failed running ManifestGet: %v", err)
 		}
-		if manifest.GetMediaType(mGet) != mediatype.Docker2Manifest {
-			t.Errorf("Unexpected media type: %s", manifest.GetMediaType(mGet))
-		}
-		if mGet.GetDescriptor().Digest != mDigest {
-			t.Errorf("Unexpected digest: %s", mGet.GetDescriptor().Digest.String())
+		if !m.IsSet() {
+			t.Errorf("manifest is not set on a get request")
 		}
 	})
 	t.Run("Head", func(t *testing.T) {
-		headRef, err := ref.New(tsURL.Host + repoPath + ":" + headTag)
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + goodTag)
 		if err != nil {
-			t.Fatalf("Failed creating getRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
-		mHead, err := rc.ManifestHead(ctx, headRef)
+		m, err := rc.ManifestHead(ctx, r)
 		if err != nil {
 			t.Fatalf("Failed running ManifestHead: %v", err)
 		}
-		if manifest.GetMediaType(mHead) != mediatype.Docker2Manifest {
-			t.Errorf("Unexpected media type: %s", manifest.GetMediaType(mHead))
-		}
-		if mHead.GetDescriptor().Digest != mDigest {
-			t.Errorf("Unexpected digest: %s", mHead.GetDescriptor().Digest.String())
+		if m.IsSet() {
+			t.Errorf("manifest is set on a head request")
 		}
 	})
 	t.Run("Head no digest", func(t *testing.T) {
-		headRef, err := ref.New(tsURL.Host + repoPath + ":" + nodigestTag)
+		r, err := ref.New(tsInternalHost + "/" + repoPath + ":" + nodigestTag)
 		if err != nil {
-			t.Fatalf("Failed creating getRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
-		mHead, err := rc.ManifestHead(ctx, headRef, WithManifestRequireDigest())
+		m, err := rc.ManifestHead(ctx, r, WithManifestRequireDigest())
 		if err != nil {
 			t.Fatalf("Failed running ManifestHead: %v", err)
 		}
-		if manifest.GetMediaType(mHead) != mediatype.Docker2Manifest {
-			t.Errorf("Unexpected media type: %s", manifest.GetMediaType(mHead))
+		if manifest.GetMediaType(m) != mediatype.Docker2Manifest {
+			t.Errorf("Unexpected media type: %s", manifest.GetMediaType(m))
 		}
-		if mHead.GetDescriptor().Digest != mDigest {
-			t.Errorf("Unexpected digest: %s", mHead.GetDescriptor().Digest.String())
+		if m.GetDescriptor().Digest != mDigest {
+			t.Errorf("Unexpected digest: %s", m.GetDescriptor().Digest.String())
 		}
 	})
 	t.Run("Head No Head", func(t *testing.T) {
-		noheadRef, err := ref.New("nohead." + tsURL.Host + repoPath + ":" + noheadTag)
+		r, err := ref.New("nohead." + tsInternalHost + "/" + repoPath + ":" + noheadTag)
 		if err != nil {
-			t.Fatalf("Failed creating getRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
-		mNohead, err := rc.ManifestHead(ctx, noheadRef)
+		m, err := rc.ManifestHead(ctx, r)
 		if err == nil {
-			t.Errorf("Unexpected successful head on \"no head\" registry: %v", mNohead)
+			t.Errorf("Unexpected successful head on \"no head\" registry: %v", m)
 		} else if !errors.Is(err, errs.ErrUnsupportedAPI) {
 			t.Errorf("Expected error, expected %v, received %v", errs.ErrUnsupportedAPI, err)
 		}
 	})
 	t.Run("Get No Head", func(t *testing.T) {
-		noheadRef, err := ref.New("nohead." + tsURL.Host + repoPath + ":" + noheadTag)
+		r, err := ref.New("nohead." + tsInternalHost + "/" + repoPath + ":" + noheadTag)
 		if err != nil {
-			t.Fatalf("Failed creating getRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
-		mNohead, err := rc.ManifestGet(ctx, noheadRef)
+		m, err := rc.ManifestGet(ctx, r)
 		if err != nil {
 			t.Fatalf("Failed running ManifestGet: %v", err)
 		}
-		if manifest.GetMediaType(mNohead) != mediatype.Docker2Manifest {
-			t.Errorf("Unexpected media type: %s", manifest.GetMediaType(mNohead))
+		if manifest.GetMediaType(m) != mediatype.Docker2Manifest {
+			t.Errorf("Unexpected media type: %s", manifest.GetMediaType(m))
 		}
-		if mNohead.GetDescriptor().Digest != mDigest {
-			t.Errorf("Unexpected digest: %s", mNohead.GetDescriptor().Digest.String())
+		if m.GetDescriptor().Digest != mDigest {
+			t.Errorf("Unexpected digest: %s", m.GetDescriptor().Digest.String())
 		}
 	})
 	t.Run("Missing", func(t *testing.T) {
-		missingRef, err := ref.New("missing." + tsURL.Host + repoPath + ":" + missingTag)
+		r, err := ref.New("missing." + tsOlaregHost + "/" + repoPath + ":" + missingTag)
 		if err != nil {
-			t.Fatalf("Failed creating missingRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
-		mMissing, err := rc.ManifestGet(ctx, missingRef)
+		m, err := rc.ManifestGet(ctx, r)
 		if err == nil {
-			t.Errorf("Success running ManifestGet on missing ref: %v", mMissing)
+			t.Errorf("Success running ManifestGet on missing ref: %v", m)
 			return
 		}
 	})
+	t.Run("Get Platform", func(t *testing.T) {
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + goodTag)
+		if err != nil {
+			t.Fatalf("Failed creating ref: %v", err)
+		}
+		p, err := platform.Parse("linux/amd64")
+		if err != nil {
+			t.Fatalf("Failed parsing platform: %v", err)
+		}
+		m, err := rc.ManifestGet(ctx, r, WithManifestPlatform(p))
+		if err != nil {
+			t.Fatalf("Failed running ManifestGet: %v", err)
+		}
+		if !m.IsSet() {
+			t.Errorf("manifest is not set on a get request")
+		}
+		if m.IsList() {
+			t.Errorf("returned manifest is an index")
+		}
+	})
+	t.Run("Get Missing Platform", func(t *testing.T) {
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + goodTag)
+		if err != nil {
+			t.Fatalf("Failed creating ref: %v", err)
+		}
+		p, err := platform.Parse("linux/ppc64le")
+		if err != nil {
+			t.Fatalf("Failed parsing platform: %v", err)
+		}
+		_, err = rc.ManifestGet(ctx, r, WithManifestPlatform(p))
+		if err == nil {
+			t.Fatalf("Success running ManifestGet on missing platform")
+		}
+		if !errors.Is(err, errs.ErrNotFound) {
+			t.Errorf("Expected error %v, received %v", errs.ErrNotFound, err)
+		}
+	})
+	t.Run("Head Platform", func(t *testing.T) {
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + goodTag)
+		if err != nil {
+			t.Fatalf("Failed creating ref: %v", err)
+		}
+		p, err := platform.Parse("linux/amd64")
+		if err != nil {
+			t.Fatalf("Failed parsing platform: %v", err)
+		}
+		m, err := rc.ManifestHead(ctx, r, WithManifestPlatform(p))
+		if err != nil {
+			t.Fatalf("Failed running ManifestHead: %v", err)
+		}
+		if m.IsSet() {
+			t.Errorf("manifest is set on a head request")
+		}
+		if m.IsList() {
+			t.Errorf("returned manifest is an index")
+		}
+	})
+	t.Run("Head Missing Platform", func(t *testing.T) {
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + goodTag)
+		if err != nil {
+			t.Fatalf("Failed creating ref: %v", err)
+		}
+		p, err := platform.Parse("linux/ppc64le")
+		if err != nil {
+			t.Fatalf("Failed parsing platform: %v", err)
+		}
+		_, err = rc.ManifestHead(ctx, r, WithManifestPlatform(p))
+		if err == nil {
+			t.Fatalf("Success running ManifestHead on missing platform")
+		}
+		if !errors.Is(err, errs.ErrNotFound) {
+			t.Errorf("Expected error %v, received %v", errs.ErrNotFound, err)
+		}
+	})
 	t.Run("Data", func(t *testing.T) {
-		dataRef, err := ref.New(tsURL.Host + repoPath + ":data")
+		r, err := ref.New(tsInternalHost + "/" + repoPath + ":data")
 		if err != nil {
-			t.Errorf("Failed creating getRef: %v", err)
+			t.Errorf("Failed creating ref: %v", err)
 		}
 		d := descriptor.Descriptor{
 			MediaType: mediatype.Docker2Manifest,
@@ -314,11 +388,11 @@ func TestManifest(t *testing.T) {
 			Digest:    mDigest,
 			Data:      mBody,
 		}
-		mGet, err := rc.ManifestGet(ctx, dataRef, WithManifestDesc(d))
+		m, err := rc.ManifestGet(ctx, r, WithManifestDesc(d))
 		if err != nil {
 			t.Fatalf("failed running ManifestGet: %v", err)
 		}
-		mBodyOut, err := mGet.RawBody()
+		mBodyOut, err := m.RawBody()
 		if err != nil {
 			t.Fatalf("failed running RawBody: %v", err)
 		}
@@ -327,9 +401,9 @@ func TestManifest(t *testing.T) {
 		}
 	})
 	t.Run("Data fallback", func(t *testing.T) {
-		getRef, err := ref.New(tsURL.Host + repoPath + ":" + getTag)
+		r, err := ref.New(tsInternalHost + "/" + repoPath + ":" + goodTag)
 		if err != nil {
-			t.Fatalf("Failed creating getRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
 		d := descriptor.Descriptor{
 			MediaType: mediatype.Docker2Manifest,
@@ -337,16 +411,16 @@ func TestManifest(t *testing.T) {
 			Digest:    mDigest,
 			Data:      []byte("invalid data"),
 		}
-		_, err = rc.ManifestGet(ctx, getRef, WithManifestDesc(d))
+		_, err = rc.ManifestGet(ctx, r, WithManifestDesc(d))
 		if err != nil {
 			t.Errorf("Failed running ManifestGet: %v", err)
 			return
 		}
 	})
 	t.Run("Bad Data and Found Digest", func(t *testing.T) {
-		missingRef, err := ref.New("missing." + tsURL.Host + repoPath + ":" + missingTag)
+		r, err := ref.New(tsInternalHost + "/" + repoPath + ":" + missingTag)
 		if err != nil {
-			t.Fatalf("Failed creating missingRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
 		d := descriptor.Descriptor{
 			MediaType: mediatype.Docker2Manifest,
@@ -354,16 +428,16 @@ func TestManifest(t *testing.T) {
 			Digest:    mDigest,
 			Data:      []byte("invalid data"),
 		}
-		_, err = rc.ManifestGet(ctx, missingRef, WithManifestDesc(d))
+		_, err = rc.ManifestGet(ctx, r, WithManifestDesc(d))
 		if err != nil {
 			t.Errorf("get with descriptor failed, didn't fall back to digest")
 			return
 		}
 	})
 	t.Run("Bad Data and Missing Digest", func(t *testing.T) {
-		missingRef, err := ref.New("missing." + tsURL.Host + repoPath + ":" + missingTag)
+		r, err := ref.New("missing." + tsOlaregHost + "/" + repoPath + ":" + missingTag)
 		if err != nil {
-			t.Fatalf("Failed creating missingRef: %v", err)
+			t.Fatalf("Failed creating ref: %v", err)
 		}
 		d := descriptor.Descriptor{
 			MediaType: mediatype.Docker2Manifest,
@@ -371,7 +445,7 @@ func TestManifest(t *testing.T) {
 			Digest:    missingDigest,
 			Data:      []byte("invalid data"),
 		}
-		_, err = rc.ManifestGet(ctx, missingRef, WithManifestDesc(d))
+		_, err = rc.ManifestGet(ctx, r, WithManifestDesc(d))
 		if err == nil {
 			t.Errorf("Success running ManifestGet on missing ref")
 			return
@@ -391,4 +465,24 @@ func TestManifest(t *testing.T) {
 			t.Errorf("ManifestGet did not respond with invalid ref: %v", err)
 		}
 	})
+	t.Run("Delete", func(t *testing.T) {
+		r, err := ref.New(tsOlaregHost + "/" + repoPath + ":" + deleteTag)
+		if err != nil {
+			t.Fatalf("Failed creating ref: %v", err)
+		}
+		m, err := rc.ManifestGet(ctx, r)
+		if err != nil {
+			t.Fatalf("failed to run get request: %v", err)
+		}
+		r = r.SetDigest(m.GetDescriptor().Digest.String())
+		err = rc.ManifestDelete(ctx, r, WithManifest(m), WithManifestCheckReferrers())
+		if err != nil {
+			t.Errorf("ManifestDelete failed: %v", err)
+		}
+		_, err = rc.ManifestHead(ctx, r)
+		if !errors.Is(err, errs.ErrNotFound) {
+			t.Fatalf("head after delete did not return a non-found: %v", err)
+		}
+
+	})
 }
diff --git a/scheme/reg/referrer_test.go b/scheme/reg/referrer_test.go
index 6b014d6..7a1b04c 100644
--- a/scheme/reg/referrer_test.go
+++ b/scheme/reg/referrer_test.go
@@ -302,6 +302,37 @@ func TestReferrer(t *testing.T) {
 				Body: mlBody,
 			},
 		},
+		{
+			ReqEntry: reqresp.ReqEntry{
+				Name:   "Head manifest",
+				Method: "HEAD",
+				Path:   "/v2" + repoPath + "/manifests/" + mDigest.String(),
+			},
+			RespEntry: reqresp.RespEntry{
+				Status: http.StatusOK,
+				Headers: http.Header{
+					"Content-Length":        {fmt.Sprintf("%d", mLen)},
+					"Content-Type":          []string{mediatype.Docker2Manifest},
+					"Docker-Content-Digest": []string{mDigest.String()},
+				},
+			},
+		},
+		{
+			ReqEntry: reqresp.ReqEntry{
+				Name:   "Get manifest",
+				Method: "GET",
+				Path:   "/v2" + repoPath + "/manifests/" + mDigest.String(),
+			},
+			RespEntry: reqresp.RespEntry{
+				Status: http.StatusOK,
+				Headers: http.Header{
+					"Content-Length":        {fmt.Sprintf("%d", mLen)},
+					"Content-Type":          []string{mediatype.Docker2Manifest},
+					"Docker-Content-Digest": []string{mDigest.String()},
+				},
+				Body: mBody,
+			},
+		},
 		{
 			ReqEntry: reqresp.ReqEntry{
 				Name:    "Get A",