diff --git a/drivers/driver.go b/drivers/driver.go index 1d2a659661..770b431bdd 100644 --- a/drivers/driver.go +++ b/drivers/driver.go @@ -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() } @@ -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 diff --git a/drivers/overlay/overlay.go b/drivers/overlay/overlay.go index c5d93e9a36..39caa6f21d 100644 --- a/drivers/overlay/overlay.go +++ b/drivers/overlay/overlay.go @@ -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 { @@ -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 { @@ -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, @@ -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) } @@ -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 { @@ -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 diff --git a/layers.go b/layers.go index 394c007312..1ed265d5dc 100644 --- a/layers.go +++ b/layers.go @@ -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) { @@ -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 +}