From 46f0d373ce7bf532387d02adfe77e79b4f04d8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 10 Sep 2024 20:31:53 +0200 Subject: [PATCH] Add oci/layout.List MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new API allows for listing all manifests in an OCI layout's index. Signed-off-by: Miloslav Trmač Signed-off-by: Valentin Rothberg --- oci/layout/reader.go | 52 ++++++++++++++++++++++++++++++ oci/layout/reader_test.go | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 oci/layout/reader.go create mode 100644 oci/layout/reader_test.go diff --git a/oci/layout/reader.go b/oci/layout/reader.go new file mode 100644 index 0000000000..078ef68615 --- /dev/null +++ b/oci/layout/reader.go @@ -0,0 +1,52 @@ +package layout + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/containers/image/v5/types" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// This file is named reader.go for consistency with other transports +// handling of “image containers”, but we don’t actually need a stateful reader object. + +// ListResult wraps the image reference and the manifest for loading +type ListResult struct { + Reference types.ImageReference + ManifestDescriptor imgspecv1.Descriptor +} + +// List returns a slice of manifests included in the archive +func List(dir string) ([]ListResult, error) { + var res []ListResult + + indexJSON, err := os.ReadFile(filepath.Join(dir, imgspecv1.ImageIndexFile)) + if err != nil { + return nil, err + } + var index imgspecv1.Index + if err := json.Unmarshal(indexJSON, &index); err != nil { + return nil, err + } + + for manifestIndex, md := range index.Manifests { + refName := md.Annotations[imgspecv1.AnnotationRefName] + index := -1 + if refName == "" { + index = manifestIndex + } + ref, err := newReference(dir, refName, index) + if err != nil { + return nil, fmt.Errorf("error creating image reference: %w", err) + } + reference := ListResult{ + Reference: ref, + ManifestDescriptor: md, + } + res = append(res, reference) + } + return res, nil +} diff --git a/oci/layout/reader_test.go b/oci/layout/reader_test.go new file mode 100644 index 0000000000..ebfe916d50 --- /dev/null +++ b/oci/layout/reader_test.go @@ -0,0 +1,68 @@ +package layout + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + + for _, test := range []struct { + path string + num int + digests []string + names map[int]string + }{ + { + path: "fixtures/two_images_manifest", + num: 2, + digests: []string{ + "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", + }, + names: map[int]string{0: "", 1: ""}, + }, + { + path: "fixtures/manifest", + num: 1, + digests: []string{ + "sha256:84afb6189c4d69f2d040c5f1dc4e0a16fed9b539ce9cfb4ac2526ae4e0576cc0", + }, + names: map[int]string{0: "v0.1.1"}, + }, + { + path: "fixtures/name_lookups", + num: 4, + digests: []string{ + "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + }, + names: map[int]string{0: "a", 1: "b", 2: "invalid-mime", 3: "invalid-mime"}, + }, + } { + results, err := List(test.path) + require.NoError(t, err) + require.NotNil(t, results) + require.Len(t, results, test.num) + for i, res := range results { + ociRef, ok := res.Reference.(ociReference) + require.True(t, ok) + require.Equal(t, test.digests[i], res.ManifestDescriptor.Digest.String()) + require.Equal(t, test.names[i], ociRef.image) + if test.names[i] != "" { + require.True(t, strings.HasSuffix(res.Reference.StringWithinTransport(), test.names[i])) + } + _, err := ParseReference(fmt.Sprintf("%s:@%d", test.path, i)) + require.NoError(t, err) + } + } + + results, err := List("fixtures/i_do_not_exist") + require.Error(t, err) + require.Nil(t, results) +}