Skip to content

Commit

Permalink
add support for local cnb caching (digitalocean#1216)
Browse files Browse the repository at this point in the history
* add support for local cnb caching

* address pr feedback
  • Loading branch information
nicktate committed Sep 20, 2022
1 parent df94a36 commit 2fa66bb
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 15 deletions.
2 changes: 2 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const (
ArgClusterNodePool = "node-pool"
// ArgClusterUpdateKubeconfig updates the local kubeconfig.
ArgClusterUpdateKubeconfig = "update-kubeconfig"
// ArgNoCache represents whether or not to omit the cache on the next command.
ArgNoCache = "no-cache"
// ArgNodePoolName is a cluster's node pool name argument.
ArgNodePoolName = "name"
// ArgNodePoolCount is a cluster's node pool count argument.
Expand Down
23 changes: 23 additions & 0 deletions commands/apps_dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ func AppsDev() *Command {
"Additional environment variables to inject into the build.",
)

AddBoolFlag(
build, doctl.ArgNoCache,
"", false,
"Whether or not to omit the cache for the build.",
)

AddStringFlag(
build, doctl.ArgAppDevBuildCommand,
"", "",
Expand All @@ -100,6 +106,10 @@ func AppsDev() *Command {
// RunAppsDevBuild builds an app component locally.
func RunAppsDevBuild(c *CmdConfig) error {
ctx := context.Background()
noCache, err := c.Doit.GetBool(c.NS, doctl.ArgNoCache)
if err != nil {
return err
}
timeout, err := c.Doit.GetDuration(c.NS, doctl.ArgTimeout)
if err != nil {
return err
Expand Down Expand Up @@ -253,6 +263,17 @@ func RunAppsDevBuild(c *CmdConfig) error {
return err
}

if noCache {
err = conf.ClearCacheDir(ctx, component)
if err != nil {
return err
}
}
err = conf.EnsureCacheDir(ctx, component)
if err != nil {
return err
}

if Interactive {
choice, err := confirm.New(
"start build?",
Expand Down Expand Up @@ -306,6 +327,8 @@ func RunAppsDevBuild(c *CmdConfig) error {

builder, err := c.componentBuilderFactory.NewComponentBuilder(cli, conf.contextDir, spec, builder.NewBuilderOpts{
Component: component,
LocalCacheDir: conf.CacheDir(component),
NoCache: noCache,
Registry: registryName,
EnvOverride: envs,
BuildCommandOverride: buildOverrride,
Expand Down
30 changes: 24 additions & 6 deletions commands/apps_dev_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -138,12 +139,6 @@ func RunAppsDevConfigUnset(c *CmdConfig) error {
return nil
}

type appDevConfig struct {
contextDir string
cmdConfig *CmdConfig
viper *viper.Viper
}

var validAppDevKeys = map[string]bool{
doctl.ArgApp: true,
doctl.ArgAppSpec: true,
Expand All @@ -161,6 +156,29 @@ func outputValidAppDevKeys() string {
return strings.Join(keys, ", ")
}

type appDevConfig struct {
contextDir string
cmdConfig *CmdConfig
viper *viper.Viper
}

func (c *appDevConfig) CacheDir(component string) string {
return filepath.Join(c.contextDir, ".do", "cache", component)
}

func (c *appDevConfig) EnsureCacheDir(ctx context.Context, component string) error {
err := os.MkdirAll(filepath.Join(c.contextDir, ".do", "cache", component), os.ModePerm)
if err != nil {
return err
}

return ensureStringInFile(filepath.Join(c.contextDir, ".do", ".gitignore"), "/cache")
}

func (c *appDevConfig) ClearCacheDir(ctx context.Context, component string) error {
return os.RemoveAll(filepath.Join(c.contextDir, ".do", "cache", component))
}

func (c *appDevConfig) WriteConfig() error {
return c.viper.WriteConfig()
}
Expand Down
23 changes: 22 additions & 1 deletion internal/apps/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/digitalocean/doctl/commands/charm"
"github.com/digitalocean/doctl/commands/charm/template"
"github.com/digitalocean/godo"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
)

// ComponentBuilderFactory is the interface for creating a component builder.
Expand Down Expand Up @@ -41,6 +43,7 @@ type baseComponentBuilder struct {
envOverrides map[string]string
buildCommandOverride string
copyOnWriteSemantics bool
noCache bool

logWriter io.Writer
}
Expand Down Expand Up @@ -99,6 +102,19 @@ func (b baseComponentBuilder) getEnvMap() (map[string]string, error) {
return envMap, nil
}

func (b *baseComponentBuilder) imageExists(ctx context.Context, ref string) (bool, error) {
images, err := b.cli.ImageList(ctx, types.ImageListOptions{
Filters: filters.NewArgs(filters.Arg("reference", ref)),
})
if err != nil {
return false, err
}
if len(images) < 1 {
return false, nil
}
return true, nil
}

// NewBuilderOpts ...
type NewBuilderOpts struct {
Component string
Expand All @@ -107,6 +123,8 @@ type NewBuilderOpts struct {
BuildCommandOverride string
LogWriter io.Writer
Versioning Versioning
LocalCacheDir string
NoCache bool
}

type Versioning struct {
Expand Down Expand Up @@ -156,9 +174,11 @@ func (f *DefaultComponentBuilderFactory) NewComponentBuilder(cli DockerEngineCli
opts.EnvOverride,
opts.BuildCommandOverride,
copyOnWriteSemantics,
opts.NoCache,
opts.LogWriter,
},
versioning: cnbVersioning,
versioning: cnbVersioning,
localCacheDir: opts.LocalCacheDir,
}, nil
}

Expand All @@ -172,6 +192,7 @@ func (f *DefaultComponentBuilderFactory) NewComponentBuilder(cli DockerEngineCli
opts.EnvOverride,
opts.BuildCommandOverride,
copyOnWriteSemantics,
opts.NoCache,
opts.LogWriter,
},
}, nil
Expand Down
35 changes: 28 additions & 7 deletions internal/apps/builder/cnb.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ import (

const (
// CNBBuilderImage represents the local cnb builder.
CNBBuilderImage = "digitaloceanapps/cnb-local-builder:dev"
CNBBuilderImage = "digitaloceanapps/cnb-local-builder:v0.46.0"

appVarAllowListKey = "APP_VARS"
appVarPrefix = "APP_VAR_"
cnbCacheDir = "/cnb/cache"
)

// CNBComponentBuilder represents a CNB builder.
type CNBComponentBuilder struct {
baseComponentBuilder
versioning CNBVersioning
versioning CNBVersioning
localCacheDir string
}

// CNBVersioning contains CNB versioning config.
Expand All @@ -56,7 +58,7 @@ func (b *CNBComponentBuilder) Build(ctx context.Context) (res ComponentBuilderRe
return res, err
}

env, err := b.cnbEnv()
env, err := b.cnbEnv(ctx)
if err != nil {
return res, fmt.Errorf("configuring environment variables: %w", err)
}
Expand All @@ -74,6 +76,14 @@ func (b *CNBComponentBuilder) Build(ctx context.Context) (res ComponentBuilderRe
})
}

if b.localCacheDir != "" {
mounts = append(mounts, mount.Mount{
Type: mount.TypeBind,
Source: b.localCacheDir,
Target: cnbCacheDir,
})
}

buildContainer, err := b.cli.ContainerCreate(ctx, &container.Config{
Image: CNBBuilderImage,
Entrypoint: []string{"sh", "-c", "sleep infinity"},
Expand Down Expand Up @@ -174,14 +184,14 @@ func (b *CNBComponentBuilder) Build(ctx context.Context) (res ComponentBuilderRe
return res, nil
}

func (b *CNBComponentBuilder) cnbEnv() ([]string, error) {
func (b *CNBComponentBuilder) cnbEnv(ctx context.Context) ([]string, error) {
envs := []string{}
appVars := []string{}

envMap, err := b.getEnvMap()
if err != nil {
return nil, err
}
envs := []string{}

appVars := []string{}
for k, v := range envMap {
envs = append(envs, appVarPrefix+k+"="+v)
appVars = append(appVars, k)
Expand Down Expand Up @@ -217,6 +227,17 @@ func (b *CNBComponentBuilder) cnbEnv() ([]string, error) {
envs = append(envs, "VERSION_PINNING_LIST="+string(versioningJSON))
}

if exists, err := b.imageExists(ctx, b.ImageOutputName()); err != nil {
return nil, err
} else if exists {
envs = append(envs, "PREVIOUS_APP_IMAGE_URL="+b.ImageOutputName())
}

if b.localCacheDir != "" {
envs = append(envs, "APP_CACHE_DIR="+cnbCacheDir)
}

sort.Strings(envs)

return envs, nil
}
11 changes: 11 additions & 0 deletions internal/apps/builder/cnb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/digitalocean/godo"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -86,6 +87,7 @@ func TestCNBComponentBuild(t *testing.T) {
},
buildCommandOverride: "custom build command",
},
localCacheDir: "/cache",
}

buildID := "build-id"
Expand All @@ -97,12 +99,16 @@ func TestCNBComponentBuild(t *testing.T) {
Force: true,
}).Return(nil)
mockClient.EXPECT().ContainerStart(ctx, buildID, types.ContainerStartOptions{}).Return(nil)
mockClient.EXPECT().ImageList(ctx, types.ImageListOptions{
Filters: filters.NewArgs(filters.Arg("reference", builder.ImageOutputName())),
}).Return([]types.ImageSummary{{ /*single entry*/ }}, nil)

execID := "exec-id"
mockClient.EXPECT().ContainerExecCreate(ctx, buildID, types.ExecConfig{
AttachStderr: true,
AttachStdout: true,
Env: []string{
"APP_CACHE_DIR=" + cnbCacheDir,
"APP_IMAGE_URL=" + builder.ImageOutputName(),
"APP_PLATFORM_COMPONENT_TYPE=" + string(service.GetType()),
appVarAllowListKey + "=build-arg-1,override-1,run-build-arg-1,useroverride-1",
Expand All @@ -112,6 +118,7 @@ func TestCNBComponentBuild(t *testing.T) {
appVarPrefix + "useroverride-1=newval",
"BUILD_COMMAND=" + builder.buildCommandOverride,
"CNB_UPLOAD_RETRY=1",
"PREVIOUS_APP_IMAGE_URL=" + builder.ImageOutputName(),
"SOURCE_DIR=" + service.GetSourceDir(),
},
Cmd: []string{"sh", "-c", "/.app_platform/build.sh"},
Expand Down Expand Up @@ -176,6 +183,10 @@ func TestCNBComponentBuild(t *testing.T) {

mockClient.EXPECT().CopyToContainer(ctx, buildID, filepath.Clean("/"), gomock.Any(), gomock.Any()).Return(nil)

mockClient.EXPECT().ImageList(ctx, types.ImageListOptions{
Filters: filters.NewArgs(filters.Arg("reference", builder.ImageOutputName())),
}).Return([]types.ImageSummary{ /*no entries*/ }, nil)

execID := "exec-id"
mockClient.EXPECT().ContainerExecCreate(ctx, buildID, types.ExecConfig{
AttachStderr: true,
Expand Down
3 changes: 2 additions & 1 deletion internal/apps/builder/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type DockerEngineClient interface {
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)
ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error
ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error)
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error)
}
15 changes: 15 additions & 0 deletions internal/apps/builder/container_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/apps/builder/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func (b *DockerComponentBuilder) Build(ctx context.Context) (ComponentBuilderRes
b.ImageOutputName(),
},
BuildArgs: buildArgs,
NoCache: b.noCache,
}
dockerRes, err := b.cli.ImageBuild(ctx, tar, opts)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions internal/apps/builder/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func TestDockerComponentBuild(t *testing.T) {
component: service,
buildCommandOverride: "test",
logWriter: &logBuf,
noCache: true,
},
}

Expand All @@ -96,6 +97,7 @@ func TestDockerComponentBuild(t *testing.T) {
"override-1": strPtr("newval"),
"run-build-arg-1": strPtr("run-build-val-1"),
},
NoCache: true,
}).Return(types.ImageBuildResponse{
Body: ioutil.NopCloser(strings.NewReader("")),
}, nil)
Expand Down

0 comments on commit 2fa66bb

Please sign in to comment.