Skip to content

Commit 2b47efa

Browse files
authored
Merge pull request #1245 from bkneis/POD-823/cache
Pod 823/cache
2 parents cf2e086 + aa1e749 commit 2b47efa

File tree

24 files changed

+286
-33
lines changed

24 files changed

+286
-33
lines changed

cmd/agent/container/setup.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ func dockerlessBuild(
302302
args = append(args, parseIgnorePaths(dockerlessOptions.IgnorePaths)...)
303303
args = append(args, "--build-arg", "TARGETOS="+runtime.GOOS)
304304
args = append(args, "--build-arg", "TARGETARCH="+runtime.GOARCH)
305+
if dockerlessOptions.RegistryCache != "" {
306+
log.Debug("Appending registry cache to dockerless build arguments ", dockerlessOptions.RegistryCache)
307+
args = append(args, "--registry-cache", dockerlessOptions.RegistryCache)
308+
}
305309

306310
// ignore mounts
307311
args = append(args, "--ignore-path", setupInfo.SubstitutionContext.ContainerWorkspaceFolder)

cmd/agent/workspace/build.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ func (cmd *BuildCmd) Run(ctx context.Context) error {
8181
for _, platform := range platforms {
8282
// build the image
8383
imageName, err := runner.Build(ctx, provider2.BuildOptions{
84-
CLIOptions: workspaceInfo.CLIOptions,
85-
86-
Platform: platform,
84+
CLIOptions: workspaceInfo.CLIOptions,
85+
RegistryCache: workspaceInfo.RegistryCache,
86+
Platform: platform,
87+
ExportCache: true,
8788
})
8889
if err != nil {
8990
logger.Errorf("Error building image: %v", err)

cmd/agent/workspace/up.go

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ func (cmd *UpCmd) devPodUp(ctx context.Context, workspaceInfo *provider2.AgentWo
126126

127127
// start the devcontainer
128128
result, err := runner.Up(ctx, devcontainer.UpOptions{
129-
CLIOptions: workspaceInfo.CLIOptions,
129+
CLIOptions: workspaceInfo.CLIOptions,
130+
RegistryCache: workspaceInfo.RegistryCache,
130131
}, workspaceInfo.InjectTimeout)
131132
if err != nil {
132133
return nil, err
@@ -206,7 +207,7 @@ func initWorkspace(ctx context.Context, cancel context.CancelFunc, workspaceInfo
206207
}
207208

208209
// install docker in background
209-
errChan := make(chan error, 1)
210+
errChan := make(chan error, 2)
210211
go func() {
211212
if !workspaceInfo.Agent.IsDockerDriver() || workspaceInfo.Agent.Docker.Install == "false" {
212213
errChan <- nil
@@ -229,12 +230,28 @@ func initWorkspace(ctx context.Context, cancel context.CancelFunc, workspaceInfo
229230
}
230231
}
231232

232-
// wait until docker is installed
233+
// wait until docker is installed before configuring docker daemon
233234
err = <-errChan
234235
if err != nil {
235236
return nil, nil, "", errors.Wrap(err, "install docker")
236237
}
237238

239+
// If we are provisioning the machine, ensure the daemon has required options
240+
local, err := workspaceInfo.Agent.Local.Bool()
241+
if workspaceInfo.Agent.IsDockerDriver() && err != nil && !local {
242+
errChan <- configureDockerDaemon(ctx, logger)
243+
} else {
244+
logger.Debug("Skipping configuring daemon")
245+
errChan <- nil
246+
}
247+
248+
// wait until docker daemon is configured
249+
err = <-errChan
250+
if err != nil {
251+
logger.Warn("Could not find docker daemon config file, if using the registry cache, please ensure the daemon is configured with containerd-snapshotter=true")
252+
logger.Warn("More info at https://docs.docker.com/engine/storage/containerd/")
253+
}
254+
238255
return tunnelClient, logger, dockerCredentialsDir, nil
239256
}
240257

@@ -410,7 +427,7 @@ func prepareImage(workspaceDir, image string) error {
410427
return nil
411428
}
412429

413-
func installDocker(log log.Logger) error {
430+
func installDocker(log log.Logger) (err error) {
414431
if !command.Exists("docker") {
415432
writer := log.Writer(logrus.InfoLevel, false)
416433
defer writer.Close()
@@ -420,11 +437,33 @@ func installDocker(log log.Logger) error {
420437
shellCommand := exec.Command("sh", "-c", scripts.InstallDocker)
421438
shellCommand.Stdout = writer
422439
shellCommand.Stderr = writer
423-
err := shellCommand.Run()
424-
if err != nil {
440+
err = shellCommand.Run()
441+
}
442+
return err
443+
}
444+
445+
func configureDockerDaemon(ctx context.Context, log log.Logger) (err error) {
446+
log.Info("Configuring docker daemon ...")
447+
// Enable image snapshotter in the dameon
448+
var daemonConfig = []byte(`{
449+
"features": {
450+
"containerd-snapshotter": true
451+
}
452+
}`)
453+
// Check rootless docker
454+
homeDir, err := os.UserHomeDir()
455+
if err != nil {
456+
return err
457+
}
458+
if _, err = os.Stat(fmt.Sprintf("%s/.config/docker", homeDir)); !errors.Is(err, os.ErrNotExist) {
459+
err = os.WriteFile(fmt.Sprintf("%s/.config/docker/daemon.json", homeDir), daemonConfig, 0644)
460+
}
461+
// otherwise assume default
462+
if err != nil {
463+
if err = os.WriteFile("/etc/docker/daemon.json", daemonConfig, 0644); err != nil {
425464
return err
426465
}
427466
}
428-
429-
return nil
467+
// reload docker daemon
468+
return exec.CommandContext(ctx, "pkill", "-HUP", "dockerd").Run()
430469
}

e2e/tests/build/build.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,18 @@ var _ = DevPodDescribe("devpod build test suite", func() {
5757
err = f.DevPodBuild(ctx, tempDir, "--force-build", "--platform", "linux/amd64,linux/arm64", "--repository", prebuildRepo, "--skip-push")
5858
framework.ExpectNoError(err)
5959

60+
// parse the dockerfile
61+
file, err := dockerfile.Parse(modifiedDockerfileContents)
62+
framework.ExpectNoError(err)
63+
info := &config.ImageBuildInfo{Dockerfile: file}
64+
6065
// make sure images are there
61-
prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default)
66+
prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, info, log.Default)
6267
framework.ExpectNoError(err)
6368
_, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false)
6469
framework.ExpectNoError(err)
6570

66-
prebuildHash, err = config.CalculatePrebuildHash(cfg, "linux/arm64", "arm64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default)
71+
prebuildHash, err = config.CalculatePrebuildHash(cfg, "linux/arm64", "arm64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, info, log.Default)
6772
framework.ExpectNoError(err)
6873
_, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false)
6974
framework.ExpectNoError(err)
@@ -98,8 +103,13 @@ var _ = DevPodDescribe("devpod build test suite", func() {
98103
err = f.DevPodBuild(ctx, tempDir, "--skip-push")
99104
framework.ExpectNoError(err)
100105

106+
// parse the dockerfile
107+
file, err := dockerfile.Parse(modifiedDockerfileContents)
108+
framework.ExpectNoError(err)
109+
info := &config.ImageBuildInfo{Dockerfile: file}
110+
101111
// make sure images are there
102-
prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default)
112+
prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, info, log.Default)
103113
framework.ExpectNoError(err)
104114
_, err = dockerHelper.InspectImage(ctx, dockerdriver.GetImageName(tempDir, prebuildHash), false)
105115
framework.ExpectNoError(err)
@@ -154,8 +164,13 @@ var _ = DevPodDescribe("devpod build test suite", func() {
154164
err = f.DevPodBuild(ctx, tempDir, "--force-build", "--force-internal-buildkit", "--repository", prebuildRepo, "--skip-push")
155165
framework.ExpectNoError(err)
156166

167+
// parse the dockerfile
168+
file, err := dockerfile.Parse(modifiedDockerfileContents)
169+
framework.ExpectNoError(err)
170+
info := &config.ImageBuildInfo{Dockerfile: file}
171+
157172
// make sure images are there
158-
prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default)
173+
prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, info, log.Default)
159174
framework.ExpectNoError(err)
160175

161176
_, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false)

examples/build/.devcontainer.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "My dev env",
3+
"build": {
4+
"context": ".",
5+
"dockerfile": "./Dockerfile"
6+
},
7+
"features":{
8+
"ghcr.io/devcontainers/features/github-cli:1": {}
9+
}
10+
}

examples/build/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM mcr.microsoft.com/devcontainers/go:1.22-bullseye
2+
3+
ARG TARGETOS
4+
ARG TARGETARCH
5+
6+
# Install Node.js
7+
RUN \
8+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
9+
&& apt-get update \
10+
&& apt-get install -y --no-install-recommends nodejs \
11+
&& apt-get clean \
12+
&& rm -rf /var/lib/apt/lists/*
13+
14+
# Set environment variables for Rust
15+
ENV RUSTUP_HOME=/usr/local/rustup \
16+
CARGO_HOME=/usr/local/cargo \
17+
PATH=/usr/local/cargo/bin:$PATH \
18+
RUST_VERSION=1.69.0
19+
20+
# Install Protobuf compiler
21+
RUN \
22+
apt-get update \
23+
&& apt-get install -y --no-install-recommends protobuf-compiler \
24+
&& apt-get clean \
25+
&& rm -rf /var/lib/apt/lists/*
26+
27+
COPY app /app
28+
COPY files /files
29+
30+
RUN echo hello

examples/build/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## Build Example
2+
3+
This folder holds a super simple devcontainer configuration that builds a local Dockerfile with a devcontainer feature. You can start this project via:
4+
```
5+
devpod up ./examples/build
6+
```

pkg/client/clientimplementation/workspace_client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ func (s *workspaceClient) agentInfo(cliOptions provider.CLIOptions) (string, *pr
203203
// Get the timeout from the context options
204204
agentInfo.InjectTimeout = config.ParseTimeOption(s.devPodConfig, config.ContextOptionAgentInjectTimeout)
205205

206+
// Set registry cache from context option
207+
agentInfo.RegistryCache = s.devPodConfig.ContextOption(config.ContextOptionRegistryCache)
208+
206209
// marshal config
207210
out, err := json.Marshal(agentInfo)
208211
if err != nil {

pkg/config/context.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
ContextOptionSSHAgentForwarding = "SSH_AGENT_FORWARDING"
1515
ContextOptionSSHConfigPath = "SSH_CONFIG_PATH"
1616
ContextOptionAgentInjectTimeout = "AGENT_INJECT_TIMEOUT"
17+
ContextOptionRegistryCache = "REGISTRY_CACHE"
1718
)
1819

1920
var ContextOptions = []ContextOption{
@@ -86,4 +87,9 @@ var ContextOptions = []ContextOption{
8687
Description: "Specifies the timeout to inject the agent",
8788
Default: "20",
8889
},
90+
{
91+
Name: ContextOptionRegistryCache,
92+
Description: "Specifies the registry to use as a build cache, e.g. gcr.io/my-project/my-dev-env",
93+
Default: "",
94+
},
8995
}

pkg/devcontainer/build.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ func (r *runner) build(
103103
ImageMetadata: imageMetadata,
104104
ImageName: overrideBuildImageName,
105105
PrebuildHash: imageTag,
106+
RegistryCache: options.RegistryCache,
106107
}, nil
107108
}
108109

@@ -133,6 +134,7 @@ func (r *runner) extendImage(
133134
ImageDetails: imageBuildInfo.ImageDetails,
134135
ImageMetadata: extendedBuildInfo.MetadataConfig,
135136
ImageName: imageBase,
137+
RegistryCache: options.RegistryCache,
136138
}, nil
137139
}
138140

@@ -287,7 +289,7 @@ func (r *runner) buildImage(
287289
return nil, err
288290
}
289291

290-
prebuildHash, err := config.CalculatePrebuildHash(parsedConfig.Config, options.Platform, targetArch, config.GetContextPath(parsedConfig.Config), dockerfilePath, dockerfileContent, r.Log)
292+
prebuildHash, err := config.CalculatePrebuildHash(parsedConfig.Config, options.Platform, targetArch, config.GetContextPath(parsedConfig.Config), dockerfilePath, dockerfileContent, buildInfo, r.Log)
291293
if err != nil {
292294
return nil, err
293295
}
@@ -319,6 +321,7 @@ func (r *runner) buildImage(
319321
ImageMetadata: extendedBuildInfo.MetadataConfig,
320322
ImageName: prebuildImage,
321323
PrebuildHash: prebuildHash,
324+
RegistryCache: options.RegistryCache,
322325
}, nil
323326
} else if err != nil {
324327
r.Log.Debugf("Error trying to find prebuild image %s: %v", prebuildImage, err)
@@ -333,7 +336,7 @@ func (r *runner) buildImage(
333336
return nil, fmt.Errorf("cannot build devcontainer because driver is non-docker and dockerless fallback is disabled")
334337
}
335338

336-
return dockerlessFallback(r.LocalWorkspaceFolder, substitutionContext.ContainerWorkspaceFolder, parsedConfig, buildInfo, extendedBuildInfo, dockerfileContent)
339+
return dockerlessFallback(r.LocalWorkspaceFolder, substitutionContext.ContainerWorkspaceFolder, parsedConfig, buildInfo, extendedBuildInfo, dockerfileContent, options)
337340
}
338341

339342
return dockerDriver.BuildDevContainer(ctx, prebuildHash, parsedConfig, extendedBuildInfo, dockerfilePath, dockerfileContent, r.LocalWorkspaceFolder, options)
@@ -346,6 +349,7 @@ func dockerlessFallback(
346349
buildInfo *config.ImageBuildInfo,
347350
extendedBuildInfo *feature.ExtendedBuildInfo,
348351
dockerfileContent string,
352+
options provider.BuildOptions,
349353
) (*config.BuildInfo, error) {
350354
contextPath := config.GetContextPath(parsedConfig.Config)
351355
devPodInternalFolder := filepath.Join(contextPath, config.DevPodContextFeatureFolder)
@@ -380,6 +384,7 @@ func dockerlessFallback(
380384

381385
User: buildInfo.User,
382386
},
387+
RegistryCache: options.RegistryCache,
383388
}, nil
384389
}
385390

pkg/devcontainer/build/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type BuildOptions struct {
88

99
Images []string
1010
CacheFrom []string
11+
CacheTo []string
1112

1213
Dockerfile string
1314
Context string

pkg/devcontainer/buildkit/buildkit.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func Build(ctx context.Context, client *buildkit.Client, writer io.Writer, platf
2828
if err != nil {
2929
return err
3030
}
31+
cacheTo, err := ParseCacheEntry(options.CacheTo)
32+
if err != nil {
33+
return err
34+
}
3135

3236
// is context stream?
3337
attachable := []session.Attachable{}
@@ -42,6 +46,7 @@ func Build(ctx context.Context, client *buildkit.Client, writer io.Writer, platf
4246
},
4347
Session: attachable,
4448
CacheImports: cacheFrom,
49+
CacheExports: cacheTo,
4550
}
4651

4752
// set options target

pkg/devcontainer/config/build.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type BuildInfo struct {
2121
ImageMetadata *ImageMetadataConfig
2222
ImageName string
2323
PrebuildHash string
24+
RegistryCache string
2425

2526
Dockerless *BuildInfoDockerless
2627
}

pkg/devcontainer/config/prebuild.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import (
1515
"github.com/loft-sh/log/hash"
1616
)
1717

18-
func CalculatePrebuildHash(originalConfig *DevContainerConfig, platform, architecture, contextPath, dockerfilePath, dockerfileContent string, log log.Logger) (string, error) {
18+
func CalculatePrebuildHash(
19+
originalConfig *DevContainerConfig,
20+
platform, architecture, contextPath, dockerfilePath, dockerfileContent string,
21+
buildInfo *ImageBuildInfo,
22+
log log.Logger) (string, error) {
1923
parsedConfig := CloneDevContainerConfig(originalConfig)
2024

2125
if platform != "" {
@@ -57,8 +61,17 @@ func CalculatePrebuildHash(originalConfig *DevContainerConfig, platform, archite
5761
}
5862
excludes = append(excludes, DevPodContextFeatureFolder+"/")
5963

64+
// find exact files to hash
65+
// todo pass down target or search all
66+
// todo update DirectoryHash function
67+
var includes []string
68+
if buildInfo.Dockerfile != nil {
69+
includes = buildInfo.Dockerfile.BuildContextFiles()
70+
}
71+
log.Debug("Build context files to use for hash are ", includes)
72+
6073
// get hash of the context directory
61-
contextHash, err := util.DirectoryHash(contextPath, excludes)
74+
contextHash, err := util.DirectoryHash(contextPath, excludes, includes)
6275
if err != nil {
6376
return "", err
6477
}

pkg/devcontainer/run.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ type runner struct {
8585
type UpOptions struct {
8686
provider2.CLIOptions
8787

88-
NoBuild bool
89-
ForceBuild bool
88+
NoBuild bool
89+
ForceBuild bool
90+
RegistryCache string
9091
}
9192

9293
func (r *runner) Up(ctx context.Context, options UpOptions, timeout time.Duration) (*config.Result, error) {

0 commit comments

Comments
 (0)