Skip to content

Commit

Permalink
Merge pull request #27 from tri-adam/descriptor
Browse files Browse the repository at this point in the history
refactor: expose descriptor for image/index
  • Loading branch information
tri-adam authored Nov 8, 2023
2 parents 655ff77 + d4c7de8 commit b26470f
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 45 deletions.
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)
}
})
}
}

0 comments on commit b26470f

Please sign in to comment.