@@ -32,6 +32,17 @@ const containerLogPath = "/tmp/code-server.log"
32
32
// For example, when setting environment variables for the container.
33
33
const containerHome = "/home/user"
34
34
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
+
35
46
// Docker labels for sail state.
36
47
const (
37
48
sailLabel = "com.coder.sail"
@@ -44,6 +55,12 @@ const (
44
55
proxyURLLabel = sailLabel + ".proxy_url"
45
56
)
46
57
58
+ // Docker labels for user configuration.
59
+ const (
60
+ onOpenLabel = "on_open"
61
+ projectRootLabel = "project_root"
62
+ )
63
+
47
64
// runner holds all the information needed to assemble a new sail container.
48
65
// The runner stores itself as state on the container.
49
66
// It enables quick iteration on a container with small modifications to it's config.
@@ -68,6 +85,8 @@ type runner struct {
68
85
// the container's root process.
69
86
// We want code-server to be the root process as it gives us the nice guarantee that
70
87
// 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.
71
90
func (r * runner ) runContainer (image string ) error {
72
91
cli := dockerClient ()
73
92
defer cli .Close ()
@@ -131,6 +150,11 @@ func (r *runner) runContainer(image string) error {
131
150
return xerrors .Errorf ("failed to start container: %w" , err )
132
151
}
133
152
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
+
134
158
return nil
135
159
}
136
160
@@ -457,7 +481,7 @@ func (r *runner) projectDir(image string) (string, error) {
457
481
return "" , xerrors .Errorf ("failed to inspect image: %w" , err )
458
482
}
459
483
460
- proot , ok := img .Config .Labels ["project_root" ]
484
+ proot , ok := img .Config .Labels [projectRootLabel ]
461
485
if ok {
462
486
return filepath .Join (proot , r .projectName ), nil
463
487
}
@@ -491,6 +515,63 @@ func runnerFromContainer(name string) (*runner, error) {
491
515
}, nil
492
516
}
493
517
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
+
494
575
func (r * runner ) forkProxy () error {
495
576
var err error
496
577
r .proxyURL , err = forkProxy (r .cntName )
0 commit comments