Skip to content

Commit

Permalink
restorer and exporter working as expected
Browse files Browse the repository at this point in the history
Signed-off-by: Joey Brown <[email protected]>
  • Loading branch information
joeybrown-sf committed Jul 2, 2024
1 parent 9e19760 commit e505545
Show file tree
Hide file tree
Showing 19 changed files with 180 additions and 49 deletions.
3 changes: 2 additions & 1 deletion cache/caching_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/buildpacks/imgutil"
"github.com/buildpacks/imgutil/fakes"
"github.com/buildpacks/lifecycle/cmd"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"

Expand Down Expand Up @@ -37,7 +38,7 @@ func testCachingImage(t *testing.T, when spec.G, it spec.S) {
fakeImage = fakes.NewImage("some-image", "", nil)
tmpDir, err = os.MkdirTemp("", "")
h.AssertNil(t, err)
volumeCache, err = cache.NewVolumeCache(tmpDir)
volumeCache, err = cache.NewVolumeCache(tmpDir, cmd.DefaultLogger)
h.AssertNil(t, err)
subject = cache.NewCachingImage(fakeImage, volumeCache)
layerPath, layerSHA, layerData = h.RandomLayer(t, tmpDir)
Expand Down
18 changes: 18 additions & 0 deletions cache/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,21 @@ import (
)

var errCacheCommitted = errors.New("cache cannot be modified after commit")

type ReadErr struct {
msg string
}

func NewReadErr(msg string) ReadErr {
return ReadErr{msg: msg}
}

func (e ReadErr) Error() string {
return e.msg
}

func IsReadErr(err error) (bool, *ReadErr) {
var e ReadErr
isReadErr := errors.As(err, &e)
return isReadErr, &e
}
37 changes: 36 additions & 1 deletion cache/image_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,20 @@ func (c *ImageCache) ReuseLayer(diffID string) error {
return c.newImage.ReuseLayer(diffID)
}

func IsLayerNotFound(err error) bool {
var e imgutil.ErrLayerNotFound
return errors.As(err, &e)
}

func (c *ImageCache) RetrieveLayer(diffID string) (io.ReadCloser, error) {
return c.origImage.GetLayer(diffID)
closer, err := c.origImage.GetLayer(diffID)
if err != nil {
if IsLayerNotFound(err) {
return nil, NewReadErr(fmt.Sprintf("Layer with SHA '%s' not found", diffID))
}
return nil, NewReadErr(fmt.Sprintf("unknown error retrieving layer with SHA '%s'", diffID))
}
return closer, nil
}

func (c *ImageCache) Commit() error {
Expand All @@ -129,3 +141,26 @@ func (c *ImageCache) Commit() error {

return nil
}

func (c *ImageCache) LayerExists(diffID string) (bool, error) {
layers, err := c.origImage.UnderlyingImage().Layers()
if err != nil {
return false, errors.Wrap(err, "getting image layers")
}

for _, layer := range layers {
d, err := layer.DiffID()
if err != nil {
return false, errors.Wrap(err, "getting layer diffID")
}

if d.String() == diffID {
return true, nil
}
}
return false, nil
}

func (c *ImageCache) Destroy() {
c.imageDeleter.DeleteImage(c.origImage)
}
4 changes: 2 additions & 2 deletions cache/image_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func testImageCache(t *testing.T, when spec.G, it spec.S) {
when("layer does not exist", func() {
it("returns an error", func() {
_, err := subject.RetrieveLayer("some_nonexistent_sha")
h.AssertError(t, err, "failed to get layer with sha 'some_nonexistent_sha'")
h.AssertError(t, err, "unknown error retrieving layer with SHA 'some_nonexistent_sha'")
})
})
})
Expand Down Expand Up @@ -236,7 +236,7 @@ func testImageCache(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, subject.AddLayerFile(testLayerTarPath, testLayerSHA))

_, err := subject.RetrieveLayer(testLayerSHA)
h.AssertError(t, err, fmt.Sprintf("failed to get layer with sha '%s'", testLayerSHA))
h.AssertError(t, err, fmt.Sprintf("unknown error retrieving layer with SHA '%s'", testLayerSHA))
})
})
})
Expand Down
7 changes: 4 additions & 3 deletions cache/image_deleter.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
// ImageDeleter defines the methods available to delete and compare cached images
type ImageDeleter interface {
DeleteOrigImageIfDifferentFromNewImage(origImage, newImage imgutil.Image)
DeleteImage(image imgutil.Image)
}

// ImageDeleterImpl is a component to manage cache image deletion
Expand All @@ -35,13 +36,13 @@ func (c *ImageDeleterImpl) DeleteOrigImageIfDifferentFromNewImage(origImage, new
}

if !same {
c.deleteImage(origImage)
c.DeleteImage(origImage)
}
}
}

// deleteImage deletes an image
func (c *ImageDeleterImpl) deleteImage(image imgutil.Image) {
// DeleteImage deletes an image
func (c *ImageDeleterImpl) DeleteImage(image imgutil.Image) {
if c.deletionEnabled {
if err := image.Delete(); err != nil {
c.logger.Warnf("Unable to delete cache image: %v", err.Error())
Expand Down
45 changes: 41 additions & 4 deletions cache/volume_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package cache

import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/buildpacks/lifecycle/log"
"github.com/pkg/errors"

"github.com/buildpacks/lifecycle/internal/fsutil"
Expand All @@ -20,9 +22,10 @@ type VolumeCache struct {
backupDir string
stagingDir string
committedDir string
logger log.Logger
}

func NewVolumeCache(dir string) (*VolumeCache, error) {
func NewVolumeCache(dir string, logger log.Logger) (*VolumeCache, error) {
if _, err := os.Stat(dir); err != nil {
return nil, err
}
Expand All @@ -32,6 +35,7 @@ func NewVolumeCache(dir string) (*VolumeCache, error) {
backupDir: filepath.Join(dir, "committed-backup"),
stagingDir: filepath.Join(dir, "staging"),
committedDir: filepath.Join(dir, "committed"),
logger: logger,
}

if err := c.setupStagingDir(); err != nil {
Expand Down Expand Up @@ -133,7 +137,20 @@ func (c *VolumeCache) ReuseLayer(diffID string) error {
if c.committed {
return errCacheCommitted
}
if err := os.Link(diffIDPath(c.committedDir, diffID), diffIDPath(c.stagingDir, diffID)); err != nil && !os.IsExist(err) {
committedPath := diffIDPath(c.committedDir, diffID)
stagingPath := diffIDPath(c.stagingDir, diffID)

if _, err := os.Stat(committedPath); err != nil {
if os.IsNotExist(err) {
return NewReadErr(fmt.Sprintf("Layer with SHA '%s' not found", diffID))
}
if os.IsPermission(err) {
return NewReadErr(fmt.Sprintf("Cannot read cache layer with SHA '%s' due to insufficient permissions", diffID))
}
return errors.Wrapf(err, "reusing layer (%s)", diffID)
}

if err := os.Link(committedPath, stagingPath); err != nil && !os.IsExist(err) {
return errors.Wrapf(err, "reusing layer (%s)", diffID)
}
return nil
Expand All @@ -146,7 +163,10 @@ func (c *VolumeCache) RetrieveLayer(diffID string) (io.ReadCloser, error) {
}
file, err := os.Open(path)
if err != nil {
return nil, errors.Wrapf(err, "opening layer with SHA '%s'", diffID)
if os.IsPermission(err) {
return nil, NewReadErr(fmt.Sprintf("cannot read cache layer with SHA '%s' due to insufficient permissions", diffID))
}
return nil, NewReadErr(fmt.Sprintf("unknown error retrieving layer with SHA '%s'", diffID))
}
return file, nil
}
Expand All @@ -165,7 +185,7 @@ func (c *VolumeCache) RetrieveLayerFile(diffID string) (string, error) {
path := diffIDPath(c.committedDir, diffID)
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return "", errors.Wrapf(err, "layer with SHA '%s' not found", diffID)
return "", NewReadErr(fmt.Sprintf("Layer with SHA '%s' not found", diffID))
}
return "", errors.Wrapf(err, "retrieving layer with SHA '%s'", diffID)
}
Expand Down Expand Up @@ -206,3 +226,20 @@ func (c *VolumeCache) setupStagingDir() error {
}
return os.MkdirAll(c.stagingDir, 0777)
}

func (c *VolumeCache) LayerExists(diffID string) (bool, error) {
path := diffIDPath(c.committedDir, diffID)
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, errors.Wrapf(err, "retrieving layer with SHA '%s'", diffID)
}
return true, nil
}

func (c *VolumeCache) Destroy() {
if err := os.RemoveAll(c.dir); err != nil {
c.logger.Warnf("Unable to delete cache directory: %v", err.Error())
}
}
24 changes: 14 additions & 10 deletions cache/volume_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"path/filepath"
"testing"

"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/log"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"

Expand All @@ -28,6 +30,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
backupDir string
stagingDir string
committedDir string
testLogger log.Logger
)

it.Before(func() {
Expand All @@ -42,6 +45,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
backupDir = filepath.Join(volumeDir, "committed-backup")
stagingDir = filepath.Join(volumeDir, "staging")
committedDir = filepath.Join(volumeDir, "committed")
testLogger = cmd.DefaultLogger
})

it.After(func() {
Expand All @@ -50,7 +54,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {

when("#NewVolumeCache", func() {
it("returns an error when the volume path does not exist", func() {
_, err := cache.NewVolumeCache(filepath.Join(tmpDir, "does_not_exist"))
_, err := cache.NewVolumeCache(filepath.Join(tmpDir, "does_not_exist"), testLogger)
if err == nil {
t.Fatal("expected NewVolumeCache to fail because volume path does not exist")
}
Expand All @@ -66,7 +70,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("clears staging", func() {
var err error

subject, err = cache.NewVolumeCache(volumeDir)
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
h.AssertNil(t, err)

_, err = os.Stat(filepath.Join(stagingDir, "some-layer.tar"))
Expand All @@ -80,7 +84,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("creates staging dir", func() {
var err error

subject, err = cache.NewVolumeCache(volumeDir)
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
h.AssertNil(t, err)

_, err = os.Stat(stagingDir)
Expand All @@ -92,7 +96,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("creates committed dir", func() {
var err error

subject, err = cache.NewVolumeCache(volumeDir)
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
h.AssertNil(t, err)

_, err = os.Stat(committedDir)
Expand All @@ -109,7 +113,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("clears the backup dir", func() {
var err error

subject, err = cache.NewVolumeCache(volumeDir)
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
h.AssertNil(t, err)

_, err = os.Stat(filepath.Join(backupDir, "some-layer.tar"))
Expand All @@ -124,7 +128,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it.Before(func() {
var err error

subject, err = cache.NewVolumeCache(volumeDir)
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
h.AssertNil(t, err)
})

Expand Down Expand Up @@ -206,7 +210,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
when("layer does not exist", func() {
it("returns an error", func() {
_, err := subject.RetrieveLayer("some_nonexistent_sha")
h.AssertError(t, err, "layer with SHA 'some_nonexistent_sha' not found")
h.AssertError(t, err, "Layer with SHA 'some_nonexistent_sha' not found")
})
})
})
Expand All @@ -230,7 +234,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
when("layer does not exist", func() {
it("returns an error", func() {
_, err := subject.RetrieveLayerFile("some_nonexistent_sha")
h.AssertError(t, err, "layer with SHA 'some_nonexistent_sha' not found")
h.AssertError(t, err, "Layer with SHA 'some_nonexistent_sha' not found")
})
})
})
Expand Down Expand Up @@ -340,7 +344,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, subject.AddLayerFile(tarPath, "some_sha"))

_, err := subject.RetrieveLayer("some_sha")
h.AssertError(t, err, "layer with SHA 'some_sha' not found")
h.AssertError(t, err, "Layer with SHA 'some_sha' not found")
})
})

Expand Down Expand Up @@ -415,7 +419,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, subject.AddLayer(layerReader, layerSha))

_, err := subject.RetrieveLayer(layerSha)
h.AssertError(t, err, fmt.Sprintf("layer with SHA '%s' not found", layerSha))
h.AssertError(t, err, fmt.Sprintf("Layer with SHA '%s' not found", layerSha))
})
})

Expand Down
7 changes: 4 additions & 3 deletions cmd/lifecycle/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/buildpacks/imgutil/layout"
"github.com/buildpacks/imgutil/local"
"github.com/buildpacks/imgutil/remote"
"github.com/buildpacks/lifecycle/log"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
Expand Down Expand Up @@ -200,7 +201,7 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore phase.Cache, analyz
case e.UseLayout:
appImage, runImageID, err = e.initLayoutAppImage(analyzedMD)
case e.UseDaemon:
appImage, runImageID, err = e.initDaemonAppImage(analyzedMD)
appImage, runImageID, err = e.initDaemonAppImage(analyzedMD, cmd.DefaultLogger)
default:
appImage, runImageID, err = e.initRemoteAppImage(analyzedMD)
}
Expand Down Expand Up @@ -258,7 +259,7 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore phase.Cache, analyz
return nil
}

func (e *exportCmd) initDaemonAppImage(analyzedMD files.Analyzed) (imgutil.Image, string, error) {
func (e *exportCmd) initDaemonAppImage(analyzedMD files.Analyzed, logger log.Logger) (imgutil.Image, string, error) {
var opts = []imgutil.ImageOption{
local.FromBaseImage(e.RunImageRef),
}
Expand Down Expand Up @@ -301,7 +302,7 @@ func (e *exportCmd) initDaemonAppImage(analyzedMD files.Analyzed) (imgutil.Image
}

if e.LaunchCacheDir != "" {
volumeCache, err := cache.NewVolumeCache(e.LaunchCacheDir)
volumeCache, err := cache.NewVolumeCache(e.LaunchCacheDir, logger)
if err != nil {
return nil, "", cmd.FailErr(err, "create launch cache")
}
Expand Down
Loading

0 comments on commit e505545

Please sign in to comment.