From 31e0eab192ca9167f44748c206a1e72dfdfbf65a Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 03:01:42 +0000 Subject: [PATCH 01/15] fix: pass through signals to inner container --- cli/docker.go | 96 +++++++++++++++++++++++++++++--------- dockerutil/container.go | 13 ++++-- dockerutil/exec.go | 45 +++++++++++++----- dockerutil/image.go | 7 ++- integration/docker_test.go | 66 ++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 41 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 40d433e..3cf018e 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -7,11 +7,13 @@ import ( "io" "net/url" "os" + "os/signal" "path" "path/filepath" "sort" "strconv" "strings" + "syscall" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -647,7 +649,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker bootDir := filepath.Join(imgMeta.HomeDir, ".coder") blog.Infof("Creating %q directory to host Coder assets...", bootDir) - _, err = dockerutil.ExecContainer(ctx, client, dockerutil.ExecConfig{ + _, _, err = dockerutil.ExecContainer(ctx, client, dockerutil.ExecConfig{ ContainerID: containerID, User: imgMeta.UID, Cmd: "mkdir", @@ -677,31 +679,79 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker blog.Info("Envbox startup complete!") - // The bootstrap script doesn't return since it execs the agent - // meaning that it can get pretty noisy if we were to log by default. - // In order to allow users to discern issues getting the bootstrap script - // to complete successfully we pipe the output to stdout if - // CODER_DEBUG=true. - debugWriter := io.Discard - if flags.debug { - debugWriter = os.Stdout - } - // Bootstrap the container if a script has been provided. - blog.Infof("Bootstrapping workspace...") - err = dockerutil.BootstrapContainer(ctx, client, dockerutil.BootstrapConfig{ - ContainerID: containerID, - User: imgMeta.UID, - Script: flags.boostrapScript, - // We set this because the default behavior is to download the agent - // to /tmp/coder.XXXX. This causes a race to happen where we finish - // downloading the binary but before we can execute systemd remounts - // /tmp. - Env: []string{fmt.Sprintf("BINARY_DIR=%s", bootDir)}, - StdOutErr: debugWriter, + bootstrapExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ + User: imgMeta.UID, + Cmd: []string{"/bin/sh", "-s"}, + Env: []string{fmt.Sprintf("BINARY_DIR=%s", bootDir)}, + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Detach: true, }) if err != nil { - return xerrors.Errorf("boostrap container: %w", err) + return xerrors.Errorf("create exec: %w", err) + } + resp, err := client.ContainerExecAttach(ctx, bootstrapExec.ID, dockertypes.ExecStartCheck{}) + if err != nil { + return xerrors.Errorf("attach exec: %w", err) + } + + _, err = io.Copy(resp.Conn, strings.NewReader(flags.boostrapScript)) + if err != nil { + return xerrors.Errorf("copy stdin: %w", err) } + err = resp.CloseWrite() + if err != nil { + return xerrors.Errorf("close write: %w", err) + } + + go func() { + defer resp.Close() + rd := io.LimitReader(resp.Reader, 1<<10) + _, err := io.Copy(blog, rd) + if err != nil { + log.Error(ctx, "copy bootstrap output", slog.Error(err)) + } + }() + + inspect, err := client.ContainerExecInspect(ctx, bootstrapExec.ID) + if err != nil { + return xerrors.Errorf("exec inspect: %w", err) + } + + go func() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + sig := <-sigs + sigstr := "TERM" + if sig == syscall.SIGINT { + sigstr = "INT" + } + + killExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ + User: imgMeta.UID, + Cmd: []string{"sh", "-c", fmt.Sprintf("kill -%s %d", sigstr, inspect.Pid)}, + AttachStdout: true, + AttachStderr: true, + }) + if err != nil { + log.Error(ctx, "create kill exec", slog.Error(err)) + return + } + + err = dockerutil.WaitForExit(ctx, client, killExec.ID) + if err != nil { + log.Error(ctx, "wait for kill exec to complete", slog.Error(err)) + return + } + + err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) + if err != nil { + log.Error(ctx, "wait for bootstrap to exit", slog.Error(err)) + os.Exit(1) + } + os.Exit(0) + }() return nil } diff --git a/dockerutil/container.go b/dockerutil/container.go index a10a371..4c72c89 100644 --- a/dockerutil/container.go +++ b/dockerutil/container.go @@ -113,8 +113,8 @@ func BootstrapContainer(ctx context.Context, client DockerClient, conf Bootstrap var err error for r, n := retry.New(time.Second, time.Second*2), 0; r.Wait(ctx) && n < 10; n++ { - var out []byte - out, err = ExecContainer(ctx, client, ExecConfig{ + var out io.Reader + out, _, err = ExecContainer(ctx, client, ExecConfig{ ContainerID: conf.ContainerID, User: conf.User, Cmd: "/bin/sh", @@ -122,9 +122,16 @@ func BootstrapContainer(ctx context.Context, client DockerClient, conf Bootstrap Stdin: strings.NewReader(conf.Script), Env: conf.Env, StdOutErr: conf.StdOutErr, + Detach: conf.Detach, }) if err != nil { - err = xerrors.Errorf("boostrap container (%s): %w", out, err) + output, rerr := io.ReadAll(out) + if rerr != nil { + err = xerrors.Errorf("read all: %w", err) + continue + } + + err = xerrors.Errorf("boostrap container (%s): %w", output, err) continue } break diff --git a/dockerutil/exec.go b/dockerutil/exec.go index 6f78822..57b0b1c 100644 --- a/dockerutil/exec.go +++ b/dockerutil/exec.go @@ -4,11 +4,13 @@ import ( "bytes" "context" "io" + "time" dockertypes "github.com/docker/docker/api/types" "golang.org/x/xerrors" "github.com/coder/envbox/xio" + "github.com/coder/retry" ) type ExecConfig struct { @@ -24,9 +26,9 @@ type ExecConfig struct { // ExecContainer runs a command in a container. It returns the output and any error. // If an error occurs during the execution of the command, the output is appended to the error. -func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) ([]byte, error) { +func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) (io.Reader, int, error) { exec, err := client.ContainerExecCreate(ctx, config.ContainerID, dockertypes.ExecConfig{ - Detach: true, + Detach: config.Detach, Cmd: append([]string{config.Cmd}, config.Args...), User: config.User, AttachStderr: true, @@ -35,23 +37,23 @@ func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) Env: config.Env, }) if err != nil { - return nil, xerrors.Errorf("exec create: %w", err) + return nil, 0, xerrors.Errorf("exec create: %w", err) } resp, err := client.ContainerExecAttach(ctx, exec.ID, dockertypes.ExecStartCheck{}) if err != nil { - return nil, xerrors.Errorf("attach to exec: %w", err) + return nil, 0, xerrors.Errorf("attach to exec: %w", err) } defer resp.Close() if config.Stdin != nil { _, err = io.Copy(resp.Conn, config.Stdin) if err != nil { - return nil, xerrors.Errorf("copy stdin: %w", err) + return nil, 0, xerrors.Errorf("copy stdin: %w", err) } err = resp.CloseWrite() if err != nil { - return nil, xerrors.Errorf("close write: %w", err) + return nil, 0, xerrors.Errorf("close write: %w", err) } } @@ -73,22 +75,43 @@ func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) _, err = io.Copy(wr, resp.Reader) if err != nil { - return nil, xerrors.Errorf("copy cmd output: %w", err) + return nil, 0, xerrors.Errorf("copy cmd output: %w", err) } resp.Close() inspect, err := client.ContainerExecInspect(ctx, exec.ID) if err != nil { - return nil, xerrors.Errorf("exec inspect: %w", err) + return nil, 0, xerrors.Errorf("exec inspect: %w", err) } if inspect.Running { - return nil, xerrors.Errorf("unexpectedly still running") + return nil, 0, xerrors.Errorf("unexpectedly still running") } if inspect.ExitCode > 0 { - return nil, xerrors.Errorf("%s: exit code %d", buf.Bytes(), inspect.ExitCode) + return nil, 0, xerrors.Errorf("%s: exit code %d", buf.Bytes(), inspect.ExitCode) } - return buf.Bytes(), nil + return &buf, inspect.Pid, nil +} + +func WaitForExit(ctx context.Context, client DockerClient, execID string) error { + for r := retry.New(time.Second, time.Second); r.Wait(ctx); { + inspect, err := client.ContainerExecInspect(ctx, execID) + if err != nil { + return xerrors.Errorf("exec inspect: %w", err) + } + + if inspect.Running { + continue + } + + if inspect.ExitCode > 0 { + return xerrors.Errorf("exit code %d", inspect.ExitCode) + } + + return nil + } + + return ctx.Err() } diff --git a/dockerutil/image.go b/dockerutil/image.go index fb1cfaf..257cf50 100644 --- a/dockerutil/image.go +++ b/dockerutil/image.go @@ -1,7 +1,6 @@ package dockerutil import ( - "bytes" "context" "encoding/json" "fmt" @@ -192,14 +191,14 @@ func GetImageMetadata(ctx context.Context, client DockerClient, image, username return ImageMetadata{}, xerrors.Errorf("CVMs do not support NFS volumes") } - _, err = ExecContainer(ctx, client, ExecConfig{ + _, _, err = ExecContainer(ctx, client, ExecConfig{ ContainerID: inspect.ID, Cmd: "stat", Args: []string{"/sbin/init"}, }) initExists := err == nil - out, err := ExecContainer(ctx, client, ExecConfig{ + out, _, err := ExecContainer(ctx, client, ExecConfig{ ContainerID: inspect.ID, Cmd: "getent", Args: []string{"passwd", username}, @@ -208,7 +207,7 @@ func GetImageMetadata(ctx context.Context, client DockerClient, image, username return ImageMetadata{}, xerrors.Errorf("get /etc/passwd entry for %s: %w", username, err) } - users, err := xunix.ParsePasswd(bytes.NewReader(out)) + users, err := xunix.ParsePasswd(out) if err != nil { return ImageMetadata{}, xerrors.Errorf("parse passwd entry for (%s): %w", out, err) } diff --git a/integration/docker_test.go b/integration/docker_test.go index fc1b7af..b4db261 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -272,6 +272,54 @@ func TestDocker(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedHostname, strings.TrimSpace(string(hostname))) }) + + t.Run("HandleSignals", func(t *testing.T) { + t.Parallel() + + pool, err := dockertest.NewPool("") + require.NoError(t, err) + + var ( + tmpdir = integrationtest.TmpDir(t) + binds = integrationtest.DefaultBinds(t, tmpdir) + ) + homeDir := filepath.Join(tmpdir, "home") + err = os.MkdirAll(homeDir, 0o777) + require.NoError(t, err) + + binds = append(binds, bindMount(homeDir, "/home/coder", false)) + + // Run the envbox container. + resource := integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ + Image: integrationtest.UbuntuImage, + Username: "root", + Binds: binds, + BootstrapScript: sigtrapScript, + }) + + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"/bin/sh", "-c", "stat /home/coder/foo"}, + }) + require.Error(t, err) + + err = resource.Close() + require.NoError(t, err) + + // Run the envbox container. + resource = integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ + Image: integrationtest.UbuntuImage, + Username: "root", + Binds: binds, + BootstrapScript: sigtrapScript, + }) + t.Logf("envbox %q started successfully, recreating...", resource.Container.ID) + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"/bin/sh", "-c", "stat /home/coder/foo"}, + }) + require.NoError(t, err) + }) } func requireSliceNoContains(t *testing.T, ss []string, els ...string) { @@ -303,3 +351,21 @@ func bindMount(src, dest string, ro bool) string { } return fmt.Sprintf("%s:%s", src, dest) } + +const sigtrapScript = `#!/bin/bash + +# Function to handle cleanup +cleanup() { + touch /home/coder/foo + exit 0 +} + +# Trap SIGINT (Ctrl+C) and SIGTERM signals +trap 'cleanup' SIGINT SIGTERM + +# Main loop or processing logic (replace with your script's logic) +while true; do + echo "Working..." + sleep 1 +done +` From 844e438a54f897d76513dedb58805ce91db7c3c0 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 03:14:42 +0000 Subject: [PATCH 02/15] stop container first --- integration/docker_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration/docker_test.go b/integration/docker_test.go index b4db261..a76c9c1 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -303,6 +303,9 @@ func TestDocker(t *testing.T) { }) require.Error(t, err) + err = pool.Client.StopContainer(resource.Container.ID, 10) + require.NoError(t, err) + err = resource.Close() require.NoError(t, err) From bd95b76136c494edc5954add3f2345696b22cf80 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 04:23:37 +0000 Subject: [PATCH 03/15] add some debug logging --- cli/docker.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/docker.go b/cli/docker.go index 3cf018e..30f82f6 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -722,11 +722,13 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + log.Info(ctx, "waiting for signal") sig := <-sigs sigstr := "TERM" if sig == syscall.SIGINT { sigstr = "INT" } + log.Debug(ctx, "received signal", slog.F("signal", sigstr)) killExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ User: imgMeta.UID, @@ -746,8 +748,8 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker } err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) + log.Info(ctx, "exiting envbox", slog.Error(err)) if err != nil { - log.Error(ctx, "wait for bootstrap to exit", slog.Error(err)) os.Exit(1) } os.Exit(0) From 1bb519302d539310c873c1fc2f34d69051f25d2d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 04:41:55 +0000 Subject: [PATCH 04/15] try --- cli/docker.go | 19 ++++++++++--------- integration/docker_test.go | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 30f82f6..afe15aa 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -20,6 +20,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/spf13/cobra" "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "cdr.dev/slog" @@ -719,7 +720,8 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker return xerrors.Errorf("exec inspect: %w", err) } - go func() { + var eg errgroup.Group + eg.Go(func() error { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) log.Info(ctx, "waiting for signal") @@ -737,25 +739,24 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker AttachStderr: true, }) if err != nil { - log.Error(ctx, "create kill exec", slog.Error(err)) - return + return xerrors.Errorf("create kill exec: %w", err) } err = dockerutil.WaitForExit(ctx, client, killExec.ID) if err != nil { - log.Error(ctx, "wait for kill exec to complete", slog.Error(err)) - return + return xerrors.Errorf("wait for kill exec to complete: %w", err) } err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) log.Info(ctx, "exiting envbox", slog.Error(err)) if err != nil { - os.Exit(1) + return xerrors.Errorf("wait for exit: %w", err) } - os.Exit(0) - }() - return nil + return nil + }) + + return eg.Wait() } //nolint:revive diff --git a/integration/docker_test.go b/integration/docker_test.go index a76c9c1..8d3445e 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -309,6 +309,7 @@ func TestDocker(t *testing.T) { err = resource.Close() require.NoError(t, err) + t.Logf("envbox %q started successfully, recreating...", resource.Container.ID) // Run the envbox container. resource = integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ Image: integrationtest.UbuntuImage, @@ -316,7 +317,6 @@ func TestDocker(t *testing.T) { Binds: binds, BootstrapScript: sigtrapScript, }) - t.Logf("envbox %q started successfully, recreating...", resource.Container.ID) _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ ContainerID: resource.Container.ID, Cmd: []string{"/bin/sh", "-c", "stat /home/coder/foo"}, From e6bea71f10fca9ef53031cd958e3c040a9959812 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 04:55:57 +0000 Subject: [PATCH 05/15] wtf --- cli/docker.go | 66 +++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index afe15aa..bfd18b0 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -20,7 +20,6 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/spf13/cobra" "golang.org/x/exp/slices" - "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "cdr.dev/slog" @@ -720,43 +719,48 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker return xerrors.Errorf("exec inspect: %w", err) } - var eg errgroup.Group - eg.Go(func() error { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - log.Info(ctx, "waiting for signal") - sig := <-sigs - sigstr := "TERM" - if sig == syscall.SIGINT { - sigstr = "INT" - } - log.Debug(ctx, "received signal", slog.F("signal", sigstr)) + go func() { + err := func() error { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + log.Info(ctx, "waiting for signal") + sig := <-sigs + sigstr := "TERM" + if sig == syscall.SIGINT { + sigstr = "INT" + } + log.Debug(ctx, "received signal", slog.F("signal", sigstr)) - killExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ - User: imgMeta.UID, - Cmd: []string{"sh", "-c", fmt.Sprintf("kill -%s %d", sigstr, inspect.Pid)}, - AttachStdout: true, - AttachStderr: true, - }) - if err != nil { - return xerrors.Errorf("create kill exec: %w", err) - } + killExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ + User: imgMeta.UID, + Cmd: []string{"sh", "-c", fmt.Sprintf("kill -%s %d", sigstr, inspect.Pid)}, + AttachStdout: true, + AttachStderr: true, + }) + if err != nil { + return xerrors.Errorf("create kill exec: %w", err) + } - err = dockerutil.WaitForExit(ctx, client, killExec.ID) - if err != nil { - return xerrors.Errorf("wait for kill exec to complete: %w", err) - } + err = dockerutil.WaitForExit(ctx, client, killExec.ID) + if err != nil { + return xerrors.Errorf("wait for kill exec to complete: %w", err) + } + + err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) + if err != nil { + return xerrors.Errorf("wait for exit: %w", err) + } - err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) + return nil + }() log.Info(ctx, "exiting envbox", slog.Error(err)) if err != nil { - return xerrors.Errorf("wait for exit: %w", err) + os.Exit(1) } + os.Exit(0) + }() - return nil - }) - - return eg.Wait() + return nil } //nolint:revive From bf3efb03708a0bd60023274f58f2b6c8cab8f884 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 05:05:00 +0000 Subject: [PATCH 06/15] hm --- cli/docker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/docker.go b/cli/docker.go index bfd18b0..20442e0 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -276,6 +276,7 @@ func dockerCmd() *cobra.Command { } }() + fmt.Println("HELLO????") log.Debug(ctx, "waiting for dockerd") // We wait for the daemon after spawning the goroutine in case @@ -751,6 +752,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker return xerrors.Errorf("wait for exit: %w", err) } + fmt.Println("WELL WE GOT HERE AT LEAST") return nil }() log.Info(ctx, "exiting envbox", slog.Error(err)) From c7d4a87c7bf33a761182cf4897c124b35daa08d8 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 05:11:18 +0000 Subject: [PATCH 07/15] idk --- cli/docker.go | 3 +-- integration/docker_test.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 20442e0..3977f2a 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -276,7 +276,6 @@ func dockerCmd() *cobra.Command { } }() - fmt.Println("HELLO????") log.Debug(ctx, "waiting for dockerd") // We wait for the daemon after spawning the goroutine in case @@ -752,10 +751,10 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker return xerrors.Errorf("wait for exit: %w", err) } - fmt.Println("WELL WE GOT HERE AT LEAST") return nil }() log.Info(ctx, "exiting envbox", slog.Error(err)) + log.Sync() if err != nil { os.Exit(1) } diff --git a/integration/docker_test.go b/integration/docker_test.go index 8d3445e..a6b01f3 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -303,7 +303,8 @@ func TestDocker(t *testing.T) { }) require.Error(t, err) - err = pool.Client.StopContainer(resource.Container.ID, 10) + time.Sleep(time.Second * 5) + err = pool.Client.StopContainer(resource.Container.ID, 30) require.NoError(t, err) err = resource.Close() From ee1e4c04b6cceb8c3d4d152a1ab647cc8f65ce87 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 05:13:01 +0000 Subject: [PATCH 08/15] i'm an idiot --- cli/docker.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 3977f2a..a267f20 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -718,11 +718,10 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker if err != nil { return xerrors.Errorf("exec inspect: %w", err) } - + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) go func() { err := func() error { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) log.Info(ctx, "waiting for signal") sig := <-sigs sigstr := "TERM" From 350c99513f161dba6d0b8d67ceb00ad53ff28745 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 05:24:50 +0000 Subject: [PATCH 09/15] hmmm --- cli/docker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/docker.go b/cli/docker.go index a267f20..76f877e 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "syscall" + "time" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -759,6 +760,8 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker } os.Exit(0) }() + log.Info(ctx, "HELP") + time.Sleep(time.Second) return nil } From 1e5fbce525389bca5a638cdf75448bfd4f4e0a43 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 3 Jul 2024 05:33:51 +0000 Subject: [PATCH 10/15] int --- integration/docker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/docker_test.go b/integration/docker_test.go index a6b01f3..2bde94e 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -365,7 +365,7 @@ cleanup() { } # Trap SIGINT (Ctrl+C) and SIGTERM signals -trap 'cleanup' SIGINT SIGTERM +trap 'cleanup' INT TERM # Main loop or processing logic (replace with your script's logic) while true; do From aeec951e00af4106c7a51393d889cb46e62de503 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 19 Jul 2024 18:20:49 +0000 Subject: [PATCH 11/15] add integration test --- cli/docker.go | 69 +++++++++------------------ cli/root.go | 4 +- cmd/envbox/main.go | 27 +++++++++-- dockerutil/exec.go | 18 ++++++- integration/docker_test.go | 14 +++--- integration/integrationtest/docker.go | 27 ++++++++++- 6 files changed, 97 insertions(+), 62 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 76f877e..b185acd 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -7,14 +7,12 @@ import ( "io" "net/url" "os" - "os/signal" + "os/exec" "path" "path/filepath" "sort" "strconv" "strings" - "syscall" - "time" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -148,7 +146,7 @@ type flags struct { ethlink string } -func dockerCmd() *cobra.Command { +func dockerCmd(ch chan func() error) *cobra.Command { var flags flags cmd := &cobra.Command{ @@ -287,7 +285,7 @@ func dockerCmd() *cobra.Command { return xerrors.Errorf("wait for dockerd: %w", err) } - err = runDockerCVM(ctx, log, client, blog, flags) + err = runDockerCVM(ctx, log, client, blog, ch, flags) if err != nil { // It's possible we failed because we ran out of disk while // pulling the image. We should restart the daemon and use @@ -316,7 +314,7 @@ func dockerCmd() *cobra.Command { }() log.Debug(ctx, "reattempting container creation") - err = runDockerCVM(ctx, log, client, blog, flags) + err = runDockerCVM(ctx, log, client, blog, ch, flags) } if err != nil { blog.Errorf("Failed to run envbox: %v", err) @@ -359,7 +357,7 @@ func dockerCmd() *cobra.Command { return cmd } -func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.DockerClient, blog buildlog.Logger, flags flags) error { +func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.DockerClient, blog buildlog.Logger, shutdownCh chan func() error, flags flags) error { fs := xunix.GetFS(ctx) // Set our OOM score to something really unfavorable to avoid getting killed @@ -692,6 +690,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker if err != nil { return xerrors.Errorf("create exec: %w", err) } + resp, err := client.ContainerExecAttach(ctx, bootstrapExec.ID, dockertypes.ExecStartCheck{}) if err != nil { return xerrors.Errorf("attach exec: %w", err) @@ -715,53 +714,31 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker } }() - inspect, err := client.ContainerExecInspect(ctx, bootstrapExec.ID) + // We can't just call ExecInspect because there's a race where the cmd + // hasn't been assigned a PID yet. + bootstrapPID, err := dockerutil.GetExecPID(ctx, client, bootstrapExec.ID) if err != nil { return xerrors.Errorf("exec inspect: %w", err) } - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - go func() { - err := func() error { - log.Info(ctx, "waiting for signal") - sig := <-sigs - sigstr := "TERM" - if sig == syscall.SIGINT { - sigstr = "INT" - } - log.Debug(ctx, "received signal", slog.F("signal", sigstr)) - killExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ - User: imgMeta.UID, - Cmd: []string{"sh", "-c", fmt.Sprintf("kill -%s %d", sigstr, inspect.Pid)}, - AttachStdout: true, - AttachStderr: true, - }) - if err != nil { - return xerrors.Errorf("create kill exec: %w", err) - } - - err = dockerutil.WaitForExit(ctx, client, killExec.ID) - if err != nil { - return xerrors.Errorf("wait for kill exec to complete: %w", err) - } + shutdownCh <- func() error { + log.Debug(ctx, "killing container", slog.F("bootstrap_pid", bootstrapPID)) - err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) - if err != nil { - return xerrors.Errorf("wait for exit: %w", err) - } + // The PID returned is the PID _outside_ the container... + out, err := exec.Command("kill", "-TERM", strconv.Itoa(bootstrapPID)).CombinedOutput() + if err != nil { + return xerrors.Errorf("kill bootstrap process (%s): %w", out, err) + } - return nil - }() - log.Info(ctx, "exiting envbox", slog.Error(err)) - log.Sync() + log.Debug(ctx, "sent kill signal waiting for process to exit") + err = dockerutil.WaitForExit(ctx, client, bootstrapExec.ID) if err != nil { - os.Exit(1) + return xerrors.Errorf("wait for exit: %w", err) } - os.Exit(0) - }() - log.Info(ctx, "HELP") - time.Sleep(time.Second) + + log.Debug(ctx, "bootstrap process successfully exited") + return nil + } return nil } diff --git a/cli/root.go b/cli/root.go index 0656520..8a03644 100644 --- a/cli/root.go +++ b/cli/root.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" ) -func Root() *cobra.Command { +func Root(ch chan func() error) *cobra.Command { cmd := &cobra.Command{ Use: "envbox", SilenceErrors: true, @@ -15,6 +15,6 @@ func Root() *cobra.Command { }, } - cmd.AddCommand(dockerCmd()) + cmd.AddCommand(dockerCmd(ch)) return cmd } diff --git a/cmd/envbox/main.go b/cmd/envbox/main.go index 2dfdb94..9c38338 100644 --- a/cmd/envbox/main.go +++ b/cmd/envbox/main.go @@ -3,18 +3,37 @@ package main import ( "fmt" "os" + "os/signal" "runtime" + "syscall" "github.com/coder/envbox/cli" ) func main() { - _, err := cli.Root().ExecuteC() + ch := make(chan func() error, 1) + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGWINCH) + go func() { + fmt.Println("waiting for signal") + <-sigs + fmt.Println("Got signal") + select { + case fn := <-ch: + fmt.Println("running shutdown function") + err := fn() + if err != nil { + fmt.Fprintf(os.Stderr, "shutdown function failed: %v", err) + os.Exit(1) + } + default: + fmt.Println("no shutdown function") + } + os.Exit(0) + }() + _, err := cli.Root(ch).ExecuteC() if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } - - // We exit the main thread while keepin all the other procs goin strong. runtime.Goexit() } diff --git a/dockerutil/exec.go b/dockerutil/exec.go index 57b0b1c..624e573 100644 --- a/dockerutil/exec.go +++ b/dockerutil/exec.go @@ -95,6 +95,23 @@ func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) return &buf, inspect.Pid, nil } +func GetExecPID(ctx context.Context, client DockerClient, execID string) (int, error) { + for r := retry.New(time.Second, time.Second); r.Wait(ctx); { + inspect, err := client.ContainerExecInspect(ctx, execID) + if err != nil { + return 0, xerrors.Errorf("exec inspect: %w", err) + } + + if inspect.Pid == 0 { + continue + } + return inspect.Pid, nil + } + + return 0, ctx.Err() + +} + func WaitForExit(ctx context.Context, client DockerClient, execID string) error { for r := retry.New(time.Second, time.Second); r.Wait(ctx); { inspect, err := client.ContainerExecInspect(ctx, execID) @@ -112,6 +129,5 @@ func WaitForExit(ctx context.Context, client DockerClient, execID string) error return nil } - return ctx.Err() } diff --git a/integration/docker_test.go b/integration/docker_test.go index 2bde94e..d043e76 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -289,10 +289,12 @@ func TestDocker(t *testing.T) { binds = append(binds, bindMount(homeDir, "/home/coder", false)) + envs := []string{fmt.Sprintf("%s=%s:%s", cli.EnvMounts, "/home/coder", "/home/coder")} // Run the envbox container. resource := integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ Image: integrationtest.UbuntuImage, Username: "root", + Envs: envs, Binds: binds, BootstrapScript: sigtrapScript, }) @@ -303,9 +305,8 @@ func TestDocker(t *testing.T) { }) require.Error(t, err) - time.Sleep(time.Second * 5) - err = pool.Client.StopContainer(resource.Container.ID, 30) - require.NoError(t, err) + // Simulate a shutdown. + integrationtest.StopContainer(t, pool, resource.Container.ID, 30*time.Second) err = resource.Close() require.NoError(t, err) @@ -315,6 +316,7 @@ func TestDocker(t *testing.T) { resource = integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ Image: integrationtest.UbuntuImage, Username: "root", + Envs: envs, Binds: binds, BootstrapScript: sigtrapScript, }) @@ -357,17 +359,13 @@ func bindMount(src, dest string, ro bool) string { } const sigtrapScript = `#!/bin/bash - -# Function to handle cleanup cleanup() { - touch /home/coder/foo + echo "HANDLING A SIGNAL!" && touch /home/coder/foo && echo "touched file" exit 0 } -# Trap SIGINT (Ctrl+C) and SIGTERM signals trap 'cleanup' INT TERM -# Main loop or processing logic (replace with your script's logic) while true; do echo "Working..." sleep 1 diff --git a/integration/integrationtest/docker.go b/integration/integrationtest/docker.go index 96ebd4e..4ee8412 100644 --- a/integration/integrationtest/docker.go +++ b/integration/integrationtest/docker.go @@ -33,7 +33,7 @@ const ( HelloWorldImage = "gcr.io/coder-dev-1/sreya/hello-world" // UbuntuImage is just vanilla ubuntu (80MB) but the user is set to a non-root // user . - UbuntuImage = "gcr.io/coder-dev-1/sreya/ubuntu-coder" + UbuntuImage = "gcr.io/coder-dev-1/sreya/ubuntu-coder:jon" ) // TODO use df to determine if an environment is running in a docker container or not. @@ -264,6 +264,31 @@ func ExecEnvbox(t *testing.T, pool *dockertest.Pool, conf ExecConfig) ([]byte, e return buf.Bytes(), nil } +func StopContainer(t *testing.T, pool *dockertest.Pool, id string, to time.Duration) { + t.Helper() + + err := pool.Client.KillContainer(docker.KillContainerOptions{ + ID: id, + Signal: docker.SIGTERM, + }) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), to) + defer cancel() + for r := retry.New(time.Second, time.Second); r.Wait(ctx); { + cnt, err := pool.Client.InspectContainer(id) + require.NoError(t, err) + + if cnt.State.Running { + continue + } + + return + } + + t.Fatalf("timed out waiting for container %s to stop", id) +} + // cmdLineEnvs returns args passed to the /envbox command // but using their env var alias. func cmdLineEnvs(c *CreateDockerCVMConfig) []string { From fe2653e0c711049614fe67b4bf41296828ef8645 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 19 Jul 2024 18:43:21 +0000 Subject: [PATCH 12/15] make fmt --- cli/clitest/cli.go | 2 +- cli/docker.go | 4 ++++ dockerutil/dockerfake/client.go | 4 +++- dockerutil/exec.go | 1 - 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cli/clitest/cli.go b/cli/clitest/cli.go index f6ff8cf..49f74a6 100644 --- a/cli/clitest/cli.go +++ b/cli/clitest/cli.go @@ -56,7 +56,7 @@ func New(t *testing.T, cmd string, args ...string) (context.Context, *cobra.Comm ctx = ctx(t, fs, execer, mnt, client) ) - root := cli.Root() + root := cli.Root(nil) // This is the one thing that isn't really mocked for the tests. // I cringe at the thought of introducing yet another mock so // let's avoid it for now. diff --git a/cli/docker.go b/cli/docker.go index b185acd..213f451 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -678,6 +678,10 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker blog.Info("Envbox startup complete!") + if flags.boostrapScript == "" { + return nil + } + bootstrapExec, err := client.ContainerExecCreate(ctx, containerID, dockertypes.ExecConfig{ User: imgMeta.UID, Cmd: []string{"/bin/sh", "-s"}, diff --git a/dockerutil/dockerfake/client.go b/dockerutil/dockerfake/client.go index f735f1c..3706cd8 100644 --- a/dockerutil/dockerfake/client.go +++ b/dockerutil/dockerfake/client.go @@ -162,7 +162,9 @@ func (m MockClient) ContainerExecCreate(ctx context.Context, name string, config func (m MockClient) ContainerExecInspect(ctx context.Context, id string) (dockertypes.ContainerExecInspect, error) { if m.ContainerExecInspectFn == nil { - return dockertypes.ContainerExecInspect{}, nil + return dockertypes.ContainerExecInspect{ + Pid: 1, + }, nil } return m.ContainerExecInspectFn(ctx, id) diff --git a/dockerutil/exec.go b/dockerutil/exec.go index 624e573..964e406 100644 --- a/dockerutil/exec.go +++ b/dockerutil/exec.go @@ -109,7 +109,6 @@ func GetExecPID(ctx context.Context, client DockerClient, execID string) (int, e } return 0, ctx.Err() - } func WaitForExit(ctx context.Context, client DockerClient, execID string) error { From b92da5638d886e405936c1681322291e2658746d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 19 Jul 2024 19:44:30 +0000 Subject: [PATCH 13/15] lint --- cli/docker.go | 2 +- cmd/envbox/main.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 213f451..190b282 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -677,7 +677,6 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker } blog.Info("Envbox startup complete!") - if flags.boostrapScript == "" { return nil } @@ -729,6 +728,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker log.Debug(ctx, "killing container", slog.F("bootstrap_pid", bootstrapPID)) // The PID returned is the PID _outside_ the container... + //nolint:gosec out, err := exec.Command("kill", "-TERM", strconv.Itoa(bootstrapPID)).CombinedOutput() if err != nil { return xerrors.Errorf("kill bootstrap process (%s): %w", out, err) diff --git a/cmd/envbox/main.go b/cmd/envbox/main.go index 9c38338..d55572e 100644 --- a/cmd/envbox/main.go +++ b/cmd/envbox/main.go @@ -1,12 +1,14 @@ package main import ( - "fmt" + "context" "os" "os/signal" "runtime" "syscall" + "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogjson" "github.com/coder/envbox/cli" ) @@ -15,19 +17,21 @@ func main() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGWINCH) go func() { - fmt.Println("waiting for signal") + log := slog.Make(slogjson.Sink(os.Stderr)) + ctx := context.Background() + log.Info(ctx, "waiting for signal") <-sigs - fmt.Println("Got signal") + log.Info(ctx, "got signal") select { case fn := <-ch: - fmt.Println("running shutdown function") + log.Info(ctx, "running shutdown function") err := fn() if err != nil { - fmt.Fprintf(os.Stderr, "shutdown function failed: %v", err) + log.Error(ctx, "running shutdown function") os.Exit(1) } default: - fmt.Println("no shutdown function") + log.Info(ctx, "no shutdown function") } os.Exit(0) }() From e7f70bf59b2967076f622f71479ff71dd35f42e5 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 19 Jul 2024 19:46:16 +0000 Subject: [PATCH 14/15] fmt --- cmd/envbox/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/envbox/main.go b/cmd/envbox/main.go index d55572e..813e62b 100644 --- a/cmd/envbox/main.go +++ b/cmd/envbox/main.go @@ -27,12 +27,13 @@ func main() { log.Info(ctx, "running shutdown function") err := fn() if err != nil { - log.Error(ctx, "running shutdown function") + log.Error(ctx, "shutdown function failed", slog.Error(err)) os.Exit(1) } default: log.Info(ctx, "no shutdown function") } + log.Info(ctx, "exiting") os.Exit(0) }() _, err := cli.Root(ch).ExecuteC() From bde2f137efb7d5e01ab8f970f7ea0d59352953ca Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 19 Jul 2024 20:00:00 +0000 Subject: [PATCH 15/15] refactor some shit --- cli/docker.go | 2 +- dockerutil/container.go | 2 +- dockerutil/exec.go | 20 ++++++++++---------- dockerutil/image.go | 4 ++-- integration/integrationtest/docker.go | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cli/docker.go b/cli/docker.go index 190b282..928a7c2 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -648,7 +648,7 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker bootDir := filepath.Join(imgMeta.HomeDir, ".coder") blog.Infof("Creating %q directory to host Coder assets...", bootDir) - _, _, err = dockerutil.ExecContainer(ctx, client, dockerutil.ExecConfig{ + _, err = dockerutil.ExecContainer(ctx, client, dockerutil.ExecConfig{ ContainerID: containerID, User: imgMeta.UID, Cmd: "mkdir", diff --git a/dockerutil/container.go b/dockerutil/container.go index 4c72c89..1d27d4e 100644 --- a/dockerutil/container.go +++ b/dockerutil/container.go @@ -114,7 +114,7 @@ func BootstrapContainer(ctx context.Context, client DockerClient, conf Bootstrap var err error for r, n := retry.New(time.Second, time.Second*2), 0; r.Wait(ctx) && n < 10; n++ { var out io.Reader - out, _, err = ExecContainer(ctx, client, ExecConfig{ + out, err = ExecContainer(ctx, client, ExecConfig{ ContainerID: conf.ContainerID, User: conf.User, Cmd: "/bin/sh", diff --git a/dockerutil/exec.go b/dockerutil/exec.go index 964e406..47423ed 100644 --- a/dockerutil/exec.go +++ b/dockerutil/exec.go @@ -26,7 +26,7 @@ type ExecConfig struct { // ExecContainer runs a command in a container. It returns the output and any error. // If an error occurs during the execution of the command, the output is appended to the error. -func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) (io.Reader, int, error) { +func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) (io.Reader, error) { exec, err := client.ContainerExecCreate(ctx, config.ContainerID, dockertypes.ExecConfig{ Detach: config.Detach, Cmd: append([]string{config.Cmd}, config.Args...), @@ -37,23 +37,23 @@ func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) Env: config.Env, }) if err != nil { - return nil, 0, xerrors.Errorf("exec create: %w", err) + return nil, xerrors.Errorf("exec create: %w", err) } resp, err := client.ContainerExecAttach(ctx, exec.ID, dockertypes.ExecStartCheck{}) if err != nil { - return nil, 0, xerrors.Errorf("attach to exec: %w", err) + return nil, xerrors.Errorf("attach to exec: %w", err) } defer resp.Close() if config.Stdin != nil { _, err = io.Copy(resp.Conn, config.Stdin) if err != nil { - return nil, 0, xerrors.Errorf("copy stdin: %w", err) + return nil, xerrors.Errorf("copy stdin: %w", err) } err = resp.CloseWrite() if err != nil { - return nil, 0, xerrors.Errorf("close write: %w", err) + return nil, xerrors.Errorf("close write: %w", err) } } @@ -75,24 +75,24 @@ func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) _, err = io.Copy(wr, resp.Reader) if err != nil { - return nil, 0, xerrors.Errorf("copy cmd output: %w", err) + return nil, xerrors.Errorf("copy cmd output: %w", err) } resp.Close() inspect, err := client.ContainerExecInspect(ctx, exec.ID) if err != nil { - return nil, 0, xerrors.Errorf("exec inspect: %w", err) + return nil, xerrors.Errorf("exec inspect: %w", err) } if inspect.Running { - return nil, 0, xerrors.Errorf("unexpectedly still running") + return nil, xerrors.Errorf("unexpectedly still running") } if inspect.ExitCode > 0 { - return nil, 0, xerrors.Errorf("%s: exit code %d", buf.Bytes(), inspect.ExitCode) + return nil, xerrors.Errorf("%s: exit code %d", buf.Bytes(), inspect.ExitCode) } - return &buf, inspect.Pid, nil + return &buf, nil } func GetExecPID(ctx context.Context, client DockerClient, execID string) (int, error) { diff --git a/dockerutil/image.go b/dockerutil/image.go index 257cf50..5d545d2 100644 --- a/dockerutil/image.go +++ b/dockerutil/image.go @@ -191,14 +191,14 @@ func GetImageMetadata(ctx context.Context, client DockerClient, image, username return ImageMetadata{}, xerrors.Errorf("CVMs do not support NFS volumes") } - _, _, err = ExecContainer(ctx, client, ExecConfig{ + _, err = ExecContainer(ctx, client, ExecConfig{ ContainerID: inspect.ID, Cmd: "stat", Args: []string{"/sbin/init"}, }) initExists := err == nil - out, _, err := ExecContainer(ctx, client, ExecConfig{ + out, err := ExecContainer(ctx, client, ExecConfig{ ContainerID: inspect.ID, Cmd: "getent", Args: []string{"passwd", username}, diff --git a/integration/integrationtest/docker.go b/integration/integrationtest/docker.go index 4ee8412..088952c 100644 --- a/integration/integrationtest/docker.go +++ b/integration/integrationtest/docker.go @@ -33,7 +33,7 @@ const ( HelloWorldImage = "gcr.io/coder-dev-1/sreya/hello-world" // UbuntuImage is just vanilla ubuntu (80MB) but the user is set to a non-root // user . - UbuntuImage = "gcr.io/coder-dev-1/sreya/ubuntu-coder:jon" + UbuntuImage = "gcr.io/coder-dev-1/sreya/ubuntu-coder" ) // TODO use df to determine if an environment is running in a docker container or not.