Skip to content

Commit

Permalink
Merge pull request #28 from tri-adam/layer-offset
Browse files Browse the repository at this point in the history
feat: expose `type Layer`, add `Offset()`
  • Loading branch information
tri-adam authored Nov 8, 2023
2 parents b26470f + c89b986 commit 21ddb5a
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 24 deletions.
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
}

0 comments on commit 21ddb5a

Please sign in to comment.