Skip to content

feat: set user, workdir, entrypoint when pushing image #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion envbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,11 @@ func Run(ctx context.Context, options Options) error {
return xerrors.Errorf("add exe path to ignore list: %w", err)
}
// Copy the envbuilder binary into the build context.
buildParams.DockerfileContent += fmt.Sprintf("\nCOPY %s %s", exePath, exePath)
buildParams.DockerfileContent += fmt.Sprintf(`
COPY --chmod=0755 %s %s
USER root
WORKDIR /
ENTRYPOINT [%q]`, exePath, exePath, exePath)
dst := filepath.Join(buildParams.BuildContext, exePath)
if err := copyFile(exePath, dst); err != nil {
return xerrors.Errorf("copy running binary to build context: %w", err)
Expand Down
117 changes: 48 additions & 69 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1138,16 +1138,63 @@ func TestPushImage(t *testing.T) {
require.NoError(t, err)

// Then: the image should be pushed
_, err = remote.Image(ref)
img, err := remote.Image(ref)
require.NoError(t, err, "expected image to be present after build + push")

// Then: the image should have its directives replaced with those required
// to run envbuilder automatically
configFile, err := img.ConfigFile()
require.NoError(t, err, "expected image to return a config file")

assert.Equal(t, "root", configFile.Config.User, "user must be root")
assert.Equal(t, "/", configFile.Config.WorkingDir, "workdir must be /")
if assert.Len(t, configFile.Config.Entrypoint, 1) {
assert.Equal(t, "/.envbuilder/bin/envbuilder", configFile.Config.Entrypoint[0], "incorrect entrypoint")
}

// Then: re-running envbuilder with GET_CACHED_IMAGE should succeed
_, err = runEnvbuilder(t, options{env: []string{
envbuilderEnv("GIT_URL", srv.URL),
envbuilderEnv("CACHE_REPO", testRepo),
envbuilderEnv("GET_CACHED_IMAGE", "1"),
}})
require.NoError(t, err)

// When: we pull the image we just built
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
require.NoError(t, err)
defer cli.Close()
rc, err := cli.ImagePull(ctx, ref.String(), image.PullOptions{})
require.NoError(t, err)
t.Cleanup(func() { _ = rc.Close() })
_, err = io.ReadAll(rc)
require.NoError(t, err)

// When: we run the image we just built
ctr, err := cli.ContainerCreate(ctx, &container.Config{
Image: ref.String(),
Entrypoint: []string{"sleep", "infinity"},
Labels: map[string]string{
testContainerLabel: "true",
},
}, nil, nil, nil, "")
require.NoError(t, err)
t.Cleanup(func() {
_ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
RemoveVolumes: true,
Force: true,
})
})
err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{})
require.NoError(t, err)

// Then: the envbuilder binary exists in the image!
out := execContainer(t, ctr.ID, "/.envbuilder/bin/envbuilder --help")
require.Regexp(t, `(?s)^USAGE:\s+envbuilder`, strings.TrimSpace(out))
out = execContainer(t, ctr.ID, "cat /root/date.txt")
require.NotEmpty(t, strings.TrimSpace(out))
})

t.Run("CacheAndPushAuth", func(t *testing.T) {
Expand Down Expand Up @@ -1397,74 +1444,6 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
})
}

func TestEmbedBinaryImage(t *testing.T) {
t.Parallel()

srv := createGitServer(t, gitServerOptions{
files: map[string]string{
".devcontainer/Dockerfile": fmt.Sprintf("FROM %s\nRUN date --utc > /root/date.txt", testImageAlpine),
".devcontainer/devcontainer.json": `{
"name": "Test",
"build": {
"dockerfile": "Dockerfile"
},
}`,
},
})

testReg := setupInMemoryRegistry(t, setupInMemoryRegistryOpts{})
testRepo := testReg + "/test-embed-binary-image"
ref, err := name.ParseReference(testRepo + ":latest")
require.NoError(t, err)

_, err = runEnvbuilder(t, options{env: []string{
envbuilderEnv("GIT_URL", srv.URL),
envbuilderEnv("CACHE_REPO", testRepo),
envbuilderEnv("PUSH_IMAGE", "1"),
}})
require.NoError(t, err)

_, err = remote.Image(ref)
require.NoError(t, err, "expected image to be present after build + push")

ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
require.NoError(t, err)
t.Cleanup(func() {
cli.Close()
})

// Pull the image we just built
rc, err := cli.ImagePull(ctx, ref.String(), image.PullOptions{})
require.NoError(t, err)
t.Cleanup(func() { _ = rc.Close() })
_, err = io.ReadAll(rc)
require.NoError(t, err)

// Run it
ctr, err := cli.ContainerCreate(ctx, &container.Config{
Image: ref.String(),
Cmd: []string{"sleep", "infinity"},
Labels: map[string]string{
testContainerLabel: "true",
},
}, nil, nil, nil, "")
require.NoError(t, err)
t.Cleanup(func() {
_ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
RemoveVolumes: true,
Force: true,
})
})
err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{})
require.NoError(t, err)

out := execContainer(t, ctr.ID, "[[ -f \"/.envbuilder/bin/envbuilder\" ]] && echo \"exists\"")
require.Equal(t, "exists", strings.TrimSpace(out))
out = execContainer(t, ctr.ID, "cat /root/date.txt")
require.NotEmpty(t, strings.TrimSpace(out))
}

func TestChownHomedir(t *testing.T) {
t.Parallel()

Expand Down