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

feat: expose type Layer, add Offset() #28

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
79 changes: 68 additions & 11 deletions pkg/sif/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package sif

import (
"bytes"
"errors"
"fmt"

Expand All @@ -13,19 +14,66 @@ import (
"github.com/google/go-containerregistry/pkg/v1/types"
)

var _ partial.CompressedImageCore = (*image)(nil)
var _ v1.Image = (*image)(nil)

type image struct {
f *fileImage
desc *v1.Descriptor
rawManifest []byte
}

// Layers returns the ordered collection of filesystem layers that comprise this image. The order
// of the list is oldest/base layer first, and most-recent/top layer last.
func (im *image) Layers() ([]v1.Layer, error) {
m, err := im.Manifest()
if err != nil {
return nil, err
}

ls := make([]v1.Layer, len(m.Layers))
for i, d := range m.Layers {
l, err := im.LayerByDigest(d.Digest)
if err != nil {
return nil, err
}

ls[i] = l
}

return ls, nil
}

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

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

// ConfigName returns the hash of the image's config file, also known as the Image ID.
func (im *image) ConfigName() (v1.Hash, error) {
b, err := im.RawConfigFile()
if err != nil {
return v1.Hash{}, err
}

h, _, err := v1.SHA256(bytes.NewReader(b))
return h, err
}

// ConfigFile returns this image's config file.
func (im *image) ConfigFile() (*v1.ConfigFile, error) {
b, err := im.RawConfigFile()
if err != nil {
return nil, err
}

return v1.ParseConfigFile(bytes.NewReader(b))
}

// RawConfigFile returns the serialized bytes of ConfigFile().
func (im *image) RawConfigFile() ([]byte, error) {
manifest, err := im.Manifest()
Expand All @@ -36,9 +84,15 @@ func (im *image) RawConfigFile() ([]byte, error) {
return im.f.Bytes(manifest.Config.Digest)
}

// Digest returns the sha256 of this image's manifest.
func (im *image) Digest() (v1.Hash, error) {
h, _, err := v1.SHA256(bytes.NewReader(im.rawManifest))
return h, err
}

// Manifest returns this image's Manifest object.
func (im *image) Manifest() (*v1.Manifest, error) {
return partial.Manifest(im)
return v1.ParseManifest(bytes.NewReader(im.rawManifest))
}

// RawManifest returns the serialized bytes of Manifest().
Expand All @@ -55,22 +109,15 @@ var errLayerNotFoundInImage = errors.New("layer not found in image")

// LayerByDigest returns a Layer for interacting with a particular layer of the image, looking it
// up by "digest" (the compressed hash).
func (im *image) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
func (im *image) LayerByDigest(h v1.Hash) (v1.Layer, error) {
manifest, err := im.Manifest()
if err != nil {
return nil, err
}

if h == manifest.Config.Digest {
return &layer{
f: im.f,
desc: manifest.Config,
}, nil
}

for _, desc := range manifest.Layers {
if h == desc.Digest {
return &layer{
return &Layer{
f: im.f,
desc: desc,
}, nil
Expand All @@ -79,3 +126,13 @@ func (im *image) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {

return nil, fmt.Errorf("%w: %v", errLayerNotFoundInImage, h)
}

// LayerByDiffID is an analog to LayerByDigest, looking up by "diff id" (the uncompressed hash).
func (im *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) {
h, err := partial.DiffIDToBlob(im, h)
if err != nil {
return nil, err
}

return im.LayerByDigest(h)
}
3 changes: 1 addition & 2 deletions pkg/sif/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"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"
)
Expand Down Expand Up @@ -74,7 +73,7 @@ func Test_imageIndex_Image(t *testing.T) {
t.Error(err)
}

if d, err := partial.Descriptor(img); err != nil {
if d, err := img.(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
3 changes: 1 addition & 2 deletions pkg/sif/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"fmt"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sylabs/sif/v2/pkg/sif"
)
Expand Down Expand Up @@ -116,7 +115,7 @@ func (ix *imageIndex) Image(h v1.Hash) (v1.Image, error) {
desc: desc,
rawManifest: b,
}
return partial.CompressedToImage(&img)
return &img, nil
}

// ImageIndex returns a v1.ImageIndex that this ImageIndex references.
Expand Down
41 changes: 34 additions & 7 deletions pkg/sif/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,61 @@ import (
"github.com/google/go-containerregistry/pkg/v1/types"
)

var _ partial.CompressedLayer = (*layer)(nil)
var _ v1.Layer = (*Layer)(nil)

type layer struct {
type Layer struct {
f *fileImage
desc v1.Descriptor
}

// Digest returns the Hash of the compressed layer.
func (l *layer) Digest() (v1.Hash, error) {
func (l *Layer) Digest() (v1.Hash, error) {
return l.desc.Digest, nil
}

// DiffID returns the Hash of the uncompressed layer.
func (l *Layer) DiffID() (v1.Hash, error) {
r, err := l.Uncompressed()
if err != nil {
return v1.Hash{}, err
}
defer r.Close()

h, _, err := v1.SHA256(r)
return h, err
}

// Compressed returns an io.ReadCloser for the compressed layer contents.
func (l *layer) Compressed() (io.ReadCloser, error) {
func (l *Layer) Compressed() (io.ReadCloser, error) {
return l.f.Blob(l.desc.Digest)
}

// Uncompressed returns an io.ReadCloser for the uncompressed layer contents.
func (l *Layer) Uncompressed() (io.ReadCloser, error) {
cl, err := partial.CompressedToLayer(l)
if err != nil {
return nil, err
}

return cl.Uncompressed()
}

// Size returns the compressed size of the Layer.
func (l *layer) Size() (int64, error) {
func (l *Layer) Size() (int64, error) {
return l.desc.Size, nil
}

// Offset returns the offset within the SIF image of the Layer.
func (l *Layer) Offset() (int64, error) {
return l.f.Offset(l.desc.Digest)
}

// MediaType returns the media type of the Layer.
func (l *layer) MediaType() (types.MediaType, error) {
func (l *Layer) MediaType() (types.MediaType, error) {
return l.desc.MediaType, nil
}

// Descriptor returns the original descriptor from an image manifest. See partial.Descriptor.
func (l *layer) Descriptor() (*v1.Descriptor, error) {
func (l *Layer) Descriptor() (*v1.Descriptor, error) {
return &l.desc, nil
}
30 changes: 28 additions & 2 deletions pkg/sif/layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"testing"

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

// layerFromPath returns a Layer for the test to use, populated from the OCI Image with the
Expand Down Expand Up @@ -66,11 +66,37 @@ func TestLayer_Descriptor(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if d, err := partial.Descriptor(tt.l); err != nil {
if d, err := tt.l.(*sif.Layer).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)
}
})
}
}

func TestLayer_Offset(t *testing.T) {
tests := []struct {
name string
l v1.Layer
wantOffset int64
}{
{
name: "DockerManifest",
l: layerFromPath(t, "hello-world-docker-v2-manifest",
"sha256:432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338",
"sha256:7050e35b49f5e348c4809f5eff915842962cb813f32062d3bbdd35c750dd7d01",
),
wantOffset: 32176,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if d, err := tt.l.(*sif.Layer).Offset(); err != nil {
t.Error(err)
} else if got, want := d, tt.wantOffset; !reflect.DeepEqual(got, want) {
t.Errorf("got offset %+v, want %+v", got, want)
}
})
}
}
10 changes: 10 additions & 0 deletions pkg/sif/sif.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ func (f *fileImage) Bytes(h v1.Hash) ([]byte, error) {

return d.GetData()
}

// Offset returns the offset within the SIF image of the blob with the supplied digest.
func (f *fileImage) Offset(h v1.Hash) (int64, error) {
d, err := f.GetDescriptor(sif.WithOCIBlobDigest(h))
if err != nil {
return 0, err
}

return d.Offset(), nil
}