1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package run
18
19import (
20	"context"
21	gocontext "context"
22	"encoding/csv"
23	"fmt"
24	"strings"
25
26	"github.com/containerd/console"
27	"github.com/containerd/containerd"
28	"github.com/containerd/containerd/cio"
29	"github.com/containerd/containerd/cmd/ctr/commands"
30	"github.com/containerd/containerd/cmd/ctr/commands/tasks"
31	"github.com/containerd/containerd/containers"
32	"github.com/containerd/containerd/namespaces"
33	"github.com/containerd/containerd/oci"
34	gocni "github.com/containerd/go-cni"
35	specs "github.com/opencontainers/runtime-spec/specs-go"
36	"github.com/pkg/errors"
37	"github.com/sirupsen/logrus"
38	"github.com/urfave/cli"
39)
40
41func withMounts(context *cli.Context) oci.SpecOpts {
42	return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *specs.Spec) error {
43		mounts := make([]specs.Mount, 0)
44		for _, mount := range context.StringSlice("mount") {
45			m, err := parseMountFlag(mount)
46			if err != nil {
47				return err
48			}
49			mounts = append(mounts, m)
50		}
51		return oci.WithMounts(mounts)(ctx, client, container, s)
52	}
53}
54
55// parseMountFlag parses a mount string in the form "type=foo,source=/path,destination=/target,options=rbind:rw"
56func parseMountFlag(m string) (specs.Mount, error) {
57	mount := specs.Mount{}
58	r := csv.NewReader(strings.NewReader(m))
59
60	fields, err := r.Read()
61	if err != nil {
62		return mount, err
63	}
64
65	for _, field := range fields {
66		v := strings.Split(field, "=")
67		if len(v) != 2 {
68			return mount, fmt.Errorf("invalid mount specification: expected key=val")
69		}
70
71		key := v[0]
72		val := v[1]
73		switch key {
74		case "type":
75			mount.Type = val
76		case "source", "src":
77			mount.Source = val
78		case "destination", "dst":
79			mount.Destination = val
80		case "options":
81			mount.Options = strings.Split(val, ":")
82		default:
83			return mount, fmt.Errorf("mount option %q not supported", key)
84		}
85	}
86
87	return mount, nil
88}
89
90// Command runs a container
91var Command = cli.Command{
92	Name:           "run",
93	Usage:          "run a container",
94	ArgsUsage:      "[flags] Image|RootFS ID [COMMAND] [ARG...]",
95	SkipArgReorder: true,
96	Flags: append([]cli.Flag{
97		cli.BoolFlag{
98			Name:  "rm",
99			Usage: "remove the container after running",
100		},
101		cli.BoolFlag{
102			Name:  "null-io",
103			Usage: "send all IO to /dev/null",
104		},
105		cli.StringFlag{
106			Name:  "log-uri",
107			Usage: "log uri",
108		},
109		cli.BoolFlag{
110			Name:  "detach,d",
111			Usage: "detach from the task after it has started execution",
112		},
113		cli.StringFlag{
114			Name:  "fifo-dir",
115			Usage: "directory used for storing IO FIFOs",
116		},
117		cli.StringFlag{
118			Name:  "cgroup",
119			Usage: "cgroup path (To disable use of cgroup, set to \"\" explicitly)",
120		},
121		cli.StringFlag{
122			Name:  "platform",
123			Usage: "run image for specific platform",
124		},
125	}, append(platformRunFlags, append(commands.SnapshotterFlags, commands.ContainerFlags...)...)...),
126	Action: func(context *cli.Context) error {
127		var (
128			err error
129			id  string
130			ref string
131
132			tty       = context.Bool("tty")
133			detach    = context.Bool("detach")
134			config    = context.IsSet("config")
135			enableCNI = context.Bool("cni")
136		)
137
138		if config {
139			id = context.Args().First()
140			if context.NArg() > 1 {
141				return errors.New("with spec config file, only container id should be provided")
142			}
143		} else {
144			id = context.Args().Get(1)
145			ref = context.Args().First()
146
147			if ref == "" {
148				return errors.New("image ref must be provided")
149			}
150		}
151		if id == "" {
152			return errors.New("container id must be provided")
153		}
154		client, ctx, cancel, err := commands.NewClient(context)
155		if err != nil {
156			return err
157		}
158		defer cancel()
159		container, err := NewContainer(ctx, client, context)
160		if err != nil {
161			return err
162		}
163		if context.Bool("rm") && !detach {
164			defer container.Delete(ctx, containerd.WithSnapshotCleanup)
165		}
166		var con console.Console
167		if tty {
168			con = console.Current()
169			defer con.Reset()
170			if err := con.SetRaw(); err != nil {
171				return err
172			}
173		}
174		var network gocni.CNI
175		if enableCNI {
176			if network, err = gocni.New(gocni.WithDefaultConf); err != nil {
177				return err
178			}
179		}
180
181		opts := getNewTaskOpts(context)
182		ioOpts := []cio.Opt{cio.WithFIFODir(context.String("fifo-dir"))}
183		task, err := tasks.NewTask(ctx, client, container, context.String("checkpoint"), con, context.Bool("null-io"), context.String("log-uri"), ioOpts, opts...)
184		if err != nil {
185			return err
186		}
187
188		var statusC <-chan containerd.ExitStatus
189		if !detach {
190			defer func() {
191				if enableCNI {
192					if err := network.Remove(ctx, fullID(ctx, container), ""); err != nil {
193						logrus.WithError(err).Error("network review")
194					}
195				}
196				task.Delete(ctx)
197			}()
198
199			if statusC, err = task.Wait(ctx); err != nil {
200				return err
201			}
202		}
203		if context.IsSet("pid-file") {
204			if err := commands.WritePidFile(context.String("pid-file"), int(task.Pid())); err != nil {
205				return err
206			}
207		}
208		if enableCNI {
209			if _, err := network.Setup(ctx, fullID(ctx, container), fmt.Sprintf("/proc/%d/ns/net", task.Pid())); err != nil {
210				return err
211			}
212		}
213		if err := task.Start(ctx); err != nil {
214			return err
215		}
216		if detach {
217			return nil
218		}
219		if tty {
220			if err := tasks.HandleConsoleResize(ctx, task, con); err != nil {
221				logrus.WithError(err).Error("console resize")
222			}
223		} else {
224			sigc := commands.ForwardAllSignals(ctx, task)
225			defer commands.StopCatch(sigc)
226		}
227		status := <-statusC
228		code, _, err := status.Result()
229		if err != nil {
230			return err
231		}
232		if _, err := task.Delete(ctx); err != nil {
233			return err
234		}
235		if code != 0 {
236			return cli.NewExitError("", int(code))
237		}
238		return nil
239	},
240}
241
242func fullID(ctx context.Context, c containerd.Container) string {
243	id := c.ID()
244	ns, ok := namespaces.Namespace(ctx)
245	if !ok {
246		return id
247	}
248	return fmt.Sprintf("%s-%s", ns, id)
249}
250