Skip to content

Commit

Permalink
Enable to export layers from Additional Layer Store
Browse files Browse the repository at this point in the history
Currently, layers aquired from additional layer store cannot be exported
(e.g. `podman save`, `podman push`).

This is because the current additional layer store exposes only *extracted view*
of layers. Tar is not reproducible so the runtime cannot reproduce the tar
archive that has the same diff ID as the original.

This commit solves this issue by introducing a new API "`blob`" to the
additional layer store. This file exposes the raw contents of that layer. When
*(c/storage).layerStore.Diff is called, it acquires the diff contents from this
`blob` file which the same digest as the original layer.

Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
  • Loading branch information
ktock committed May 15, 2021
1 parent 4f69205 commit 2bb8cde
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 5 deletions.
7 changes: 7 additions & 0 deletions drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ type AdditionalLayer interface {
// Info returns arbitrary information stored along with this layer (i.e. `info` file)
Info() (io.ReadCloser, error)

// Blob returns a reader of the raw contents of this layer.
Blob() (io.ReadCloser, error)

// Release tells the additional layer store that we don't use this handler.
Release()
}
Expand All @@ -243,6 +246,10 @@ type AdditionalLayerStoreDriver interface {
// LookupAdditionalLayer looks up additional layer store by the specified
// digest and ref and returns an object representing that layer.
LookupAdditionalLayer(d digest.Digest, ref string) (AdditionalLayer, error)

// LookupAdditionalLayer looks up additional layer store by the specified
// ID and returns an object representing that layer.
LookupAdditionalLayerByID(id string) (AdditionalLayer, error)
}

// DiffGetterDriver is the interface for layered file system drivers that
Expand Down
44 changes: 39 additions & 5 deletions drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ func (d *Driver) Cleanup() error {
// LookupAdditionalLayer looks up additional layer store by the specified
// digest and ref and returns an object representing that layer.
// This API is experimental and can be changed without bumping the major version number.
// TODO: to remove the comment once it's no longer experimental.
func (d *Driver) LookupAdditionalLayer(dgst digest.Digest, ref string) (graphdriver.AdditionalLayer, error) {
l, err := d.getAdditionalLayerPath(dgst, ref)
if err != nil {
Expand All @@ -727,6 +728,25 @@ func (d *Driver) LookupAdditionalLayer(dgst digest.Digest, ref string) (graphdri
}, nil
}

// LookupAdditionalLayerByID looks up additional layer store by the specified
// ID and returns an object representing that layer.
// This API is experimental and can be changed without bumping the major version number.
// TODO: to remove the comment once it's no longer experimental.
func (d *Driver) LookupAdditionalLayerByID(id string) (graphdriver.AdditionalLayer, error) {
l, err := d.getAdditionalLayerPathByID(id)
if err != nil {
return nil, err
}
// Tell the additional layer store that we use this layer.
// This will increase reference counter on the store's side.
// This will be decreased on Release() method.
notifyUseAdditionalLayer(l)
return &additionalLayer{
path: l,
d: d,
}, nil
}

// CreateFromTemplate creates a layer with the same contents and parent as another layer.
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
if readWrite {
Expand Down Expand Up @@ -1818,9 +1838,7 @@ func (d *Driver) getAdditionalLayerPath(dgst digest.Digest, ref string) (string,
for _, p := range []string{
filepath.Join(target, "diff"),
filepath.Join(target, "info"),
// TODO(ktock): We should have an API to expose the stream data of this layer
// to enable the client to retrieve the entire contents of this
// layer when it exports this layer.
filepath.Join(target, "blob"),
} {
if _, err := os.Stat(p); err != nil {
return "", errors.Wrapf(graphdriver.ErrLayerUnknown,
Expand All @@ -1835,8 +1853,8 @@ func (d *Driver) getAdditionalLayerPath(dgst digest.Digest, ref string) (string,
}

func (d *Driver) releaseAdditionalLayerByID(id string) {
if al, err := ioutil.ReadFile(path.Join(d.dir(id), "additionallayer")); err == nil {
notifyReleaseAdditionalLayer(string(al))
if al, err := d.getAdditionalLayerPathByID(id); err == nil {
notifyReleaseAdditionalLayer(al)
} else if !os.IsNotExist(err) {
logrus.Warnf("unexpected error on reading Additional Layer Store pointer %v", err)
}
Expand All @@ -1851,12 +1869,19 @@ type additionalLayer struct {

// Info returns arbitrary information stored along with this layer (i.e. `info` file).
// This API is experimental and can be changed without bumping the major version number.
// TODO: to remove the comment once it's no longer experimental.
func (al *additionalLayer) Info() (io.ReadCloser, error) {
return os.Open(filepath.Join(al.path, "info"))
}

// Blob returns a reader of the raw contents of this leyer.
func (al *additionalLayer) Blob() (io.ReadCloser, error) {
return os.Open(filepath.Join(al.path, "blob"))
}

// CreateAs creates a new layer from this additional layer.
// This API is experimental and can be changed without bumping the major version number.
// TODO: to remove the comment once it's no longer experimental.
func (al *additionalLayer) CreateAs(id, parent string) error {
// TODO: support opts
if err := al.d.Create(id, parent, nil); err != nil {
Expand All @@ -1876,8 +1901,17 @@ func (al *additionalLayer) CreateAs(id, parent string) error {
return os.Symlink(filepath.Join(al.path, "diff"), diffDir)
}

func (d *Driver) getAdditionalLayerPathByID(id string) (string, error) {
al, err := ioutil.ReadFile(path.Join(d.dir(id), "additionallayer"))
if err != nil {
return "", err
}
return string(al), nil
}

// Release tells the additional layer store that we don't use this handler.
// This API is experimental and can be changed without bumping the major version number.
// TODO: to remove the comment once it's no longer experimental.
func (al *additionalLayer) Release() {
// Tell the additional layer store that we don't use this layer handler.
// This will decrease the reference counter on the store's side, which was
Expand Down
59 changes: 59 additions & 0 deletions layers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,52 @@ func (r *layerStore) Diff(from, to string, options *DiffOptions) (io.ReadCloser,
return maybeCompressReadCloser(diff)
}

if ad, ok := r.driver.(drivers.AdditionalLayerStoreDriver); ok {
if aLayer, err := ad.LookupAdditionalLayerByID(to); err == nil {
// This is an additional layer. We leverage blob API for aquiring the reproduced raw blob.
info, err := aLayer.Info()
if err != nil {
aLayer.Release()
return nil, err
}
defer info.Close()
layer := &Layer{}
if err := json.NewDecoder(info).Decode(layer); err != nil {
aLayer.Release()
return nil, err
}
blob, err := aLayer.Blob()
if err != nil {
aLayer.Release()
return nil, err
}
// If layer compression type is different from the expected one, decompress and convert it.
if compression != layer.CompressionType {
diff, err := archive.DecompressStream(blob)
if err != nil {
if err2 := blob.Close(); err2 != nil {
err = errors.Wrapf(err, "failed to close blob file: %v", err2)
}
aLayer.Release()
return nil, err
}
rc, err := maybeCompressReadCloser(diff)
if err != nil {
if err2 := closeAll(blob.Close, diff.Close); err2 != nil {
err = errors.Wrapf(err, "failed to cleanup: %v", err2)
}
aLayer.Release()
return nil, err
}
return ioutils.NewReadCloserWrapper(rc, func() error {
defer aLayer.Release()
return closeAll(blob.Close, rc.Close)
}), nil
}
return ioutils.NewReadCloserWrapper(blob, func() error { defer aLayer.Release(); return blob.Close() }), nil
}
}

tsfile, err := os.Open(r.tspath(to))
if err != nil {
if !os.IsNotExist(err) {
Expand Down Expand Up @@ -1733,3 +1779,16 @@ func (r *layerStore) ReloadIfChanged() error {
}
return nil
}

func closeAll(closes ...func() error) (rErr error) {
for _, f := range closes {
if err := f(); err != nil {
if rErr == nil {
rErr = errors.Wrapf(err, "close error")
continue
}
rErr = errors.Wrapf(rErr, "%v", err)
}
}
return
}

0 comments on commit 2bb8cde

Please sign in to comment.