1
16
17 package tasks
18
19 import (
20 "errors"
21 "io"
22 "net/url"
23 "os"
24
25 "github.com/containerd/console"
26 "github.com/containerd/containerd"
27 "github.com/containerd/containerd/cio"
28 "github.com/containerd/containerd/cmd/ctr/commands"
29 "github.com/containerd/containerd/oci"
30 "github.com/sirupsen/logrus"
31 "github.com/urfave/cli"
32 )
33
34 var execCommand = cli.Command{
35 Name: "exec",
36 Usage: "Execute additional processes in an existing container",
37 ArgsUsage: "[flags] CONTAINER CMD [ARG...]",
38 SkipArgReorder: true,
39 Flags: []cli.Flag{
40 cli.StringFlag{
41 Name: "cwd",
42 Usage: "Working directory of the new process",
43 },
44 cli.BoolFlag{
45 Name: "tty,t",
46 Usage: "Allocate a TTY for the container",
47 },
48 cli.BoolFlag{
49 Name: "detach,d",
50 Usage: "Detach from the task after it has started execution",
51 },
52 cli.StringFlag{
53 Name: "exec-id",
54 Required: true,
55 Usage: "Exec specific id for the process",
56 },
57 cli.StringFlag{
58 Name: "fifo-dir",
59 Usage: "Directory used for storing IO FIFOs",
60 },
61 cli.StringFlag{
62 Name: "log-uri",
63 Usage: "Log uri for custom shim logging",
64 },
65 cli.StringFlag{
66 Name: "user",
67 Usage: "User id or name",
68 },
69 },
70 Action: func(context *cli.Context) error {
71 var (
72 id = context.Args().First()
73 args = context.Args().Tail()
74 tty = context.Bool("tty")
75 detach = context.Bool("detach")
76 )
77 if id == "" {
78 return errors.New("container id must be provided")
79 }
80 client, ctx, cancel, err := commands.NewClient(context)
81 if err != nil {
82 return err
83 }
84 defer cancel()
85 container, err := client.LoadContainer(ctx, id)
86 if err != nil {
87 return err
88 }
89 spec, err := container.Spec(ctx)
90 if err != nil {
91 return err
92 }
93 if user := context.String("user"); user != "" {
94 c, err := container.Info(ctx)
95 if err != nil {
96 return err
97 }
98 if err := oci.WithUser(user)(ctx, client, &c, spec); err != nil {
99 return err
100 }
101 }
102
103 pspec := spec.Process
104 pspec.Terminal = tty
105 pspec.Args = args
106
107 if cwd := context.String("cwd"); cwd != "" {
108 pspec.Cwd = cwd
109 }
110
111 task, err := container.Task(ctx, nil)
112 if err != nil {
113 return err
114 }
115
116 var (
117 ioCreator cio.Creator
118 stdinC = &stdinCloser{
119 stdin: os.Stdin,
120 }
121 con console.Console
122 )
123
124 fifoDir := context.String("fifo-dir")
125 logURI := context.String("log-uri")
126 ioOpts := []cio.Opt{cio.WithFIFODir(fifoDir)}
127 switch {
128 case tty && logURI != "":
129 return errors.New("can't use log-uri with tty")
130 case logURI != "" && fifoDir != "":
131 return errors.New("can't use log-uri with fifo-dir")
132
133 case tty:
134 con = console.Current()
135 defer con.Reset()
136 if err := con.SetRaw(); err != nil {
137 return err
138 }
139 ioCreator = cio.NewCreator(append([]cio.Opt{cio.WithStreams(con, con, nil), cio.WithTerminal}, ioOpts...)...)
140
141 case logURI != "":
142 uri, err := url.Parse(logURI)
143 if err != nil {
144 return err
145 }
146 ioCreator = cio.LogURI(uri)
147
148 default:
149 ioCreator = cio.NewCreator(append([]cio.Opt{cio.WithStreams(stdinC, os.Stdout, os.Stderr)}, ioOpts...)...)
150 }
151
152 process, err := task.Exec(ctx, context.String("exec-id"), pspec, ioCreator)
153 if err != nil {
154 return err
155 }
156 stdinC.closer = func() {
157 process.CloseIO(ctx, containerd.WithStdinCloser)
158 }
159
160 if !detach {
161 defer process.Delete(ctx)
162 }
163
164 statusC, err := process.Wait(ctx)
165 if err != nil {
166 return err
167 }
168
169 if err := process.Start(ctx); err != nil {
170 return err
171 }
172 if detach {
173 return nil
174 }
175 if tty {
176 if err := HandleConsoleResize(ctx, process, con); err != nil {
177 logrus.WithError(err).Error("console resize")
178 }
179 } else {
180 sigc := commands.ForwardAllSignals(ctx, process)
181 defer commands.StopCatch(sigc)
182 }
183 status := <-statusC
184 code, _, err := status.Result()
185 if err != nil {
186 return err
187 }
188 if code != 0 {
189 return cli.NewExitError("", int(code))
190 }
191 return nil
192 },
193 }
194
195 type stdinCloser struct {
196 stdin *os.File
197 closer func()
198 }
199
200 func (s *stdinCloser) Read(p []byte) (int, error) {
201 n, err := s.stdin.Read(p)
202 if err == io.EOF {
203 if s.closer != nil {
204 s.closer()
205 }
206 }
207 return n, err
208 }
209
View as plain text