-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathexec.go
148 lines (143 loc) · 3.67 KB
/
exec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package main
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"syscall"
"github.com/containerd/console"
"github.com/ktock/buildg/pkg/buildkit"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/urfave/cli"
)
func execCommand(ctx context.Context, hCtx *handlerContext) cli.Command {
return cli.Command{
Name: "exec",
Aliases: []string{"e"},
Usage: "Execute command in the step",
UsageText: `exec [OPTIONS] [ARGS...]
If ARGS isn't provided, "/bin/sh" is used by default.
container execution on non-RUN instruction is experimental.
`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "image",
Usage: "Execute command in the debuger image. If not specified, the command is executed on the rootfs of the current step.",
},
cli.StringFlag{
Name: "mountroot",
Usage: "Mountpoint to mount the rootfs of the current step. Ignored if --image isn't specified.",
Value: "/debugroot",
},
cli.BoolFlag{
Name: "init-state",
Usage: "Execute commands in an initial state of that step (experimental)",
},
cli.BoolTFlag{
Name: "tty,t",
Usage: "Allocate tty (enabled by default)",
},
cli.BoolTFlag{
Name: "i",
Usage: "Enable stdin (FIXME: must be set with tty) (enabled by default)",
},
cli.StringSliceFlag{
Name: "env,e",
Usage: "Set environment variables",
},
cli.StringFlag{
Name: "workdir,w",
Usage: "Working directory inside the container",
},
},
Action: func(clicontext *cli.Context) error {
args := clicontext.Args()
if len(args) == 0 || args[0] == "" {
args = []string{"/bin/sh"}
}
flagI := clicontext.Bool("i")
flagT := clicontext.Bool("tty")
if flagI && !flagT || !flagI && flagT {
return fmt.Errorf("flag \"-i\" and \"-t\" must be set together") // FIXME
}
h := hCtx.handler
r, done := hCtx.stdin.use()
defer done()
cfg := buildkit.ContainerConfig{
Info: hCtx.info,
Args: args,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: flagT,
Mountroot: clicontext.String("mountroot"),
InputMount: clicontext.Bool("init-state"),
Env: clicontext.StringSlice("env"),
Cwd: clicontext.String("workdir"),
WatchSignal: watchSignal,
GatewayClient: h.GatewayClient(),
}
if clicontext.Bool("image") {
cfg.Image = h.DebuggerImage()
}
if flagI {
cfg.Stdin = io.NopCloser(r)
}
hCtx.signalHandler.disable() // let the container catch signals
defer hCtx.signalHandler.enable()
proc, cleanup, err := buildkit.ExecContainer(ctx, cfg)
if err != nil {
return err
}
defer cleanup()
errCh := make(chan error)
doneCh := make(chan struct{})
go func() {
if err := proc.Wait(); err != nil {
errCh <- err
return
}
close(doneCh)
}()
select {
case <-doneCh:
case <-ctx.Done():
return ctx.Err()
case err := <-errCh:
return fmt.Errorf("process execution failed: %w", err)
}
return nil
},
}
}
func watchSignal(ctx context.Context, proc gwclient.ContainerProcess, con console.Console) {
ch := make(chan os.Signal, 1)
signals := []os.Signal{syscall.SIGWINCH, syscall.SIGINT, syscall.SIGTERM}
signal.Notify(ch, signals...)
go func() {
defer signal.Stop(ch)
for {
select {
case ss := <-ch:
switch ss {
case syscall.SIGWINCH:
if con != nil {
size, err := con.Size()
if err != nil {
continue
}
proc.Resize(ctx, gwclient.WinSize{
Cols: uint32(size.Width),
Rows: uint32(size.Height),
})
}
default:
proc.Signal(ctx, ss.(syscall.Signal))
}
case <-ctx.Done():
return
}
}
}()
ch <- syscall.SIGWINCH
}