Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ To explore more examples, tips, and advanced usage, check out the following guid
## Setup Script

The `ENVBUILDER_SETUP_SCRIPT` environment variable dynamically configures the user and init command (PID 1) after the container build process.
Custom `ENVBUILDER_INIT_SCRIPT` and `ENVBUILDER_INIT_COMMAND` values become PID 1, so they must trap signals themselves if they need prompt `SIGTERM` or `SIGINT` handling.

> **Note**: `TARGET_USER` is passed to the setup script to specify who will execute `ENVBUILDER_INIT_COMMAND` (e.g., `code`).

Expand Down
2 changes: 1 addition & 1 deletion docs/env-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
| Flag | Environment variable | Default | Description |
| - | - | - | - |
| `--setup-script` | `ENVBUILDER_SETUP_SCRIPT` | | The script to run before the init script. It runs as the root user regardless of the user specified in the devcontainer.json file. SetupScript is ran as the root user prior to the init script. It is used to configure envbuilder dynamically during the runtime. e.g. specifying whether to start systemd or tiny init for PID 1. |
| `--init-script` | `ENVBUILDER_INIT_SCRIPT` | | The script to run to initialize the workspace. Default: `sleep infinity`. |
| `--init-script` | `ENVBUILDER_INIT_SCRIPT` | | The script to run to initialize the workspace. By default, this traps `SIGTERM`/`SIGINT`, starts `sleep infinity` as a child process, and waits for it so the container exits promptly when stopped. |
| `--init-command` | `ENVBUILDER_INIT_COMMAND` | | The command to run to initialize the workspace. Default: `/bin/sh`. |
| `--init-args` | `ENVBUILDER_INIT_ARGS` | | The arguments to pass to the init command. They are split according to /bin/sh rules with https://github.com/kballard/go-shellquote. |
| `--cache-repo` | `ENVBUILDER_CACHE_REPO` | | The name of the container registry to push the cache image to. If this is empty, the cache will not be pushed. |
Expand Down
37 changes: 37 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,43 @@ func TestBuildStopStartCached(t *testing.T) {
}
}

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

for _, signal := range []string{"SIGTERM", "SIGINT"} {
t.Run(signal, func(t *testing.T) {
t.Parallel()

srv := gittest.CreateGitServer(t, gittest.Options{
Files: map[string]string{
"Dockerfile": "FROM " + testImageAlpine,
},
})
ctr, err := runEnvbuilder(t, runOpts{env: []string{
envbuilderEnv("GIT_URL", srv.URL),
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
}})
require.NoError(t, err)

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
require.NoError(t, err)
defer cli.Close()

ctx := context.Background()
err = cli.ContainerKill(ctx, ctr, signal)
require.NoError(t, err)

require.Eventually(t, func() bool {
status, err := cli.ContainerInspect(ctx, ctr)
if !assert.NoError(t, err) {
return false
}
return !status.State.Running
}, 5*time.Second, 100*time.Millisecond, "container never exited")
})
}
}

func TestCloneFailsFallback(t *testing.T) {
t.Parallel()
t.Run("BadRepo", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion options/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (o *Options) SetDefaults() {
}
}
if o.InitScript == "" {
o.InitScript = "sleep infinity"
o.InitScript = "trap 'exit 0' TERM INT\nsleep infinity &\nwait $!"
}
if o.InitCommand == "" {
o.InitCommand = "/bin/sh"
Expand Down
2 changes: 1 addition & 1 deletion options/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func TestOptions_SetDefaults(t *testing.T) {
t.Parallel()

expected := options.Options{
InitScript: "sleep infinity",
InitScript: "trap 'exit 0' TERM INT\nsleep infinity &\nwait $!",
InitCommand: "/bin/sh",
IgnorePaths: []string{"/var/run", "/product_uuid", "/product_name"},
Filesystem: chmodfs.New(osfs.New("/")),
Expand Down
8 changes: 5 additions & 3 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,11 @@ func (o *Options) CLI() serpent.OptionSet {
{
Flag: "init-script",
Env: WithEnvPrefix("INIT_SCRIPT"),
// Default: "sleep infinity", // TODO: reinstate once legacy opts are removed.
Value: serpent.StringOf(&o.InitScript),
Description: "The script to run to initialize the workspace. Default: `sleep infinity`.",
// Default: "trap 'exit 0' TERM INT\nsleep infinity &\nwait $!", // TODO: reinstate once legacy opts are removed.
Value: serpent.StringOf(&o.InitScript),
Description: "The script to run to initialize the workspace. By default, this traps " +
"`SIGTERM`/`SIGINT`, starts `sleep infinity` as a child process, and waits for it " +
"so the container exits promptly when stopped.",
},
{
Flag: "init-command",
Expand Down
5 changes: 3 additions & 2 deletions options/testdata/options.golden
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ OPTIONS:
The command to run to initialize the workspace. Default: `/bin/sh`.

--init-script string, $ENVBUILDER_INIT_SCRIPT
The script to run to initialize the workspace. Default: `sleep
infinity`.
The script to run to initialize the workspace. By default, this traps
`SIGTERM`/`SIGINT`, starts `sleep infinity` as a child process, and
waits for it so the container exits promptly when stopped.

--insecure bool, $ENVBUILDER_INSECURE
Bypass TLS verification when cloning and pulling from container
Expand Down