Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: expose descriptor for image/index #27

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/sif/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ func (im *image) RawManifest() ([]byte, error) {
return im.rawManifest, nil
}

// Descriptor returns the original descriptor from an index manifest. See partial.Descriptor.
func (im *image) Descriptor() (*v1.Descriptor, error) {
return im.desc, nil
}

var errLayerNotFoundInImage = errors.New("layer not found in image")

// LayerByDigest returns a Layer for interacting with a particular layer of the image, looking it
Expand Down
73 changes: 53 additions & 20 deletions pkg/sif/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,79 @@
package sif_test

import (
"reflect"
"testing"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/validate"
"github.com/sylabs/oci-tools/pkg/sif"
)

func TestFile_Image(t *testing.T) {
// imageIndexFromPath returns an ImageIndex for the test to use, populated from the OCI Image
// Layout with the specified path in the corpus.
func imageIndexFromPath(t *testing.T, path string) v1.ImageIndex {
t.Helper()

ii, err := sif.ImageIndexFromFileImage(fileImageFromPath(t, path))
if err != nil {
t.Fatal(err)
}

return ii
}

func Test_imageIndex_Image(t *testing.T) {
imageDigest := v1.Hash{
Algorithm: "sha256",
Hex: "432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338",
}
imageDescriptor := &v1.Descriptor{
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
Size: 525,
Digest: imageDigest,
Platform: &v1.Platform{
Architecture: "arm64",
OS: "linux",
Variant: "v8",
},
}

tests := []struct {
name string
path string
hash v1.Hash
name string
ii v1.ImageIndex
hash v1.Hash
wantErr bool
wantDescriptor *v1.Descriptor
}{
{
name: "DockerManifest",
path: corpus.SIF(t, "hello-world-docker-v2-manifest"),
hash: v1.Hash{
Algorithm: "sha256",
Hex: "432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338",
},
name: "DockerManifest",
ii: imageIndexFromPath(t, "hello-world-docker-v2-manifest"),
hash: imageDigest,
wantDescriptor: imageDescriptor,
},
{
name: "DockerManifestList",
path: corpus.SIF(t, "hello-world-docker-v2-manifest-list"),
hash: v1.Hash{
Algorithm: "sha256",
Hex: "432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338",
},
name: "DockerManifestList",
ii: imageIndexFromPath(t, "hello-world-docker-v2-manifest-list"),
hash: imageDigest,
wantDescriptor: imageDescriptor,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ii := imageIndexFromPath(t, tt.path)

img, err := ii.Image(tt.hash)
img, err := tt.ii.Image(tt.hash)
if err != nil {
t.Fatal(err)
}

if err := validate.Image(img); err != nil {
t.Fatal(err)
t.Error(err)
}

if d, err := partial.Descriptor(img); err != nil {
t.Error(err)
} else if got, want := d, tt.wantDescriptor; !reflect.DeepEqual(got, want) {
t.Errorf("got descriptor %+v, want %+v", got, want)
}
})
}
Expand Down
33 changes: 24 additions & 9 deletions pkg/sif/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package sif

import (
"bytes"
"encoding/json"
"errors"
"fmt"
Expand All @@ -26,7 +27,7 @@ func ImageIndexFromFileImage(fi *sif.FileImage) (v1.ImageIndex, error) {

type imageIndex struct {
f *fileImage
mediaType types.MediaType
desc *v1.Descriptor
rawManifest []byte
}

Expand All @@ -44,26 +45,35 @@ func (f *fileImage) ImageIndex() (v1.ImageIndex, error) {
return nil, err
}

digest, size, err := v1.SHA256(bytes.NewReader(b))
if err != nil {
return nil, err
}

return &imageIndex{
f: f,
mediaType: types.OCIImageIndex,
f: f,
desc: &v1.Descriptor{
MediaType: types.OCIImageIndex,
Size: size,
Digest: digest,
},
rawManifest: b,
}, nil
}

// MediaType of this image's manifest.
func (ix *imageIndex) MediaType() (types.MediaType, error) {
return ix.mediaType, nil
return ix.desc.MediaType, nil
}

// Digest returns the sha256 of this index's manifest.
func (ix *imageIndex) Digest() (v1.Hash, error) {
return partial.Digest(ix)
return ix.desc.Digest, nil
}

// Size returns the size of the manifest.
func (ix *imageIndex) Size() (int64, error) {
return partial.Size(ix)
return ix.desc.Size, nil
}

// IndexManifest returns this image index's manifest object.
Expand All @@ -78,6 +88,11 @@ func (ix *imageIndex) RawManifest() ([]byte, error) {
return ix.rawManifest, nil
}

// Descriptor returns the original descriptor from an index manifest. See partial.Descriptor.
func (ix *imageIndex) Descriptor() (*v1.Descriptor, error) {
return ix.desc, nil
}

var errUnexpectedMediaType = errors.New("unexpected media type")

// Image returns a v1.Image that this ImageIndex references.
Expand All @@ -96,12 +111,12 @@ func (ix *imageIndex) Image(h v1.Hash) (v1.Image, error) {
return nil, err
}

img := &image{
img := image{
f: ix.f,
desc: desc,
rawManifest: b,
}
return partial.CompressedToImage(img)
return partial.CompressedToImage(&img)
}

// ImageIndex returns a v1.ImageIndex that this ImageIndex references.
Expand All @@ -122,8 +137,8 @@ func (ix *imageIndex) ImageIndex(h v1.Hash) (v1.ImageIndex, error) {

return &imageIndex{
f: ix.f,
desc: desc,
rawManifest: b,
mediaType: desc.MediaType,
}, nil
}

Expand Down
59 changes: 44 additions & 15 deletions pkg/sif/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package sif_test

import (
"reflect"
"testing"

v1 "github.com/google/go-containerregistry/pkg/v1"
Expand All @@ -13,43 +14,71 @@ import (
ssif "github.com/sylabs/sif/v2/pkg/sif"
)

func imageIndexFromPath(t *testing.T, path string) v1.ImageIndex {
type withDescriptor interface {
Descriptor() (*v1.Descriptor, error)
}

// fileImageFromPath returns a temporary FileImage for the test to use, populated from the OCI
// Image Layout with the specified path in the corpus. The FileImage is automatically unloaded when
// the test and all its subtests complete.
func fileImageFromPath(t *testing.T, path string) *ssif.FileImage {
t.Helper()

f, err := ssif.LoadContainerFromPath(path)
f, err := ssif.LoadContainerFromPath(corpus.SIF(t, path))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = f.UnloadContainer() })

ii, err := sif.ImageIndexFromFileImage(f)
if err != nil {
t.Fatal(err)
}

return ii
return f
}

func TestFile_ImageIndex(t *testing.T) {
func TestImageIndexFromFileImage(t *testing.T) {
tests := []struct {
name string
path string
name string
f *ssif.FileImage
wantDescriptor *v1.Descriptor
}{
{
name: "DockerManifest",
path: corpus.SIF(t, "hello-world-docker-v2-manifest"),
f: fileImageFromPath(t, "hello-world-docker-v2-manifest"),
wantDescriptor: &v1.Descriptor{
MediaType: "application/vnd.oci.image.index.v1+json",
Size: 314,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "a2ca8d2eb29d4b32cabd3f2ca67c14c8ae178b93c3000da3ec63faca49a688e4",
},
},
},
{
name: "DockerManifestList",
path: corpus.SIF(t, "hello-world-docker-v2-manifest-list"),
f: fileImageFromPath(t, "hello-world-docker-v2-manifest-list"),
wantDescriptor: &v1.Descriptor{
MediaType: "application/vnd.oci.image.index.v1+json",
Size: 2069,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "00e1ee7c898a2c393ea2fe7680938f8dcbe55e51fbf08032cf37326a677f92ed",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ii := imageIndexFromPath(t, tt.path)
ii, err := sif.ImageIndexFromFileImage(tt.f)
if err != nil {
t.Fatal(err)
}

if err := validate.Index(ii); err != nil {
t.Fatal(err)
t.Error(err)
}

if d, err := ii.(withDescriptor).Descriptor(); err != nil {
t.Error(err)
} else if got, want := d, tt.wantDescriptor; !reflect.DeepEqual(got, want) {
t.Errorf("got descriptor %+v, want %+v", got, want)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sif/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (l *layer) MediaType() (types.MediaType, error) {
return l.desc.MediaType, nil
}

// Descriptor implements partial.withDescriptor.
// Descriptor returns the original descriptor from an image manifest. See partial.Descriptor.
func (l *layer) Descriptor() (*v1.Descriptor, error) {
return &l.desc, nil
}
76 changes: 76 additions & 0 deletions pkg/sif/layer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2023 Sylabs Inc. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0

package sif_test

import (
"reflect"
"testing"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
)

// layerFromPath returns a Layer for the test to use, populated from the OCI Image with the
// specified path/digests in the corpus.
func layerFromPath(t *testing.T, path string, imageDigest, layerDigest string) v1.Layer {
t.Helper()

ii := imageIndexFromPath(t, path)

imageHash, err := v1.NewHash(imageDigest)
if err != nil {
t.Fatal(err)
}

img, err := ii.Image(imageHash)
if err != nil {
t.Fatal(err)
}

layerHash, err := v1.NewHash(layerDigest)
if err != nil {
t.Fatal(err)
}

l, err := img.LayerByDigest(layerHash)
if err != nil {
t.Fatal(err)
}

return l
}

func TestLayer_Descriptor(t *testing.T) {
tests := []struct {
name string
l v1.Layer
wantDescriptor *v1.Descriptor
}{
{
name: "DockerManifest",
l: layerFromPath(t, "hello-world-docker-v2-manifest",
"sha256:432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338",
"sha256:7050e35b49f5e348c4809f5eff915842962cb813f32062d3bbdd35c750dd7d01",
),
wantDescriptor: &v1.Descriptor{
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
Size: 3208,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "7050e35b49f5e348c4809f5eff915842962cb813f32062d3bbdd35c750dd7d01",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if d, err := partial.Descriptor(tt.l); err != nil {
t.Error(err)
} else if got, want := d, tt.wantDescriptor; !reflect.DeepEqual(got, want) {
t.Errorf("got descriptor %+v, want %+v", got, want)
}
})
}
}