Repository: regclient/regclient
Pull request #382 (2023-03-04 16:09:47)

Add referrers option to index create/add
<!--

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

Adds digest-tags and referrers option to `regctl index create` and `regctl index add` so additional metadata and signatures can be included.
<!-- 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

```
regctl artifact create --ref $src \
  --platform linux/amd64 --platform linux/arm64 \
  --digest-tags --referrers $tgt
regctl artifact tree $tgt
```

<!-- Include steps that can be taken to verify the change -->

### Changelog text

Add `--referrers` and `--digest-tags` options to `regctl index add/create`
<!-- 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



Patch:
diff --git a/cmd/regctl/blob.go b/cmd/regctl/blob.go
index 459da8e..820c7ea 100644
--- a/cmd/regctl/blob.go
+++ b/cmd/regctl/blob.go
@@ -187,10 +187,10 @@ func runBlobDiffConfig(cmd *cobra.Command, args []string) error {
 
 	cDiff := diff.Diff(strings.Split(string(c1Json), "\n"), strings.Split(string(c2Json), "\n"), diffOpts...)
 
-	_, err = fmt.Fprintln(os.Stdout, strings.Join(cDiff, "\n"))
+	_, err = fmt.Fprintln(cmd.OutOrStdout(), strings.Join(cDiff, "\n"))
 	return err
 	// TODO: support templating
-	// return template.Writer(os.Stdout, blobOpts.format, cDiff)
+	// return template.Writer(cmd.OutOrStdout(), blobOpts.format, cDiff)
 }
 
 func runBlobDiffLayer(cmd *cobra.Command, args []string) error {
@@ -267,7 +267,7 @@ func runBlobDiffLayer(cmd *cobra.Command, args []string) error {
 
 	// run diff and output result
 	lDiff := diff.Diff(rep1, rep2, diffOpts...)
-	_, err = fmt.Fprintln(os.Stdout, strings.Join(lDiff, "\n"))
+	_, err = fmt.Fprintln(cmd.OutOrStdout(), strings.Join(lDiff, "\n"))
 	return err
 }
 
@@ -303,16 +303,16 @@ func runBlobGet(cmd *cobra.Command, args []string) error {
 	case "raw":
 		blobOpts.formatGet = "{{ range $key,$vals := .RawHeaders}}{{range $val := $vals}}{{printf \"%s: %s\\n\" $key $val }}{{end}}{{end}}{{printf \"\\n%s\" .RawBody}}"
 	case "rawBody", "raw-body", "body":
-		_, err = io.Copy(os.Stdout, blob)
+		_, err = io.Copy(cmd.OutOrStdout(), blob)
 		return err
 	case "rawHeaders", "raw-headers", "headers":
 		blobOpts.formatGet = "{{ range $key,$vals := .RawHeaders}}{{range $val := $vals}}{{printf \"%s: %s\\n\" $key $val }}{{end}}{{end}}"
 	case "{{printPretty .}}":
-		_, err = io.Copy(os.Stdout, blob)
+		_, err = io.Copy(cmd.OutOrStdout(), blob)
 		return err
 	}
 
-	return template.Writer(os.Stdout, blobOpts.formatGet, blob)
+	return template.Writer(cmd.OutOrStdout(), blobOpts.formatGet, blob)
 }
 
 func runBlobGetFile(cmd *cobra.Command, args []string) error {
@@ -357,11 +357,11 @@ func runBlobGetFile(cmd *cobra.Command, args []string) error {
 			Header: th,
 			Reader: rdr,
 		}
-		return template.Writer(os.Stdout, blobOpts.formatFile, data)
+		return template.Writer(cmd.OutOrStdout(), blobOpts.formatFile, data)
 	}
 	var w io.Writer
 	if len(args) < 4 {
-		w = os.Stdout
+		w = cmd.OutOrStdout()
 	} else {
 		w, err = os.Create(args[3])
 		if err != nil {
@@ -403,7 +403,7 @@ func runBlobHead(cmd *cobra.Command, args []string) error {
 		blobOpts.formatHead = "{{ range $key,$vals := .RawHeaders}}{{range $val := $vals}}{{printf \"%s: %s\\n\" $key $val }}{{end}}{{end}}"
 	}
 
-	return template.Writer(os.Stdout, blobOpts.formatHead, blob)
+	return template.Writer(cmd.OutOrStdout(), blobOpts.formatHead, blob)
 }
 
 func runBlobPut(cmd *cobra.Command, args []string) error {
@@ -438,7 +438,7 @@ func runBlobPut(cmd *cobra.Command, args []string) error {
 		Size:   dOut.Size,
 	}
 
-	return template.Writer(os.Stdout, blobOpts.formatPut, result)
+	return template.Writer(cmd.OutOrStdout(), blobOpts.formatPut, result)
 }
 
 func blobReportLayer(tr *tar.Reader) ([]string, error) {
diff --git a/cmd/regctl/image.go b/cmd/regctl/image.go
index fc75d44..683b1fa 100644
--- a/cmd/regctl/image.go
+++ b/cmd/regctl/image.go
@@ -631,7 +631,7 @@ func runImageExport(cmd *cobra.Command, args []string) error {
 			return err
 		}
 	} else {
-		w = os.Stdout
+		w = cmd.OutOrStdout()
 	}
 	rc := newRegClient()
 	defer rc.Close(ctx, r)
@@ -751,11 +751,11 @@ func runImageGetFile(cmd *cobra.Command, args []string) error {
 				Header: th,
 				Reader: rdr,
 			}
-			return template.Writer(os.Stdout, imageOpts.formatFile, data)
+			return template.Writer(cmd.OutOrStdout(), imageOpts.formatFile, data)
 		}
 		var w io.Writer
 		if len(args) < 3 {
-			w = os.Stdout
+			w = cmd.OutOrStdout()
 		} else {
 			w, err = os.Create(args[2])
 			if err != nil {
@@ -839,7 +839,7 @@ func runImageInspect(cmd *cobra.Command, args []string) error {
 	case "rawHeaders", "raw-headers", "headers":
 		imageOpts.format = "{{ range $key,$vals := .RawHeaders}}{{range $val := $vals}}{{printf \"%s: %s\\n\" $key $val }}{{end}}{{end}}"
 	}
-	return template.Writer(os.Stdout, imageOpts.format, blobConfig)
+	return template.Writer(cmd.OutOrStdout(), imageOpts.format, blobConfig)
 }
 
 func runImageMod(cmd *cobra.Command, args []string) error {
@@ -911,7 +911,7 @@ func runImageRateLimit(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	return template.Writer(os.Stdout, imageOpts.format, manifest.GetRateLimit(m))
+	return template.Writer(cmd.OutOrStdout(), imageOpts.format, manifest.GetRateLimit(m))
 }
 
 type modFlagFunc struct {
diff --git a/cmd/regctl/index.go b/cmd/regctl/index.go
index 1d45aaf..a13f880 100644
--- a/cmd/regctl/index.go
+++ b/cmd/regctl/index.go
@@ -64,6 +64,8 @@ var indexOpts struct {
 	descPlatform    string
 	digests         []string
 	format          string
+	incDigestTags   bool
+	incReferrers    bool
 	mediaType       string
 	platforms       []string
 	refs            []string
@@ -73,6 +75,8 @@ func init() {
 	indexAddCmd.Flags().StringArrayVarP(&indexOpts.descAnnotations, "desc-annotation", "", []string{}, "Annotation to add to descriptors of new entries")
 	indexAddCmd.Flags().StringVarP(&indexOpts.descPlatform, "desc-platform", "", "", "Platform to set in descriptors of new entries")
 	indexAddCmd.Flags().StringArrayVarP(&indexOpts.digests, "digest", "", []string{}, "Digest to add")
+	indexAddCmd.Flags().BoolVar(&indexOpts.incDigestTags, "digest-tags", false, "Include digest tags")
+	indexAddCmd.Flags().BoolVar(&indexOpts.incReferrers, "referrers", false, "Include referrers")
 	indexAddCmd.Flags().StringArrayVarP(&indexOpts.refs, "ref", "", []string{}, "References to add")
 	indexAddCmd.Flags().StringArrayVarP(&indexOpts.platforms, "platform", "", []string{}, "Platforms to include from ref")
 
@@ -82,6 +86,8 @@ func init() {
 	indexCreateCmd.Flags().StringVarP(&indexOpts.descPlatform, "desc-platform", "", "", "Platform to set in descriptors of new entries")
 	indexCreateCmd.Flags().StringArrayVarP(&indexOpts.digests, "digest", "", []string{}, "Digest to include in new index")
 	indexCreateCmd.Flags().StringVarP(&indexOpts.format, "format", "", "", "Format output with go template syntax")
+	indexCreateCmd.Flags().BoolVar(&indexOpts.incDigestTags, "digest-tags", false, "Include digest tags")
+	indexCreateCmd.Flags().BoolVar(&indexOpts.incReferrers, "referrers", false, "Include referrers")
 	indexCreateCmd.Flags().StringVarP(&indexOpts.mediaType, "media-type", "m", types.MediaTypeOCI1ManifestList, "Media-type for manifest list or OCI Index")
 	indexCreateCmd.Flags().StringArrayVarP(&indexOpts.refs, "ref", "", []string{}, "References to include in new index")
 	indexCreateCmd.Flags().StringArrayVarP(&indexOpts.platforms, "platform", "", []string{}, "Platforms to include from ref")
@@ -333,7 +339,15 @@ func runIndexDelete(cmd *cobra.Command, args []string) error {
 }
 
 func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref) ([]types.Descriptor, error) {
-	// TODO: set image copy opts here, support copying referrers and digest tags
+	imgCopyOpts := []regclient.ImageOpts{
+		regclient.ImageWithChild(),
+	}
+	if indexOpts.incDigestTags {
+		imgCopyOpts = append(imgCopyOpts, regclient.ImageWithDigestTags())
+	}
+	if indexOpts.incReferrers {
+		imgCopyOpts = append(imgCopyOpts, regclient.ImageWithReferrers())
+	}
 
 	descAnnotations := map[string]string{}
 	for _, a := range indexOpts.descAnnotations {
@@ -372,7 +386,7 @@ func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref)
 			tgtRef := r
 			tgtRef.Tag = ""
 			tgtRef.Digest = desc.Digest.String()
-			err = rc.ImageCopy(ctx, srcRef, tgtRef, regclient.ImageWithChild())
+			err = rc.ImageCopy(ctx, srcRef, tgtRef, imgCopyOpts...)
 			if err != nil {
 				return nil, err
 			}
@@ -399,7 +413,7 @@ func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref)
 					tgtRef := r
 					tgtRef.Tag = ""
 					tgtRef.Digest = d.Digest.String()
-					err = rc.ImageCopy(ctx, dRef, tgtRef, regclient.ImageWithChild())
+					err = rc.ImageCopy(ctx, dRef, tgtRef, imgCopyOpts...)
 					if err != nil {
 						return nil, err
 					}
diff --git a/cmd/regctl/repo.go b/cmd/regctl/repo.go
index 1abaf07..5cf9980 100644
--- a/cmd/regctl/repo.go
+++ b/cmd/regctl/repo.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"os"
 	"strings"
 
 	"github.com/regclient/regclient/pkg/template"
@@ -79,5 +78,5 @@ func runRepoLs(cmd *cobra.Command, args []string) error {
 	case "rawHeaders", "raw-headers", "headers":
 		repoOpts.format = "{{ range $key,$vals := .RawHeaders}}{{range $val := $vals}}{{printf \"%s: %s\\n\" $key $val }}{{end}}{{end}}"
 	}
-	return template.Writer(os.Stdout, repoOpts.format, rl)
+	return template.Writer(cmd.OutOrStdout(), repoOpts.format, rl)
 }
diff --git a/cmd/regctl/root.go b/cmd/regctl/root.go
index 67eca17..770ec26 100644
--- a/cmd/regctl/root.go
+++ b/cmd/regctl/root.go
@@ -85,7 +85,7 @@ func rootPreRun(cmd *cobra.Command, args []string) error {
 
 func runVersion(cmd *cobra.Command, args []string) error {
 	info := version.GetInfo()
-	return template.Writer(os.Stdout, rootOpts.format, info)
+	return template.Writer(cmd.OutOrStdout(), rootOpts.format, info)
 }
 
 func newRegClient() *regclient.RegClient {
diff --git a/cmd/regctl/tag.go b/cmd/regctl/tag.go
index 5ebb8ac..85b768b 100644
--- a/cmd/regctl/tag.go
+++ b/cmd/regctl/tag.go
@@ -1,8 +1,6 @@
 package main
 
 import (
-	"os"
-
 	"github.com/regclient/regclient/pkg/template"
 	"github.com/regclient/regclient/scheme"
 	"github.com/regclient/regclient/types/ref"
@@ -105,5 +103,5 @@ func runTagLs(cmd *cobra.Command, args []string) error {
 	case "rawHeaders", "raw-headers", "headers":
 		tagOpts.format = "{{ range $key,$vals := .RawHeaders}}{{range $val := $vals}}{{printf \"%s: %s\\n\" $key $val }}{{end}}{{end}}"
 	}
-	return template.Writer(os.Stdout, tagOpts.format, tl)
+	return template.Writer(cmd.OutOrStdout(), tagOpts.format, tl)
 }


Test patch:
diff --git a/cmd/regctl/index_test.go b/cmd/regctl/index_test.go
index 4aabb9f..28a4e00 100644
--- a/cmd/regctl/index_test.go
+++ b/cmd/regctl/index_test.go
@@ -5,10 +5,10 @@ import (
 	"testing"
 )
 
-func TestIndexCreate(t *testing.T) {
+func TestIndex(t *testing.T) {
 	tmpDir := t.TempDir()
 	tmpRef := fmt.Sprintf("ocidir://%s/repo:latest", tmpDir)
-	srcRef := "ocidir://../../testdata/testrepo:v3"
+	srcRef := "ocidir://../../testdata/testrepo:v2"
 
 	// create index with 2 platforms from test repo
 	out, err := cobraTest(t, "index", "create", "--ref", srcRef, "--platform", "linux/amd64", "--platform", "linux/arm/v7", tmpRef)
@@ -30,6 +30,32 @@ func TestIndexCreate(t *testing.T) {
 	}
 	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm64", tmpRef)
 	if err == nil {
-		t.Errorf("found linux/arm64 entry: %v", err)
+		t.Errorf("found linux/arm64 entry")
+	}
+	_, err = cobraTest(t, "artifact", "get", "--subject", tmpRef, "--platform", "linux/amd/v7", "--filter-artifact-type", "application/example.arms")
+	if err == nil {
+		t.Errorf("found referrers that were not copied")
+	}
+
+	// add artifact with referrers
+	out, err = cobraTest(t, "index", "add", "--ref", srcRef, "--platform", "linux/arm64", "--referrers", "--digest-tags", tmpRef)
+	if err != nil {
+		t.Errorf("failed to run index add: %v", err)
+		return
+	}
+	if out != "" {
+		t.Errorf("unexpected output: %v", out)
+	}
+	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm64", tmpRef)
+	if err != nil {
+		t.Errorf("failed to get linux/arm64 entry: %v", err)
+	}
+	out, err = cobraTest(t, "artifact", "get", "--subject", tmpRef, "--platform", "linux/arm64", "--filter-artifact-type", "application/example.arms")
+	if err != nil {
+		t.Errorf("artifact not found: %v", err)
+	}
+	artifact64Out := "64 arms"
+	if out != artifact64Out {
+		t.Errorf("unexpected artifact content, expected: %s, received: %s", artifact64Out, out)
 	}
 }
diff --git a/testdata/build-testrepo.sh b/testdata/build-testrepo.sh
index 795b112..d7d217e 100755
--- a/testdata/build-testrepo.sh
+++ b/testdata/build-testrepo.sh
@@ -92,6 +92,18 @@ echo eggs | regctl artifact put \
 echo signed | regctl artifact put \
   --artifact-type application/example.signature -m application/example.signature.text \
   --subject ocidir://testrepo:v2 ocidir://testrepo:a2
+echo no arms | regctl artifact put \
+  --artifact-type application/example.arms -m application/example.arms \
+  --subject ocidir://testrepo:v2 --platform linux/amd64
+echo no arms | regctl artifact put \
+  --artifact-type application/example.arms -m application/example.arms \
+  --subject ocidir://testrepo:v2 --platform linux/amd64
+echo 7 arms | regctl artifact put \
+  --artifact-type application/example.arms -m application/example.arms \
+  --subject ocidir://testrepo:v2 --platform linux/arm/v7
+echo 64 arms | regctl artifact put \
+  --artifact-type application/example.arms -m application/example.arms \
+  --subject ocidir://testrepo:v2 --platform linux/arm64
 
 # create a digest tag from v3 pointing to v1
 v1_dig="$(regctl image digest ocidir://testrepo:v1)"
diff --git a/testdata/testrepo/blobs/sha256/30d87ac83139c4a1d853180cbb407e72baea5383e8113d7bef99ef35feb3c5c1 b/testdata/testrepo/blobs/sha256/30d87ac83139c4a1d853180cbb407e72baea5383e8113d7bef99ef35feb3c5c1
new file mode 100644
index 0000000..4eadda1
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/30d87ac83139c4a1d853180cbb407e72baea5383e8113d7bef99ef35feb3c5c1
@@ -0,0 +1,1 @@
+7 arms
diff --git a/testdata/testrepo/blobs/sha256/32e85d5810eaa7fbec506d7ce5613d9aca0732b04b49a832764bb817022477ea b/testdata/testrepo/blobs/sha256/32e85d5810eaa7fbec506d7ce5613d9aca0732b04b49a832764bb817022477ea
new file mode 100644
index 0000000..6597b49
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/32e85d5810eaa7fbec506d7ce5613d9aca0732b04b49a832764bb817022477ea
@@ -0,0 +1,1 @@
+64 arms
diff --git a/testdata/testrepo/blobs/sha256/3c5862a104233f94c9b37ff0fbeba5adf26a9d1f6118fe6ee4f9b9e33a2e7a31 b/testdata/testrepo/blobs/sha256/3c5862a104233f94c9b37ff0fbeba5adf26a9d1f6118fe6ee4f9b9e33a2e7a31
new file mode 100644
index 0000000..754bef2
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/3c5862a104233f94c9b37ff0fbeba5adf26a9d1f6118fe6ee4f9b9e33a2e7a31
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/example.arms","size":2,"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"},"layers":[{"mediaType":"application/example.arms","size":8,"digest":"sha256:32e85d5810eaa7fbec506d7ce5613d9aca0732b04b49a832764bb817022477ea"}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":710,"digest":"sha256:56ed84e8d4f983411218751bcb5d544e14e28d210101a12c63a1be03108de01b"}}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/435bde2f7bea8501968fe3a9717b2d43eb7cb366a75ce2ca31fea0d7052c059c b/testdata/testrepo/blobs/sha256/435bde2f7bea8501968fe3a9717b2d43eb7cb366a75ce2ca31fea0d7052c059c
new file mode 100644
index 0000000..dd7623d
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/435bde2f7bea8501968fe3a9717b2d43eb7cb366a75ce2ca31fea0d7052c059c
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":525,"digest":"sha256:6d8412d04fda16957bba600832e840ff144cb0f2c70bb14b8799d33fbb15e6a3","artifactType":"application/example.arms"}]}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/468aaf700a290fd1f9d7f2236354c8d9635e4b5ff9c08b8ece722c3b20a653d0 b/testdata/testrepo/blobs/sha256/468aaf700a290fd1f9d7f2236354c8d9635e4b5ff9c08b8ece722c3b20a653d0
new file mode 100644
index 0000000..8e57e24
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/468aaf700a290fd1f9d7f2236354c8d9635e4b5ff9c08b8ece722c3b20a653d0
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":525,"digest":"sha256:7c759eba792dfa6a355a7e944e995add9e50de2c6401edf8d36d9b74ca1b9bde","artifactType":"application/example.arms"}]}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/6d8412d04fda16957bba600832e840ff144cb0f2c70bb14b8799d33fbb15e6a3 b/testdata/testrepo/blobs/sha256/6d8412d04fda16957bba600832e840ff144cb0f2c70bb14b8799d33fbb15e6a3
new file mode 100644
index 0000000..7fdccad
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/6d8412d04fda16957bba600832e840ff144cb0f2c70bb14b8799d33fbb15e6a3
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/example.arms","size":2,"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"},"layers":[{"mediaType":"application/example.arms","size":7,"digest":"sha256:30d87ac83139c4a1d853180cbb407e72baea5383e8113d7bef99ef35feb3c5c1"}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":710,"digest":"sha256:5da622c67d8d1b2138493a79ec39250e784db584e2561bf0ab78c1e20b7962cf"}}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/7c759eba792dfa6a355a7e944e995add9e50de2c6401edf8d36d9b74ca1b9bde b/testdata/testrepo/blobs/sha256/7c759eba792dfa6a355a7e944e995add9e50de2c6401edf8d36d9b74ca1b9bde
new file mode 100644
index 0000000..bb2bee1
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/7c759eba792dfa6a355a7e944e995add9e50de2c6401edf8d36d9b74ca1b9bde
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/example.arms","size":2,"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"},"layers":[{"mediaType":"application/example.arms","size":8,"digest":"sha256:ba8916eda90c32a3d2ae88b881bff496dcb02637801567def81586e0298e57a2"}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":710,"digest":"sha256:ef05efc8cfd478ac3140fce1297bd6b72dc5f5f1df31bfce690aa903a2c20310"}}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/9858e0889fb2a74c261dc732c2aec004aa3020b15b92d68c80cd6225b90b5de2 b/testdata/testrepo/blobs/sha256/9858e0889fb2a74c261dc732c2aec004aa3020b15b92d68c80cd6225b90b5de2
new file mode 100644
index 0000000..e88a7b4
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/9858e0889fb2a74c261dc732c2aec004aa3020b15b92d68c80cd6225b90b5de2
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":525,"digest":"sha256:3c5862a104233f94c9b37ff0fbeba5adf26a9d1f6118fe6ee4f9b9e33a2e7a31","artifactType":"application/example.arms"}]}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/ba8916eda90c32a3d2ae88b881bff496dcb02637801567def81586e0298e57a2 b/testdata/testrepo/blobs/sha256/ba8916eda90c32a3d2ae88b881bff496dcb02637801567def81586e0298e57a2
new file mode 100644
index 0000000..2beadaa
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/ba8916eda90c32a3d2ae88b881bff496dcb02637801567def81586e0298e57a2
@@ -0,0 +1,1 @@
+no arms
diff --git a/testdata/testrepo/index.json b/testdata/testrepo/index.json
index d4f4f7d..3b71e7a 100644
--- a/testdata/testrepo/index.json
+++ b/testdata/testrepo/index.json
@@ -1,1 +1,1 @@
-{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:f91e00c1c151c75243a87073297efc68b8fe83a3dd82142ef6fb9f51dddb6a37","annotations":{"org.opencontainers.image.ref.name":"b1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:0a71ca431251e73ab58c6bbfa3d3343b9c0d3334aa6554052ee886711b15c93e","annotations":{"org.opencontainers.image.ref.name":"b2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:84078061572beea3aa7e180fe60684e931f30236690bd27e24692f58d521ec4e","annotations":{"org.opencontainers.image.ref.name":"b3"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1356,"digest":"sha256:4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40","annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":922,"digest":"sha256:adab55c36c56f4054a64972a431e38e407d0060ce90888a2470d67598042f7c8","annotations":{"org.opencontainers.image.ref.name":"v2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:2e024d7fe67394cef7f0df9c303f288c7420d1fb27f7657398e897b7eb2ee1d8","annotations":{"org.opencontainers.image.ref.name":"v3"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":394,"digest":"sha256:fcb04a33e5e9ecd98d30b05a784de6fe65a26e84316a995c55a12dfbb02132e8","annotations":{"org.opencontainers.image.ref.name":"a-docker"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":532,"digest":"sha256:fd90fc9bbcdaace19655b8983e3d4efa946332f8c1f1aac40ac5bead09bdf9c9","annotations":{"org.opencontainers.image.ref.name":"a1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":482,"digest":"sha256:a39a515fb72925723bb37fb46a7ff893444cfb22defa65646d5e15cf006fcbf3","annotations":{"org.opencontainers.image.ref.name":"sha256-adab55c36c56f4054a64972a431e38e407d0060ce90888a2470d67598042f7c8"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":537,"digest":"sha256:b97ab0b611c4224b58c6b701a566b031b7a8fb7b644487668d31797013372cc4","annotations":{"org.opencontainers.image.ref.name":"a2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:2e024d7fe67394cef7f0df9c303f288c7420d1fb27f7657398e897b7eb2ee1d8","annotations":{"org.opencontainers.image.ref.name":"sha256-4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40.2e024d7fe67394ce.meta"}}]}
\ No newline at end of file
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:f91e00c1c151c75243a87073297efc68b8fe83a3dd82142ef6fb9f51dddb6a37","annotations":{"org.opencontainers.image.ref.name":"b1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:0a71ca431251e73ab58c6bbfa3d3343b9c0d3334aa6554052ee886711b15c93e","annotations":{"org.opencontainers.image.ref.name":"b2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:84078061572beea3aa7e180fe60684e931f30236690bd27e24692f58d521ec4e","annotations":{"org.opencontainers.image.ref.name":"b3"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1356,"digest":"sha256:4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40","annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":922,"digest":"sha256:adab55c36c56f4054a64972a431e38e407d0060ce90888a2470d67598042f7c8","annotations":{"org.opencontainers.image.ref.name":"v2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:2e024d7fe67394cef7f0df9c303f288c7420d1fb27f7657398e897b7eb2ee1d8","annotations":{"org.opencontainers.image.ref.name":"v3"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":394,"digest":"sha256:fcb04a33e5e9ecd98d30b05a784de6fe65a26e84316a995c55a12dfbb02132e8","annotations":{"org.opencontainers.image.ref.name":"a-docker"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":532,"digest":"sha256:fd90fc9bbcdaace19655b8983e3d4efa946332f8c1f1aac40ac5bead09bdf9c9","annotations":{"org.opencontainers.image.ref.name":"a1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":482,"digest":"sha256:a39a515fb72925723bb37fb46a7ff893444cfb22defa65646d5e15cf006fcbf3","annotations":{"org.opencontainers.image.ref.name":"sha256-adab55c36c56f4054a64972a431e38e407d0060ce90888a2470d67598042f7c8"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":537,"digest":"sha256:b97ab0b611c4224b58c6b701a566b031b7a8fb7b644487668d31797013372cc4","annotations":{"org.opencontainers.image.ref.name":"a2"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":525,"digest":"sha256:7c759eba792dfa6a355a7e944e995add9e50de2c6401edf8d36d9b74ca1b9bde"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:468aaf700a290fd1f9d7f2236354c8d9635e4b5ff9c08b8ece722c3b20a653d0","annotations":{"org.opencontainers.image.ref.name":"sha256-ef05efc8cfd478ac3140fce1297bd6b72dc5f5f1df31bfce690aa903a2c20310"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":525,"digest":"sha256:6d8412d04fda16957bba600832e840ff144cb0f2c70bb14b8799d33fbb15e6a3"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:435bde2f7bea8501968fe3a9717b2d43eb7cb366a75ce2ca31fea0d7052c059c","annotations":{"org.opencontainers.image.ref.name":"sha256-5da622c67d8d1b2138493a79ec39250e784db584e2561bf0ab78c1e20b7962cf"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":525,"digest":"sha256:3c5862a104233f94c9b37ff0fbeba5adf26a9d1f6118fe6ee4f9b9e33a2e7a31"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:9858e0889fb2a74c261dc732c2aec004aa3020b15b92d68c80cd6225b90b5de2","annotations":{"org.opencontainers.image.ref.name":"sha256-56ed84e8d4f983411218751bcb5d544e14e28d210101a12c63a1be03108de01b"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:2e024d7fe67394cef7f0df9c303f288c7420d1fb27f7657398e897b7eb2ee1d8","annotations":{"org.opencontainers.image.ref.name":"sha256-4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40.2e024d7fe67394ce.meta"}}]}
\ No newline at end of file