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