Skip to content
This repository was archived by the owner on Apr 28, 2020. It is now read-only.

Commit d41cd2b

Browse files
committed
add on_open label for running a command once open
Adding the `on_open` label to an image will cause sail to exec that command inside of the container's project directory every time it starts a container. Added the new label to the documentation.
1 parent 2125c9e commit d41cd2b

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

runner.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ const containerLogPath = "/tmp/code-server.log"
3232
// For example, when setting environment variables for the container.
3333
const containerHome = "/home/user"
3434

35+
// expandDir expands ~ to be the containerHome variable.
36+
func expandDir(path string) string {
37+
path = filepath.Clean(path)
38+
if path == "~" {
39+
path = containerHome
40+
} else if strings.HasPrefix(path, "~/") {
41+
path = filepath.Join(containerHome, path[2:])
42+
}
43+
return filepath.Clean(path)
44+
}
45+
3546
// Docker labels for sail state.
3647
const (
3748
sailLabel = "com.coder.sail"
@@ -44,6 +55,12 @@ const (
4455
proxyURLLabel = sailLabel + ".proxy_url"
4556
)
4657

58+
// Docker labels for user configuration.
59+
const (
60+
onOpenLabel = "on_open"
61+
projectRootLabel = "project_root"
62+
)
63+
4764
// runner holds all the information needed to assemble a new sail container.
4865
// The runner stores itself as state on the container.
4966
// It enables quick iteration on a container with small modifications to it's config.
@@ -68,6 +85,8 @@ type runner struct {
6885
// the container's root process.
6986
// We want code-server to be the root process as it gives us the nice guarantee that
7087
// the container is only online when code-server is working.
88+
// Additionally, runContainer also runs the image's on_open label as a sh
89+
// command inside of the project directory.
7190
func (r *runner) runContainer(image string) error {
7291
cli := dockerClient()
7392
defer cli.Close()
@@ -131,6 +150,11 @@ func (r *runner) runContainer(image string) error {
131150
return xerrors.Errorf("failed to start container: %w", err)
132151
}
133152

153+
err = r.runOnOpen(ctx, image)
154+
if err != nil {
155+
return xerrors.Errorf("failed to run on_open label in container: %w", err)
156+
}
157+
134158
return nil
135159
}
136160

@@ -457,7 +481,7 @@ func (r *runner) projectDir(image string) (string, error) {
457481
return "", xerrors.Errorf("failed to inspect image: %w", err)
458482
}
459483

460-
proot, ok := img.Config.Labels["project_root"]
484+
proot, ok := img.Config.Labels[projectRootLabel]
461485
if ok {
462486
return filepath.Join(proot, r.projectName), nil
463487
}
@@ -491,6 +515,63 @@ func runnerFromContainer(name string) (*runner, error) {
491515
}, nil
492516
}
493517

518+
// runOnOpen runs the image's `on_open` label in the container in the project directory.
519+
func (r *runner) runOnOpen(ctx context.Context, image string) error {
520+
cli := dockerClient()
521+
defer cli.Close()
522+
523+
// get project directory.
524+
projectDir, err := r.projectDir(image)
525+
if err != nil {
526+
return err
527+
}
528+
529+
// get on_open label from image
530+
img, _, err := cli.ImageInspectWithRaw(context.Background(), image)
531+
if err != nil {
532+
return xerrors.Errorf("failed to inspect image: %w", err)
533+
}
534+
onOpenCmd, ok := img.Config.Labels[onOpenLabel]
535+
if !ok {
536+
// no on_open label, so we quit early.
537+
return nil
538+
}
539+
540+
cmd := []string{onOpenCmd}
541+
return r.runInContainer(ctx, expandDir(projectDir), cmd, true)
542+
}
543+
544+
// runInContainer runs a command in the container (optionally using /bin/sh -c) using exec (detached).
545+
func (r *runner) runInContainer(ctx context.Context, workDir string, cmd []string, useSh bool) error {
546+
cli := dockerClient()
547+
defer cli.Close()
548+
549+
if useSh {
550+
cmd = append([]string{"/bin/sh", "-c"}, cmd...)
551+
}
552+
553+
execID, err := cli.ContainerExecCreate(ctx, r.cntName, types.ExecConfig{
554+
Cmd: cmd,
555+
Detach: true,
556+
WorkingDir: workDir,
557+
558+
// the following options don't attach it, but makes the script think it's running in a terminal.
559+
Tty: true,
560+
AttachStdin: true,
561+
AttachStderr: true,
562+
AttachStdout: true,
563+
})
564+
if err != nil {
565+
return xerrors.Errorf("failed to create exec configuration: %v", err)
566+
}
567+
568+
err = cli.ContainerExecStart(ctx, execID.ID, types.ExecStartCheck{Detach: true})
569+
if err != nil {
570+
return xerrors.Errorf("failed to start exec process: %v", err)
571+
}
572+
return nil
573+
}
574+
494575
func (r *runner) forkProxy() error {
495576
var err error
496577
r.proxyURL, err = forkProxy(r.cntName)

site/content/docs/concepts/labels.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ LABEL project_root "~/go/src/"
2424

2525
Will bind mount the host directory `$project_root/<org>/<repo>` to `~/go/src/<repo>` in the container.
2626

27+
### Run on Open Labels
28+
29+
You can run a command in your sail container after it starts by specifying
30+
the `on_open` label. If you'd like to run multiple commands on launch, we
31+
recommend using a `.sh` file as your `on_open` label, as you cannot provide
32+
multiple `on_open` statements.
33+
34+
The `on_open` label is run detached inside of `/bin/sh` as soon as the
35+
container is started, with the work directory set to your `project_root`
36+
(see the section above).
37+
38+
For example:
39+
```Dockerfile
40+
LABEL on_open "npm install"
41+
```
42+
```Dockerfile
43+
LABEL on_open "go get"
44+
```
45+
```Dockerfile
46+
LABEL on_open "./.sail/on_open.sh"
47+
```
48+
49+
Make sure any scripts you make are executable, otherwise sail will fail to
50+
launch.
2751

2852
### Share Labels
2953

0 commit comments

Comments
 (0)