Skip to content

Commit 7ebdf44

Browse files
BrunoQuaresmajohnstcnmtojek
authored
feat: embed binary in image when pushing image (#234)
Embeds the envbuilder binary in the image under the same path when ENVBUILDER_PUSH_IMAGE is set. Co-authored-by: Cian Johnston <[email protected]> Co-authored-by: Marcin Tojek <[email protected]>
1 parent 82ffbc9 commit 7ebdf44

File tree

4 files changed

+110
-3
lines changed

4 files changed

+110
-3
lines changed

envbuilder.go

+38
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,29 @@ func Run(ctx context.Context, options Options) error {
404404
util.AddToDefaultIgnoreList(util.IgnoreListEntry{
405405
Path: ignorePath,
406406
PrefixMatchOnly: false,
407+
AllowedPaths: nil,
407408
})
408409
}
409410

411+
// In order to allow 'resuming' envbuilder, embed the binary into the image
412+
// if it is being pushed
413+
if options.PushImage {
414+
exePath, err := os.Executable()
415+
if err != nil {
416+
return xerrors.Errorf("get exe path: %w", err)
417+
}
418+
// Add an exception for the current running binary in kaniko ignore list
419+
if err := util.AddAllowedPathToDefaultIgnoreList(exePath); err != nil {
420+
return xerrors.Errorf("add exe path to ignore list: %w", err)
421+
}
422+
// Copy the envbuilder binary into the build context.
423+
buildParams.DockerfileContent += fmt.Sprintf("\nCOPY %s %s", exePath, exePath)
424+
dst := filepath.Join(buildParams.BuildContext, exePath)
425+
if err := copyFile(exePath, dst); err != nil {
426+
return xerrors.Errorf("copy running binary to build context: %w", err)
427+
}
428+
}
429+
410430
// temp move of all ro mounts
411431
tempRemountDest := filepath.Join("/", MagicDir, "mnt")
412432
ignorePrefixes := []string{tempRemountDest, "/proc", "/sys"}
@@ -1182,3 +1202,21 @@ func maybeDeleteFilesystem(log LoggerFunc, force bool) error {
11821202

11831203
return util.DeleteFilesystem()
11841204
}
1205+
1206+
func copyFile(src, dst string) error {
1207+
content, err := os.ReadFile(src)
1208+
if err != nil {
1209+
return xerrors.Errorf("read file failed: %w", err)
1210+
}
1211+
1212+
err = os.MkdirAll(filepath.Dir(dst), 0o755)
1213+
if err != nil {
1214+
return xerrors.Errorf("mkdir all failed: %w", err)
1215+
}
1216+
1217+
err = os.WriteFile(dst, content, 0o644)
1218+
if err != nil {
1219+
return xerrors.Errorf("write file failed: %w", err)
1220+
}
1221+
return nil
1222+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ toolchain go1.22.3
66

77
// There are a few options we need added to Kaniko!
88
// See: https://github.com/GoogleContainerTools/kaniko/compare/main...coder:kaniko:main
9-
replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240612094751-9d2f7eaa733c
9+
replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240624091120-7208a49f5b15
1010

1111
require (
1212
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
126126
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
127127
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
128128
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
129-
github.com/coder/kaniko v0.0.0-20240612094751-9d2f7eaa733c h1:m/cK7QW+IIydq+7zmuGesY1k6CEZlKooSF+KtIcXke8=
130-
github.com/coder/kaniko v0.0.0-20240612094751-9d2f7eaa733c/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM=
129+
github.com/coder/kaniko v0.0.0-20240624091120-7208a49f5b15 h1:Rne2frxrqtLEQ/v4f/wS550Yp/WXLCRFzDuxg8b9woM=
130+
github.com/coder/kaniko v0.0.0-20240624091120-7208a49f5b15/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM=
131131
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
132132
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
133133
github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc=

integration/integration_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/docker/docker/api/types"
3131
"github.com/docker/docker/api/types/container"
3232
"github.com/docker/docker/api/types/filters"
33+
"github.com/docker/docker/api/types/image"
3334
"github.com/docker/docker/api/types/mount"
3435
"github.com/docker/docker/api/types/volume"
3536
"github.com/docker/docker/client"
@@ -1396,6 +1397,74 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
13961397
})
13971398
}
13981399

1400+
func TestEmbedBinaryImage(t *testing.T) {
1401+
t.Parallel()
1402+
1403+
srv := createGitServer(t, gitServerOptions{
1404+
files: map[string]string{
1405+
".devcontainer/Dockerfile": fmt.Sprintf("FROM %s\nRUN date --utc > /root/date.txt", testImageAlpine),
1406+
".devcontainer/devcontainer.json": `{
1407+
"name": "Test",
1408+
"build": {
1409+
"dockerfile": "Dockerfile"
1410+
},
1411+
}`,
1412+
},
1413+
})
1414+
1415+
testReg := setupInMemoryRegistry(t, setupInMemoryRegistryOpts{})
1416+
testRepo := testReg + "/test-embed-binary-image"
1417+
ref, err := name.ParseReference(testRepo + ":latest")
1418+
require.NoError(t, err)
1419+
1420+
_, err = runEnvbuilder(t, options{env: []string{
1421+
envbuilderEnv("GIT_URL", srv.URL),
1422+
envbuilderEnv("CACHE_REPO", testRepo),
1423+
envbuilderEnv("PUSH_IMAGE", "1"),
1424+
}})
1425+
require.NoError(t, err)
1426+
1427+
_, err = remote.Image(ref)
1428+
require.NoError(t, err, "expected image to be present after build + push")
1429+
1430+
ctx := context.Background()
1431+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
1432+
require.NoError(t, err)
1433+
t.Cleanup(func() {
1434+
cli.Close()
1435+
})
1436+
1437+
// Pull the image we just built
1438+
rc, err := cli.ImagePull(ctx, ref.String(), image.PullOptions{})
1439+
require.NoError(t, err)
1440+
t.Cleanup(func() { _ = rc.Close() })
1441+
_, err = io.ReadAll(rc)
1442+
require.NoError(t, err)
1443+
1444+
// Run it
1445+
ctr, err := cli.ContainerCreate(ctx, &container.Config{
1446+
Image: ref.String(),
1447+
Cmd: []string{"sleep", "infinity"},
1448+
Labels: map[string]string{
1449+
testContainerLabel: "true",
1450+
},
1451+
}, nil, nil, nil, "")
1452+
require.NoError(t, err)
1453+
t.Cleanup(func() {
1454+
_ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
1455+
RemoveVolumes: true,
1456+
Force: true,
1457+
})
1458+
})
1459+
err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{})
1460+
require.NoError(t, err)
1461+
1462+
out := execContainer(t, ctr.ID, "[[ -f \"/.envbuilder/bin/envbuilder\" ]] && echo \"exists\"")
1463+
require.Equal(t, "exists", strings.TrimSpace(out))
1464+
out = execContainer(t, ctr.ID, "cat /root/date.txt")
1465+
require.NotEmpty(t, strings.TrimSpace(out))
1466+
}
1467+
13991468
func TestChownHomedir(t *testing.T) {
14001469
t.Parallel()
14011470

0 commit comments

Comments
 (0)