Repository: regclient/regclient
Pull request #490 (2023-07-02 20:50:53)

Add artifact and subject options in index create
<!--

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

This adds the ability to create an index with the `artifactType` and `subject` fields.
<!-- 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

The loop example was updated with the following:

```shell
echo "child" | regctl artifact put --artifact-type application/example.child ocidir://testrepo:child
regctl index create ocidir://testrepo:loop \
  --artifact-type application/example.loop \
  --subject child \
  --ref ocidir://testrepo:child
```
<!-- Include steps that can be taken to verify the change -->

### Changelog text

- Add options to `regctl index create` for `artifactType` and `subject`
<!-- 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/index.go b/cmd/regctl/index.go
index a13f880..6696833 100644
--- a/cmd/regctl/index.go
+++ b/cmd/regctl/index.go
@@ -3,9 +3,9 @@ package main
 import (
 	"context"
 	"fmt"
-	"os"
 	"strings"
 
+	"github.com/opencontainers/go-digest"
 	"github.com/regclient/regclient"
 	"github.com/regclient/regclient/pkg/template"
 	"github.com/regclient/regclient/types"
@@ -59,6 +59,7 @@ var indexDeleteCmd = &cobra.Command{
 
 var indexOpts struct {
 	annotations     []string
+	artifactType    string
 	byDigest        bool
 	descAnnotations []string
 	descPlatform    string
@@ -69,34 +70,37 @@ var indexOpts struct {
 	mediaType       string
 	platforms       []string
 	refs            []string
+	subject         string
 }
 
 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().StringArrayVar(&indexOpts.descAnnotations, "desc-annotation", []string{}, "Annotation to add to descriptors of new entries")
+	indexAddCmd.Flags().StringVar(&indexOpts.descPlatform, "desc-platform", "", "Platform to set in descriptors of new entries")
+	indexAddCmd.Flags().StringArrayVar(&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")
-
-	indexCreateCmd.Flags().StringArrayVarP(&indexOpts.annotations, "annotation", "", []string{}, "Annotation to set on manifest")
-	indexCreateCmd.Flags().BoolVarP(&indexOpts.byDigest, "by-digest", "", false, "Push manifest by digest instead of tag")
-	indexCreateCmd.Flags().StringArrayVarP(&indexOpts.descAnnotations, "desc-annotation", "", []string{}, "Annotation to add to descriptors of new entries")
-	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")
+	indexAddCmd.Flags().StringArrayVar(&indexOpts.refs, "ref", []string{}, "References to add")
+	indexAddCmd.Flags().StringArrayVar(&indexOpts.platforms, "platform", []string{}, "Platforms to include from ref")
+
+	indexCreateCmd.Flags().StringArrayVar(&indexOpts.annotations, "annotation", []string{}, "Annotation to set on manifest")
+	indexCreateCmd.Flags().StringVar(&indexOpts.artifactType, "artifact-type", "", "Include an artifactType value")
+	indexCreateCmd.Flags().BoolVar(&indexOpts.byDigest, "by-digest", false, "Push manifest by digest instead of tag")
+	indexCreateCmd.Flags().StringArrayVar(&indexOpts.descAnnotations, "desc-annotation", []string{}, "Annotation to add to descriptors of new entries")
+	indexCreateCmd.Flags().StringVar(&indexOpts.descPlatform, "desc-platform", "", "Platform to set in descriptors of new entries")
+	indexCreateCmd.Flags().StringArrayVar(&indexOpts.digests, "digest", []string{}, "Digest to include in new index")
+	indexCreateCmd.Flags().StringVar(&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")
+	indexCreateCmd.Flags().StringVar(&indexOpts.subject, "subject", "", "Specify a subject tag or digest (this manifest must already exist in the repo)")
+	indexCreateCmd.Flags().StringArrayVar(&indexOpts.refs, "ref", []string{}, "References to include in new index")
+	indexCreateCmd.Flags().StringArrayVar(&indexOpts.platforms, "platform", []string{}, "Platforms to include from ref")
 	indexCreateCmd.RegisterFlagCompletionFunc("media-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return indexKnownTypes, cobra.ShellCompDirectiveNoFileComp
 	})
 
-	indexDeleteCmd.Flags().StringArrayVarP(&indexOpts.digests, "digest", "", []string{}, "Digest to delete")
-	indexDeleteCmd.Flags().StringArrayVarP(&indexOpts.platforms, "platform", "", []string{}, "Platform to delete")
+	indexDeleteCmd.Flags().StringArrayVar(&indexOpts.digests, "digest", []string{}, "Digest to delete")
+	indexDeleteCmd.Flags().StringArrayVar(&indexOpts.platforms, "platform", []string{}, "Platform to delete")
 
 	indexCmd.AddCommand(indexAddCmd)
 	indexCmd.AddCommand(indexCreateCmd)
@@ -163,7 +167,7 @@ func runIndexAdd(cmd *cobra.Command, args []string) error {
 	if r.Tag == "" && r.Digest != "" && indexOpts.format == "" {
 		indexOpts.format = "{{ printf \"%s\\n\" .Manifest.GetDescriptor.Digest }}"
 	}
-	return template.Writer(os.Stdout, indexOpts.format, result)
+	return template.Writer(cmd.OutOrStdout(), indexOpts.format, result)
 }
 
 func runIndexCreate(cmd *cobra.Command, args []string) error {
@@ -202,14 +206,36 @@ func runIndexCreate(cmd *cobra.Command, args []string) error {
 	}
 	descList = indexDescListRmDup(descList)
 
+	var subj *types.Descriptor
+	if indexOpts.subject != "" && indexOpts.mediaType == types.MediaTypeOCI1ManifestList {
+		rSubj := r
+		rSubj.Tag = ""
+		rSubj.Digest = ""
+		dig, err := digest.Parse(indexOpts.subject)
+		if err == nil {
+			rSubj.Digest = dig.String()
+		} else {
+			rSubj.Tag = indexOpts.subject
+		}
+		mSubj, err := rc.ManifestHead(ctx, rSubj, regclient.WithManifestRequireDigest())
+		if err != nil {
+			return fmt.Errorf("failed to lookup subject %s: %w", rSubj.CommonName(), err)
+		}
+		desc := mSubj.GetDescriptor()
+		desc.Annotations = nil
+		subj = &desc
+	}
+
 	// build the index
 	mOpts := []manifest.Opts{}
 	switch indexOpts.mediaType {
 	case types.MediaTypeOCI1ManifestList:
 		m := v1.Index{
-			Versioned: v1.IndexSchemaVersion,
-			MediaType: types.MediaTypeOCI1ManifestList,
-			Manifests: descList,
+			Versioned:    v1.IndexSchemaVersion,
+			MediaType:    types.MediaTypeOCI1ManifestList,
+			ArtifactType: indexOpts.artifactType,
+			Manifests:    descList,
+			Subject:      subj,
 		}
 		if len(annotations) > 0 {
 			m.Annotations = annotations
@@ -249,7 +275,7 @@ func runIndexCreate(cmd *cobra.Command, args []string) error {
 	if indexOpts.byDigest && indexOpts.format == "" {
 		indexOpts.format = "{{ printf \"%s\\n\" .Manifest.GetDescriptor.Digest }}"
 	}
-	return template.Writer(os.Stdout, indexOpts.format, result)
+	return template.Writer(cmd.OutOrStdout(), indexOpts.format, result)
 }
 
 func runIndexDelete(cmd *cobra.Command, args []string) error {
@@ -335,7 +361,7 @@ func runIndexDelete(cmd *cobra.Command, args []string) error {
 	if r.Tag == "" && r.Digest != "" && indexOpts.format == "" {
 		indexOpts.format = "{{ printf \"%s\\n\" .Manifest.GetDescriptor.Digest }}"
 	}
-	return template.Writer(os.Stdout, indexOpts.format, result)
+	return template.Writer(cmd.OutOrStdout(), indexOpts.format, result)
 }
 
 func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref) ([]types.Descriptor, error) {
@@ -430,7 +456,7 @@ func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref)
 		rDig.Tag = ""
 		rDig.Digest = dig
 
-		mDig, err := getManifest(ctx, rc, rDig)
+		mDig, err := rc.ManifestHead(ctx, rDig, regclient.WithManifestRequireDigest())
 		if err != nil {
 			return nil, err
 		}
@@ -444,8 +470,10 @@ func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref)
 		if err == nil {
 			desc.Platform = plat
 		}
-		if len(descAnnotations) > 0 && desc.Annotations == nil {
+		if len(descAnnotations) > 0 {
 			desc.Annotations = map[string]string{}
+		} else {
+			desc.Annotations = nil
 		}
 		for k, v := range descAnnotations {
 			desc.Annotations[k] = v
@@ -457,6 +485,16 @@ func indexBuildDescList(ctx context.Context, rc *regclient.RegClient, r ref.Ref)
 
 func indexGetPlatform(ctx context.Context, rc *regclient.RegClient, r ref.Ref, m manifest.Manifest) (*platform.Platform, error) {
 	if mi, ok := m.(manifest.Imager); ok {
+		if !m.IsSet() {
+			// fetch the manifest if it wasn't already pulled
+			m, err := rc.ManifestGet(ctx, r)
+			if err != nil {
+				return nil, err
+			}
+			if mi, ok = m.(manifest.Imager); !ok {
+				return nil, nil
+			}
+		}
 		cd, err := mi.GetConfig()
 		if err != nil {
 			return nil, err
@@ -469,15 +507,7 @@ func indexGetPlatform(ctx context.Context, rc *regclient.RegClient, r ref.Ref, m
 		if ociConfig.OS == "" {
 			return nil, nil
 		}
-		plat := platform.Platform{
-			OS:           ociConfig.OS,
-			Architecture: ociConfig.Architecture,
-			OSVersion:    ociConfig.OSVersion,
-			OSFeatures:   ociConfig.OSFeatures,
-			Variant:      ociConfig.Variant,
-			Features:     ociConfig.OSFeatures,
-		}
-		return &plat, nil
+		return &ociConfig.Platform, nil
 	}
 	return nil, nil
 }


Test patch:
diff --git a/cmd/regctl/artifact_test.go b/cmd/regctl/artifact_test.go
index e1f9f57..7a18b52 100644
--- a/cmd/regctl/artifact_test.go
+++ b/cmd/regctl/artifact_test.go
@@ -21,6 +21,7 @@ func TestArtifactPut(t *testing.T) {
 		t.Errorf("failed creating test conf: %v", err)
 		return
 	}
+	saveArtifactOpts := artifactOpts
 
 	tt := []struct {
 		name        string
@@ -74,6 +75,7 @@ func TestArtifactPut(t *testing.T) {
 				rootCmd.SetIn(bytes.NewBuffer(tc.in))
 			}
 			out, err := cobraTest(t, tc.args...)
+			artifactOpts = saveArtifactOpts
 			if tc.expectErr != nil {
 				if err == nil {
 					t.Errorf("did not receive expected error: %v", tc.expectErr)
@@ -97,6 +99,7 @@ func TestArtifactPut(t *testing.T) {
 }
 
 func TestArtifactTree(t *testing.T) {
+	saveArtifactOpts := artifactOpts
 	tt := []struct {
 		name        string
 		args        []string
@@ -151,6 +154,7 @@ func TestArtifactTree(t *testing.T) {
 	for _, tc := range tt {
 		t.Run(tc.name, func(t *testing.T) {
 			out, err := cobraTest(t, tc.args...)
+			artifactOpts = saveArtifactOpts
 			if tc.expectErr != nil {
 				if err == nil {
 					t.Errorf("did not receive expected error: %v", tc.expectErr)
diff --git a/cmd/regctl/blob_test.go b/cmd/regctl/blob_test.go
index 423ea4c..e4ff717 100644
--- a/cmd/regctl/blob_test.go
+++ b/cmd/regctl/blob_test.go
@@ -8,6 +8,8 @@ import (
 // TODO: implement tests
 
 func TestBlob(t *testing.T) {
+	saveBlobOpts := blobOpts
+	saveManifestOpts := manifestOpts
 	repo := "ocidir://../../testdata/testrepo"
 	digBaseA, err := cobraTest(t, "manifest", "get", repo+":b1", "--platform", "linux/amd64", "--format", "{{(index .Layers 0).Digest}}")
 	if err != nil {
@@ -25,10 +27,12 @@ func TestBlob(t *testing.T) {
 	if err != nil {
 		t.Errorf("failed getting layer digest: %v", err)
 	}
+	manifestOpts = saveManifestOpts
 
 	t.Run("Get", func(t *testing.T) {
 		// run a get request
 		out, err := cobraTest(t, "blob", "get", "--format", "{{printPretty .}}", repo, digBaseA)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob get: %v", err)
 		}
@@ -37,6 +41,7 @@ func TestBlob(t *testing.T) {
 		}
 		// run a head request
 		out, err = cobraTest(t, "blob", "head", "--format", "{{printPretty .}}", repo, digBaseA)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob head: %v", err)
 		}
@@ -45,6 +50,7 @@ func TestBlob(t *testing.T) {
 		}
 		// get a file from the blob
 		out, err = cobraTest(t, "blob", "get-file", repo, digBaseA, "base.txt")
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob get-file: %v", err)
 		}
@@ -62,11 +68,13 @@ func TestBlob(t *testing.T) {
 		defer rootCmd.SetIn(origIn)
 		// put a blob
 		dig, err := cobraTest(t, "blob", "put", "--format", "{{println .Digest}}", "ocidir://"+dir)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob copy: %v", err)
 		}
 		// get the blob from the tempdir
 		out, err := cobraTest(t, "blob", "get", "--format", "{{printPretty .}}", "ocidir://"+dir, dig)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob get: %v", err)
 		}
@@ -79,11 +87,13 @@ func TestBlob(t *testing.T) {
 		dir := t.TempDir()
 		// copy the blob to the tempdir
 		_, err := cobraTest(t, "blob", "copy", repo, "ocidir://"+dir, digBaseA)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob copy: %v", err)
 		}
 		// get the blob from the tempdir
 		out, err := cobraTest(t, "blob", "get", "--format", "{{printPretty .}}", "ocidir://"+dir, digBaseA)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to blob get: %v", err)
 		}
@@ -95,6 +105,7 @@ func TestBlob(t *testing.T) {
 	t.Run("Diff", func(t *testing.T) {
 		// diff the layers between two images
 		out, err := cobraTest(t, "blob", "diff-layer", repo, digBaseA, repo, digBaseB)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to diff layers: %v", err)
 		}
@@ -103,6 +114,7 @@ func TestBlob(t *testing.T) {
 		}
 		// diff the config between two images
 		out, err = cobraTest(t, "blob", "diff-config", repo, digConf1, repo, digConf3)
+		blobOpts = saveBlobOpts
 		if err != nil {
 			t.Errorf("failed to diff config: %v", err)
 		}
diff --git a/cmd/regctl/config_test.go b/cmd/regctl/config_test.go
index 35b4185..9805ee4 100644
--- a/cmd/regctl/config_test.go
+++ b/cmd/regctl/config_test.go
@@ -14,9 +14,11 @@ func TestConfig(t *testing.T) {
 		defer os.Setenv(ConfigEnv, origEnv)
 	}
 	os.Setenv(ConfigEnv, filepath.Join(tempDir, "config.json"))
+	saveConfigOpts := configOpts
 
 	// test empty config
 	out, err := cobraTest(t, "config", "get", "--format", "{{ json . }}")
+	configOpts = saveConfigOpts
 	if err != nil {
 		t.Errorf("failed to run config get: %v", err)
 	}
@@ -26,23 +28,10 @@ func TestConfig(t *testing.T) {
 
 	// set options
 	testLimit := "420000000"
-	out, err = cobraTest(t, "config", "set", "--blob-limit", testLimit)
+	out, err = cobraTest(t, "config", "set", "--blob-limit", testLimit, "--docker-cert=false", "--docker-cred=false")
+	configOpts = saveConfigOpts
 	if err != nil {
-		t.Errorf("failed to set blob-limit: %v", err)
-	}
-	if out != "" {
-		t.Errorf("unexpected output from set: %s", out)
-	}
-	out, err = cobraTest(t, "config", "set", "--docker-cert=false")
-	if err != nil {
-		t.Errorf("failed to set docker-cert: %v", err)
-	}
-	if out != "" {
-		t.Errorf("unexpected output from set: %s", out)
-	}
-	out, err = cobraTest(t, "config", "set", "--docker-cred=false")
-	if err != nil {
-		t.Errorf("failed to set docker-cred: %v", err)
+		t.Errorf("failed to set config: %v", err)
 	}
 	if out != "" {
 		t.Errorf("unexpected output from set: %s", out)
@@ -50,6 +39,7 @@ func TestConfig(t *testing.T) {
 
 	// get changes
 	out, err = cobraTest(t, "config", "get", "--format", "{{ .BlobLimit }}")
+	configOpts = saveConfigOpts
 	if err != nil {
 		t.Errorf("failed to run config get on blob-limit: %v", err)
 	}
@@ -57,6 +47,7 @@ func TestConfig(t *testing.T) {
 		t.Errorf("unexpected output for blob-limit, expected: %s, received: %s", testLimit, out)
 	}
 	out, err = cobraTest(t, "config", "get", "--format", "{{ .IncDockerCert }}")
+	configOpts = saveConfigOpts
 	if err != nil {
 		t.Errorf("failed to run config get on docker-cert: %v", err)
 	}
@@ -64,6 +55,7 @@ func TestConfig(t *testing.T) {
 		t.Errorf("unexpected output for docker-cert, expected: false, received: %s", out)
 	}
 	out, err = cobraTest(t, "config", "get", "--format", "{{ .IncDockerCred }}")
+	configOpts = saveConfigOpts
 	if err != nil {
 		t.Errorf("failed to run config get on docker-cred: %v", err)
 	}
@@ -72,30 +64,18 @@ func TestConfig(t *testing.T) {
 	}
 
 	// reset back to zero values
-	out, err = cobraTest(t, "config", "set", "--blob-limit", "0")
+	out, err = cobraTest(t, "config", "set", "--blob-limit", "0", "--docker-cert", "--docker-cred")
+	configOpts = saveConfigOpts
 	if err != nil {
 		t.Errorf("failed to set blob-limit: %v", err)
 	}
 	if out != "" {
 		t.Errorf("unexpected output from set: %s", out)
 	}
-	out, err = cobraTest(t, "config", "set", "--docker-cert")
-	if err != nil {
-		t.Errorf("failed to set docker-cert: %v", err)
-	}
-	if out != "" {
-		t.Errorf("unexpected output from set: %s", out)
-	}
-	out, err = cobraTest(t, "config", "set", "--docker-cred")
-	if err != nil {
-		t.Errorf("failed to set docker-cred: %v", err)
-	}
-	if out != "" {
-		t.Errorf("unexpected output from set: %s", out)
-	}
 
 	// verify config is empty
 	out, err = cobraTest(t, "config", "get", "--format", "{{ json . }}")
+	configOpts = saveConfigOpts
 	if err != nil {
 		t.Errorf("failed to run config get: %v", err)
 	}
diff --git a/cmd/regctl/index_test.go b/cmd/regctl/index_test.go
index 28a4e00..fc38b4b 100644
--- a/cmd/regctl/index_test.go
+++ b/cmd/regctl/index_test.go
@@ -7,50 +7,62 @@ import (
 
 func TestIndex(t *testing.T) {
 	tmpDir := t.TempDir()
-	tmpRef := fmt.Sprintf("ocidir://%s/repo:latest", tmpDir)
+	latestRef := fmt.Sprintf("ocidir://%s/repo:latest", tmpDir)
+	artifactRef := fmt.Sprintf("ocidir://%s/repo:latest", tmpDir)
 	srcRef := "ocidir://../../testdata/testrepo:v2"
+	saveIndexOpts := indexOpts
+	saveManifestOpts := manifestOpts
+	saveArtifactOpts := artifactOpts
 
 	// create index with 2 platforms from test repo
-	out, err := cobraTest(t, "index", "create", "--ref", srcRef, "--platform", "linux/amd64", "--platform", "linux/arm/v7", tmpRef)
+	out, err := cobraTest(t, "index", "create", "--ref", srcRef, "--platform", "linux/amd64", "--platform", "linux/arm/v7", latestRef)
+	indexOpts = saveIndexOpts
 	if err != nil {
 		t.Errorf("failed to run index create: %v", err)
 		return
 	}
 	if out != "" {
-		t.Errorf("unexpected output: %v", out)
+		t.Errorf("unexpected output: %s", out)
 	}
 	// verify content
-	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/amd64", tmpRef)
+	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/amd64", latestRef)
+	manifestOpts = saveManifestOpts
 	if err != nil {
 		t.Errorf("failed to get linux/amd64 entry: %v", err)
 	}
-	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm/v7", tmpRef)
+	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm/v7", latestRef)
+	manifestOpts = saveManifestOpts
 	if err != nil {
 		t.Errorf("failed to get linux/arm/v7 entry: %v", err)
 	}
-	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm64", tmpRef)
+	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm64", latestRef)
+	manifestOpts = saveManifestOpts
 	if err == nil {
 		t.Errorf("found linux/arm64 entry")
 	}
-	_, err = cobraTest(t, "artifact", "get", "--subject", tmpRef, "--platform", "linux/amd/v7", "--filter-artifact-type", "application/example.arms")
+	_, err = cobraTest(t, "artifact", "get", "--subject", latestRef, "--platform", "linux/amd/v7", "--filter-artifact-type", "application/example.arms")
+	artifactOpts = saveArtifactOpts
 	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)
+	out, err = cobraTest(t, "index", "add", "--ref", srcRef, "--platform", "linux/arm64", "--referrers", "--digest-tags", latestRef)
+	indexOpts = saveIndexOpts
 	if err != nil {
 		t.Errorf("failed to run index add: %v", err)
 		return
 	}
 	if out != "" {
-		t.Errorf("unexpected output: %v", out)
+		t.Errorf("unexpected output: %s", out)
 	}
-	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm64", tmpRef)
+	_, err = cobraTest(t, "manifest", "get", "--platform", "linux/arm64", latestRef)
+	manifestOpts = saveManifestOpts
 	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")
+	out, err = cobraTest(t, "artifact", "get", "--subject", latestRef, "--platform", "linux/arm64", "--filter-artifact-type", "application/example.arms")
+	artifactOpts = saveArtifactOpts
 	if err != nil {
 		t.Errorf("artifact not found: %v", err)
 	}
@@ -58,4 +70,24 @@ func TestIndex(t *testing.T) {
 	if out != artifact64Out {
 		t.Errorf("unexpected artifact content, expected: %s, received: %s", artifact64Out, out)
 	}
+
+	// create an index that itself is an artifact
+	testArtifactType := "application/example.test"
+	out, err = cobraTest(t, "index", "create", artifactRef, "--subject", "latest", "--artifact-type", testArtifactType, "--ref", srcRef)
+	indexOpts = saveIndexOpts
+	if err != nil {
+		t.Errorf("failed to run index create for artifact: %v", err)
+		return
+	}
+	if out != "" {
+		t.Errorf("unexpected output: %s", out)
+	}
+	out, err = cobraTest(t, "manifest", "get", artifactRef, "--format", "{{.ArtifactType}}")
+	manifestOpts = saveManifestOpts
+	if err != nil {
+		t.Errorf("failed to get artifact type from manifest: %v", err)
+	}
+	if out != testArtifactType {
+		t.Errorf("manifest artifact type, expected %s, received %s", testArtifactType, out)
+	}
 }
diff --git a/cmd/regctl/manifest_test.go b/cmd/regctl/manifest_test.go
index de3cad2..3ee2340 100644
--- a/cmd/regctl/manifest_test.go
+++ b/cmd/regctl/manifest_test.go
@@ -10,6 +10,7 @@ import (
 )
 
 func TestManifestHead(t *testing.T) {
+	saveManifestOpts := manifestOpts
 	tt := []struct {
 		name        string
 		args        []string
@@ -53,6 +54,7 @@ func TestManifestHead(t *testing.T) {
 	for _, tc := range tt {
 		t.Run(tc.name, func(t *testing.T) {
 			out, err := cobraTest(t, tc.args...)
+			manifestOpts = saveManifestOpts
 			if tc.expectErr != nil {
 				if err == nil {
 					t.Errorf("did not receive expected error: %v", tc.expectErr)
diff --git a/cmd/regctl/tag_test.go b/cmd/regctl/tag_test.go
index e2753de..64dc3d1 100644
--- a/cmd/regctl/tag_test.go
+++ b/cmd/regctl/tag_test.go
@@ -11,6 +11,7 @@ import (
 )
 
 func TestTagList(t *testing.T) {
+	saveTagOpts := tagOpts
 	tt := []struct {
 		name        string
 		args        []string
@@ -63,6 +64,7 @@ func TestTagList(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			tagOpts = optInit
 			out, err := cobraTest(t, tc.args...)
+			tagOpts = saveTagOpts
 			if tc.expectErr != nil {
 				if err == nil {
 					t.Errorf("did not receive expected error: %v", tc.expectErr)
diff --git a/testdata/build-testrepo.sh b/testdata/build-testrepo.sh
index 9823820..0938c16 100755
--- a/testdata/build-testrepo.sh
+++ b/testdata/build-testrepo.sh
@@ -114,45 +114,10 @@ regctl image copy ocidir://testrepo:v3 "ocidir://testrepo:sha256-${v1_dig}.${v3_
 
 # create a looping index that references a child in both the manifests and subject
 echo "child" | regctl artifact put --artifact-type application/example.child ocidir://testrepo:child
-dig_tag="$(regctl manifest head --format 'sha256-{{.GetDescriptor.Digest.Hex}}' ocidir://testrepo:child)"
-mt_child="$(regctl manifest head --format '{{.GetDescriptor.MediaType}}' ocidir://testrepo:child)"
-size_child="$(regctl manifest head --format '{{.GetDescriptor.Size}}' ocidir://testrepo:child)"
-digest_child="$(regctl manifest head --format '{{.GetDescriptor.Digest}}' ocidir://testrepo:child)"
-mt_loop="$(regctl manifest head --format '{{.GetDescriptor.MediaType}}' ocidir://testrepo:loop)"
-size_loop="$(regctl manifest head --format '{{.GetDescriptor.Size}}' ocidir://testrepo:loop)"
-digest_loop="$(regctl manifest head --format '{{.GetDescriptor.Digest}}' ocidir://testrepo:loop)"
-regctl manifest put ocidir://testrepo:loop <<EOF
-{
-  "schemaVersion": 2,
-  "mediaType": "application/vnd.oci.image.index.v1+json",
-  "manifests": [
-    {
-      "mediaType": "${mt_child}",
-      "size": ${size_child},
-      "digest": "${digest_child}"
-    }
-  ],
-  "subject": {
-    "mediaType": "${mt_child}",
-    "size": ${size_child},
-    "digest": "${digest_child}"
-  }
-}
-EOF
-regctl manifest put ocidir://testrepo:${dig_tag} <<EOF
-{
-  "schemaVersion": 2,
-  "mediaType": "application/vnd.oci.image.index.v1+json",
-  "manifests": [
-    {
-      "mediaType": "${mt_loop}",
-      "size": ${size_loop},
-      "digest": "${digest_loop}",
-      "artifactType": "application/example.child"
-    }
-  ]
-}
-EOF
+regctl index create ocidir://testrepo:loop \
+  --artifact-type application/example.loop \
+  --subject child \
+  --ref ocidir://testrepo:child
 
 # create an artifact with a digest tag to itself
 echo mirror | regctl artifact put \
diff --git a/testdata/testrepo/blobs/sha256/0d4ea07d06d42ad1cb756a5d9e71f62288e426b22a230b224410164181998c4c b/testdata/testrepo/blobs/sha256/0d4ea07d06d42ad1cb756a5d9e71f62288e426b22a230b224410164181998c4c
new file mode 100644
index 0000000..9413d9c
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/0d4ea07d06d42ad1cb756a5d9e71f62288e426b22a230b224410164181998c4c
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":556,"digest":"sha256:5283ed7b662424a7f9edc47f8e0e266d47f8ce997da51949d454b30eaafb5251","platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":556,"digest":"sha256:a7f0466d930515f984dc334bf786a569973119a3afaa2d4290f2268c62a19b12","platform":{"architecture":"arm64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":394,"digest":"sha256:fcb04a33e5e9ecd98d30b05a784de6fe65a26e84316a995c55a12dfbb02132e8","annotations":{"vnd.docker.reference.digest":"sha256:5283ed7b662424a7f9edc47f8e0e266d47f8ce997da51949d454b30eaafb5251","vnd.docker.reference.type":"builder"},"platform":{"architecture":"unknown","os":"unknown"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":394,"digest":"sha256:fcb04a33e5e9ecd98d30b05a784de6fe65a26e84316a995c55a12dfbb02132e8","annotations":{"vnd.docker.reference.digest":"sha256:a7f0466d930515f984dc334bf786a569973119a3afaa2d4290f2268c62a19b12","vnd.docker.reference.type":"builder"},"platform":{"architecture":"unknown","os":"unknown"}}],"annotations":{"org.example.version":"v1"}}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/32f0c6dae73ed9712577cb7f859102434e0689773c38236bd33bf2a7ff39bab8 b/testdata/testrepo/blobs/sha256/32f0c6dae73ed9712577cb7f859102434e0689773c38236bd33bf2a7ff39bab8
deleted file mode 100644
index 7378cee..0000000
--- a/testdata/testrepo/blobs/sha256/32f0c6dae73ed9712577cb7f859102434e0689773c38236bd33bf2a7ff39bab8
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "schemaVersion": 2,
-  "mediaType": "application/vnd.oci.image.index.v1+json",
-  "manifests": [
-    {
-      "mediaType": "application/vnd.oci.image.index.v1+json",
-      "size": 479,
-      "digest": "sha256:36b87a47db0b8826ac569e53563fe3d600c1304a82c69a1043ffe324d713b592",
-      "artifactType": "application/example.child"
-    }
-  ]
-}
diff --git a/testdata/testrepo/blobs/sha256/36b87a47db0b8826ac569e53563fe3d600c1304a82c69a1043ffe324d713b592 b/testdata/testrepo/blobs/sha256/36b87a47db0b8826ac569e53563fe3d600c1304a82c69a1043ffe324d713b592
deleted file mode 100644
index 395e85b..0000000
--- a/testdata/testrepo/blobs/sha256/36b87a47db0b8826ac569e53563fe3d600c1304a82c69a1043ffe324d713b592
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "schemaVersion": 2,
-  "mediaType": "application/vnd.oci.image.index.v1+json",
-  "manifests": [
-    {
-      "mediaType": "application/vnd.oci.image.manifest.v1+json",
-      "size": 414,
-      "digest": "sha256:507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b"
-    }
-  ],
-  "subject": {
-    "mediaType": "application/vnd.oci.image.manifest.v1+json",
-    "size": 414,
-    "digest": "sha256:507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b"
-  }
-}
diff --git a/testdata/testrepo/blobs/sha256/4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40 b/testdata/testrepo/blobs/sha256/4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40
deleted file mode 100644
index bb1c43f..0000000
--- a/testdata/testrepo/blobs/sha256/4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40
+++ /dev/null
@@ -1,1 +0,0 @@
-{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":556,"digest":"sha256:5283ed7b662424a7f9edc47f8e0e266d47f8ce997da51949d454b30eaafb5251","platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":556,"digest":"sha256:a7f0466d930515f984dc334bf786a569973119a3afaa2d4290f2268c62a19b12","platform":{"architecture":"arm64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":394,"digest":"sha256:fcb04a33e5e9ecd98d30b05a784de6fe65a26e84316a995c55a12dfbb02132e8","annotations":{"org.opencontainers.image.ref.name":"a-docker","vnd.docker.reference.digest":"sha256:5283ed7b662424a7f9edc47f8e0e266d47f8ce997da51949d454b30eaafb5251","vnd.docker.reference.type":"builder"},"platform":{"architecture":"unknown","os":"unknown"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":394,"digest":"sha256:fcb04a33e5e9ecd98d30b05a784de6fe65a26e84316a995c55a12dfbb02132e8","annotations":{"org.opencontainers.image.ref.name":"a-docker","vnd.docker.reference.digest":"sha256:a7f0466d930515f984dc334bf786a569973119a3afaa2d4290f2268c62a19b12","vnd.docker.reference.type":"builder"},"platform":{"architecture":"unknown","os":"unknown"}}],"annotations":{"org.example.version":"v1"}}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/c65509594eca7ea7a70208fb49a3bf2f74150ca4ec6ce66dcc92f3ff381290ff b/testdata/testrepo/blobs/sha256/c65509594eca7ea7a70208fb49a3bf2f74150ca4ec6ce66dcc92f3ff381290ff
new file mode 100644
index 0000000..0397659
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/c65509594eca7ea7a70208fb49a3bf2f74150ca4ec6ce66dcc92f3ff381290ff
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","artifactType":"application/example.loop","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":414,"digest":"sha256:507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b"}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":414,"digest":"sha256:507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b"}}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/e986d78d171eceec4b7233606ecbfa5a2c4dd9c64bbe50609df282815a37675c b/testdata/testrepo/blobs/sha256/e986d78d171eceec4b7233606ecbfa5a2c4dd9c64bbe50609df282815a37675c
new file mode 100644
index 0000000..299d4b1
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/e986d78d171eceec4b7233606ecbfa5a2c4dd9c64bbe50609df282815a37675c
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","size":445,"digest":"sha256:c65509594eca7ea7a70208fb49a3bf2f74150ca4ec6ce66dcc92f3ff381290ff","artifactType":"application/example.loop"}]}
\ No newline at end of file
diff --git a/testdata/testrepo/index.json b/testdata/testrepo/index.json
index 87575fb..9d9a188 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:7ba20869dedc0090a7074b8574eb1f096f705f6f30082d08a3160b86848089d0","annotations":{"org.opencontainers.image.ref.name":"b1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:15a3231581118fd8ce33fd581cbae6958e990cbc99a6a7a1f50c63cce95d4ec5","annotations":{"org.opencontainers.image.ref.name":"b2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:98c0bb8411acb5dafef5223e13e8f553190ab3d1994062b5785788733cdbfca3","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:4eef5c8459d36adab1f354e332af99915cb75dfe4b91e19cdd53ccfb33aa0418","annotations":{"org.opencontainers.image.ref.name":"v2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:d0b099bf44f68230c262a2a2397d4caf9c74da80374b4674b8e41bc4c8579275","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":583,"digest":"sha256:54dd4e105916f7a95f7911c0328ce753d7bd08034538933c042cfae3f63bc573","annotations":{"org.opencontainers.image.ref.name":"a1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":482,"digest":"sha256:7df92b086d7ec46a64ae0ff5652d371a7cf524d7076a925130807f464355c0d4","annotations":{"org.opencontainers.image.ref.name":"sha256-4eef5c8459d36adab1f354e332af99915cb75dfe4b91e19cdd53ccfb33aa0418"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":588,"digest":"sha256:559c971b91100f44900af38bb1391a7b2637afe674087ec68e5e6a92abcce57b","annotations":{"org.opencontainers.image.ref.name":"a2"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":576,"digest":"sha256:05d037f7d6b8fa8438c2d5f0cabd089358a56c057143527d8f60139e6799e171"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:876d3d65d4df98cdee450fb417721d1b568a4c56af1851126c18a5e22f2a1830","annotations":{"org.opencontainers.image.ref.name":"sha256-ef05efc8cfd478ac3140fce1297bd6b72dc5f5f1df31bfce690aa903a2c20310"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":576,"digest":"sha256:045226b7a97d5cc8a7c585f6816815ed662b0833734c6cbfa901137079e0997c"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:acf88dd4fa9307038a4099ff4ccd13ae77fe4d52a5f3d5edcb5a77142627ab52","annotations":{"org.opencontainers.image.ref.name":"sha256-4638b261096d1b87665ca154599c4ac1664a16ee1bc12b302f8aae24e336e071"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":576,"digest":"sha256:49fa909609a7f6c7589fb7cfa966e2cb04b682c5b2ca1da1de943a21e26bfe70"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:cf7f350eaff86a56089a93aff69bfa26aeac72d086bb5f0490bd46179260a77f","annotations":{"org.opencontainers.image.ref.name":"sha256-56ed84e8d4f983411218751bcb5d544e14e28d210101a12c63a1be03108de01b"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:d0b099bf44f68230c262a2a2397d4caf9c74da80374b4674b8e41bc4c8579275","annotations":{"org.opencontainers.image.ref.name":"sha256-4a88e72dd0e4245e6ecfbc6faae751eeeff82861f9ef39634bea07d77dbb1f40.d0b099bf44f68230.meta"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":414,"digest":"sha256:507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b","annotations":{"org.opencontainers.image.ref.name":"child"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":479,"digest":"sha256:36b87a47db0b8826ac569e53563fe3d600c1304a82c69a1043ffe324d713b592","annotations":{"org.opencontainers.image.ref.name":"loop"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":339,"digest":"sha256:32f0c6dae73ed9712577cb7f859102434e0689773c38236bd33bf2a7ff39bab8","annotations":{"org.opencontainers.image.ref.name":"sha256-507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":415,"digest":"sha256:b86ce43f2d9bf91965bd7d310744d1590ad38f6243cf656677b002f696c30482","annotations":{"org.opencontainers.image.ref.name":"mirror"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":415,"digest":"sha256:b86ce43f2d9bf91965bd7d310744d1590ad38f6243cf656677b002f696c30482","annotations":{"org.opencontainers.image.ref.name":"sha256-b86ce43f2d9bf91965bd7d310744d1590ad38f6243cf656677b002f696c30482"}}]}
\ 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:7ba20869dedc0090a7074b8574eb1f096f705f6f30082d08a3160b86848089d0","annotations":{"org.opencontainers.image.ref.name":"b1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:15a3231581118fd8ce33fd581cbae6958e990cbc99a6a7a1f50c63cce95d4ec5","annotations":{"org.opencontainers.image.ref.name":"b2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":964,"digest":"sha256:98c0bb8411acb5dafef5223e13e8f553190ab3d1994062b5785788733cdbfca3","annotations":{"org.opencontainers.image.ref.name":"b3"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1262,"digest":"sha256:0d4ea07d06d42ad1cb756a5d9e71f62288e426b22a230b224410164181998c4c","annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":922,"digest":"sha256:4eef5c8459d36adab1f354e332af99915cb75dfe4b91e19cdd53ccfb33aa0418","annotations":{"org.opencontainers.image.ref.name":"v2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:d0b099bf44f68230c262a2a2397d4caf9c74da80374b4674b8e41bc4c8579275","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":583,"digest":"sha256:54dd4e105916f7a95f7911c0328ce753d7bd08034538933c042cfae3f63bc573","annotations":{"org.opencontainers.image.ref.name":"a1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":482,"digest":"sha256:7df92b086d7ec46a64ae0ff5652d371a7cf524d7076a925130807f464355c0d4","annotations":{"org.opencontainers.image.ref.name":"sha256-4eef5c8459d36adab1f354e332af99915cb75dfe4b91e19cdd53ccfb33aa0418"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":588,"digest":"sha256:559c971b91100f44900af38bb1391a7b2637afe674087ec68e5e6a92abcce57b","annotations":{"org.opencontainers.image.ref.name":"a2"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":576,"digest":"sha256:05d037f7d6b8fa8438c2d5f0cabd089358a56c057143527d8f60139e6799e171"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:876d3d65d4df98cdee450fb417721d1b568a4c56af1851126c18a5e22f2a1830","annotations":{"org.opencontainers.image.ref.name":"sha256-ef05efc8cfd478ac3140fce1297bd6b72dc5f5f1df31bfce690aa903a2c20310"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":576,"digest":"sha256:045226b7a97d5cc8a7c585f6816815ed662b0833734c6cbfa901137079e0997c"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:acf88dd4fa9307038a4099ff4ccd13ae77fe4d52a5f3d5edcb5a77142627ab52","annotations":{"org.opencontainers.image.ref.name":"sha256-4638b261096d1b87665ca154599c4ac1664a16ee1bc12b302f8aae24e336e071"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":576,"digest":"sha256:49fa909609a7f6c7589fb7cfa966e2cb04b682c5b2ca1da1de943a21e26bfe70"},{"mediaType":"application/vnd.oci.image.index.v1+json","size":282,"digest":"sha256:cf7f350eaff86a56089a93aff69bfa26aeac72d086bb5f0490bd46179260a77f","annotations":{"org.opencontainers.image.ref.name":"sha256-56ed84e8d4f983411218751bcb5d544e14e28d210101a12c63a1be03108de01b"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":1141,"digest":"sha256:d0b099bf44f68230c262a2a2397d4caf9c74da80374b4674b8e41bc4c8579275","annotations":{"org.opencontainers.image.ref.name":"sha256-0d4ea07d06d42ad1cb756a5d9e71f62288e426b22a230b224410164181998c4c.d0b099bf44f68230.meta"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":414,"digest":"sha256:507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b","annotations":{"org.opencontainers.image.ref.name":"child"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":445,"digest":"sha256:c65509594eca7ea7a70208fb49a3bf2f74150ca4ec6ce66dcc92f3ff381290ff","annotations":{"org.opencontainers.image.ref.name":"loop"}},{"mediaType":"application/vnd.oci.image.index.v1+json","size":279,"digest":"sha256:e986d78d171eceec4b7233606ecbfa5a2c4dd9c64bbe50609df282815a37675c","annotations":{"org.opencontainers.image.ref.name":"sha256-507bd066139b92463a30828d26fb9b9e10b2bb6b0e22a875b8062798c8bf9e5b"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":415,"digest":"sha256:b86ce43f2d9bf91965bd7d310744d1590ad38f6243cf656677b002f696c30482","annotations":{"org.opencontainers.image.ref.name":"mirror"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","size":415,"digest":"sha256:b86ce43f2d9bf91965bd7d310744d1590ad38f6243cf656677b002f696c30482","annotations":{"org.opencontainers.image.ref.name":"sha256-b86ce43f2d9bf91965bd7d310744d1590ad38f6243cf656677b002f696c30482"}}]}
\ No newline at end of file