Skip to content

Commit

Permalink
cache: Add support for bind cache
Browse files Browse the repository at this point in the history
Signed-off-by: Nitish Gupta <imnitish.ng@gmail.com>
  • Loading branch information
imnitishng committed Sep 5, 2022
1 parent d5f587a commit 56d8dc0
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 9 deletions.
27 changes: 27 additions & 0 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,33 @@ func testAcceptance(
})
})

when("--cache with options for build cache as bind", func() {
var bindCacheDir, cacheFlags string
it.Before(func() {
h.SkipIf(t, !pack.SupportsFeature(invoke.Cache), "")
cacheBindName := fmt.Sprintf("%s-bind", repoName)
bindCacheDir, err := ioutil.TempDir("", cacheBindName)
assert.Nil(err)
cacheFlags = fmt.Sprintf("type=build;format=bind;name=%s", bindCacheDir)
})

it("creates image and cache image on the registry", func() {
buildArgs := []string{
repoName,
"-p", filepath.Join("testdata", "mock_app"),
"--cache",
cacheFlags,
}

output := pack.RunSuccessfully("build", buildArgs...)
assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName)

t.Log("checking that bind mount has cache contents")
assert.FileExists(fmt.Sprintf("%s/committed", bindCacheDir))
defer os.RemoveAll(bindCacheDir)
})
})

when("ctrl+c", func() {
it("stops the execution", func() {
var buf = new(bytes.Buffer)
Expand Down
12 changes: 9 additions & 3 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,16 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF
}
buildCache = cache.NewImageCache(cacheImage, l.docker)
} else {
buildCache = cache.NewVolumeCache(l.opts.Image, l.opts.Cache.Build, "build", l.docker)
switch l.opts.Cache.Build.Format {
case cache.CacheVolume:
buildCache = cache.NewVolumeCache(l.opts.Image, l.opts.Cache.Build, "build", l.docker)
l.logger.Debugf("Using build cache volume %s", style.Symbol(buildCache.Name()))
case cache.CacheBind:
buildCache = cache.NewBindCache(l.opts.Cache.Build, l.docker)
l.logger.Debugf("Using build cache dir %s", style.Symbol(buildCache.Name()))
}
}

l.logger.Debugf("Using build cache volume %s", style.Symbol(buildCache.Name()))
if l.opts.ClearCache {
if err := buildCache.Clear(ctx); err != nil {
return errors.Wrap(err, "clearing build cache")
Expand Down Expand Up @@ -251,7 +257,7 @@ func (l *LifecycleExecution) Create(ctx context.Context, publish bool, dockerHos
case cache.Image:
flags = append(flags, "-cache-image", buildCache.Name())
cacheOpts = WithBinds(volumes...)
case cache.Volume:
case cache.Volume, cache.Bind:
cacheOpts = WithBinds(append(volumes, fmt.Sprintf("%s:%s", buildCache.Name(), l.mountPaths.cacheDir()))...)
}

Expand Down
36 changes: 36 additions & 0 deletions internal/cache/bind_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cache

import (
"context"
"os"

"github.com/docker/docker/client"
)

type BindCache struct {
docker client.CommonAPIClient
bind string
}

func NewBindCache(cacheType CacheInfo, dockerClient client.CommonAPIClient) *BindCache {
return &BindCache{
bind: cacheType.Source,
docker: dockerClient,
}
}

func (c *BindCache) Name() string {
return c.bind
}

func (c *BindCache) Clear(ctx context.Context) error {
err := os.RemoveAll(c.bind)
if err != nil {
return err
}
return nil
}

func (c *BindCache) Type() Type {
return Bind
}
33 changes: 30 additions & 3 deletions internal/cache/cache_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cache
import (
"encoding/csv"
"fmt"
"path/filepath"
"strings"

"github.com/pkg/errors"
Expand All @@ -21,6 +22,7 @@ type CacheOpts struct {
const (
CacheVolume Format = iota
CacheImage
CacheBind
)

func (f Format) String() string {
Expand All @@ -29,6 +31,8 @@ func (f Format) String() string {
return "image"
case CacheVolume:
return "volume"
case CacheBind:
return "bind"
}
return ""
}
Expand Down Expand Up @@ -76,6 +80,8 @@ func (c *CacheOpts) Set(value string) error {
cache.Format = CacheImage
case "volume":
cache.Format = CacheVolume
case "bind":
cache.Format = CacheBind
default:
return errors.Errorf("invalid cache format '%s'", value)
}
Expand All @@ -84,7 +90,7 @@ func (c *CacheOpts) Set(value string) error {
}
}

err = populateMissing(c)
err = sanitize(c)
if err != nil {
return err
}
Expand All @@ -102,9 +108,30 @@ func (c *CacheOpts) Type() string {
return "cache"
}

func populateMissing(c *CacheOpts) error {
if (c.Build.Source == "" && c.Build.Format == CacheImage) || (c.Launch.Source == "" && c.Launch.Format == CacheImage) {
func sanitize(c *CacheOpts) error {
if (c.Build.Source == "" && c.Build.Format == CacheImage) ||
(c.Build.Source == "" && c.Build.Format == CacheBind) ||
(c.Launch.Source == "" && c.Launch.Format == CacheImage) ||
(c.Launch.Source == "" && c.Launch.Format == CacheBind) {
return errors.Errorf("cache 'name' is required")
}

if c.Build.Format == CacheBind || c.Launch.Format == CacheBind {
var (
resolvedPath string
err error
)
if c.Build.Format == CacheBind {
if resolvedPath, err = filepath.Abs(c.Build.Source); err != nil {
return errors.Wrap(err, "resolve absolute path")
}
c.Build.Source = filepath.Join(resolvedPath, "build-cache")
} else {
if resolvedPath, err = filepath.Abs(c.Launch.Source); err != nil {
return errors.Wrap(err, "resolve absolute path")
}
c.Launch.Source = filepath.Join(resolvedPath, "launch-cache")
}
}
return nil
}
94 changes: 91 additions & 3 deletions internal/cache/cache_opts_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package cache

import (
"fmt"
"os"
"runtime"
"strings"
"testing"

"github.com/heroku/color"
Expand Down Expand Up @@ -93,9 +97,6 @@ func testCacheOpts(t *testing.T, when spec.G, it spec.S) {
for _, testcase := range successTestCases {
var cacheFlags CacheOpts
t.Logf("Testing cache type: %s", testcase.name)
if testcase.name == "Everything missing" {
print("i am here")
}
err := cacheFlags.Set(testcase.input)

if testcase.shouldFail {
Expand Down Expand Up @@ -208,4 +209,91 @@ func testCacheOpts(t *testing.T, when spec.G, it spec.S) {
}
})
})

when("bind cache format options are passed", func() {
it("with complete options", func() {
var testcases []CacheOptTestCase
homeDir, err := os.UserHomeDir()
h.AssertNil(t, err)
cwd, err := os.Getwd()
h.AssertNil(t, err)

if runtime.GOOS != "windows" {
testcases = []CacheOptTestCase{
{
name: "Build cache as bind",
input: fmt.Sprintf("type=build;format=bind;name=%s/test-bind-build-cache", homeDir),
output: fmt.Sprintf("type=build;format=bind;name=%s/test-bind-build-cache/build-cache;type=launch;format=volume;name=;", homeDir),
},
{
name: "Build cache as bind with relative path",
input: "type=build;format=bind;name=./test-bind-build-cache-relative",
output: fmt.Sprintf("type=build;format=bind;name=%s/test-bind-build-cache-relative/build-cache;type=launch;format=volume;name=;", cwd),
},
{
name: "Launch cache as bind",
input: fmt.Sprintf("type=launch;format=bind;name=%s/test-bind-volume-cache", homeDir),
output: fmt.Sprintf("type=build;format=volume;name=;type=launch;format=bind;name=%s/test-bind-volume-cache/launch-cache;", homeDir),
},
}
} else {
testcases = []CacheOptTestCase{
{
name: "Build cache as bind",
input: fmt.Sprintf("type=build;format=bind;name=%s\\test-bind-build-cache", homeDir),
output: fmt.Sprintf("type=build;format=bind;name=%s\\test-bind-build-cache\\build-cache;type=launch;format=volume;name=;", homeDir),
},
{
name: "Build cache as bind with relative path",
input: "type=build;format=bind;name=.\\test-bind-build-cache-relative",
output: fmt.Sprintf("type=build;format=bind;name=%s\\test-bind-build-cache-relative\\build-cache;type=launch;format=volume;name=;", cwd),
},
{
name: "Launch cache as bind",
input: fmt.Sprintf("type=launch;format=bind;name=%s\\test-bind-volume-cache", homeDir),
output: fmt.Sprintf("type=build;format=volume;name=;type=launch;format=bind;name=%s\\test-bind-volume-cache\\launch-cache;", homeDir),
},
}
}

for _, testcase := range testcases {
var cacheFlags CacheOpts
t.Logf("Testing cache type: %s", testcase.name)
err := cacheFlags.Set(testcase.input)
h.AssertNil(t, err)
h.AssertEq(t, strings.ToLower(testcase.output), strings.ToLower(cacheFlags.String()))
}
})

it("with missing options", func() {
successTestCases := []CacheOptTestCase{
{
name: "Launch cache as bind missing: name",
input: "type=launch;format=bind",
output: "cache 'name' is required",
shouldFail: true,
},
{
name: "Launch cache as Volume missing: type, name",
input: "format=bind",
output: "cache 'name' is required",
shouldFail: true,
},
}

for _, testcase := range successTestCases {
var cacheFlags CacheOpts
t.Logf("Testing cache type: %s", testcase.name)
err := cacheFlags.Set(testcase.input)

if testcase.shouldFail {
h.AssertError(t, err, testcase.output)
} else {
h.AssertNil(t, err)
output := cacheFlags.String()
h.AssertEq(t, testcase.output, output)
}
}
})
})
}
1 change: 1 addition & 0 deletions internal/cache/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cache
const (
Image Type = iota
Volume
Bind
)

type Type int

0 comments on commit 56d8dc0

Please sign in to comment.