github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/cmd/ctr/commands/run/run.go (about) 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 17 package run 18 19 import ( 20 gocontext "context" 21 "encoding/csv" 22 "fmt" 23 "strings" 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/cmd/ctr/commands/tasks" 30 "github.com/containerd/containerd/containers" 31 "github.com/containerd/containerd/oci" 32 specs "github.com/opencontainers/runtime-spec/specs-go" 33 "github.com/pkg/errors" 34 "github.com/sirupsen/logrus" 35 "github.com/urfave/cli" 36 ) 37 38 func withMounts(context *cli.Context) oci.SpecOpts { 39 return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *specs.Spec) error { 40 mounts := make([]specs.Mount, 0) 41 for _, mount := range context.StringSlice("mount") { 42 m, err := parseMountFlag(mount) 43 if err != nil { 44 return err 45 } 46 mounts = append(mounts, m) 47 } 48 return oci.WithMounts(mounts)(ctx, client, container, s) 49 } 50 } 51 52 // parseMountFlag parses a mount string in the form "type=foo,source=/path,destination=/target,options=rbind:rw" 53 func parseMountFlag(m string) (specs.Mount, error) { 54 mount := specs.Mount{} 55 r := csv.NewReader(strings.NewReader(m)) 56 57 fields, err := r.Read() 58 if err != nil { 59 return mount, err 60 } 61 62 for _, field := range fields { 63 v := strings.Split(field, "=") 64 if len(v) != 2 { 65 return mount, fmt.Errorf("invalid mount specification: expected key=val") 66 } 67 68 key := v[0] 69 val := v[1] 70 switch key { 71 case "type": 72 mount.Type = val 73 case "source", "src": 74 mount.Source = val 75 case "destination", "dst": 76 mount.Destination = val 77 case "options": 78 mount.Options = strings.Split(val, ":") 79 default: 80 return mount, fmt.Errorf("mount option %q not supported", key) 81 } 82 } 83 84 return mount, nil 85 } 86 87 // Command runs a container 88 var Command = cli.Command{ 89 Name: "run", 90 Usage: "run a container", 91 ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]", 92 SkipArgReorder: true, 93 Flags: append([]cli.Flag{ 94 cli.BoolFlag{ 95 Name: "rm", 96 Usage: "remove the container after running", 97 }, 98 cli.BoolFlag{ 99 Name: "null-io", 100 Usage: "send all IO to /dev/null", 101 }, 102 cli.StringFlag{ 103 Name: "log-uri", 104 Usage: "log uri", 105 }, 106 cli.BoolFlag{ 107 Name: "detach,d", 108 Usage: "detach from the task after it has started execution", 109 }, 110 cli.StringFlag{ 111 Name: "fifo-dir", 112 Usage: "directory used for storing IO FIFOs", 113 }, 114 cli.StringFlag{ 115 Name: "cgroup", 116 Usage: "cgroup path (To disable use of cgroup, set to \"\" explicitly)", 117 }, 118 cli.StringFlag{ 119 Name: "platform", 120 Usage: "run image for specific platform", 121 }, 122 }, append(platformRunFlags, append(commands.SnapshotterFlags, commands.ContainerFlags...)...)...), 123 Action: func(context *cli.Context) error { 124 var ( 125 err error 126 id string 127 ref string 128 129 tty = context.Bool("tty") 130 detach = context.Bool("detach") 131 config = context.IsSet("config") 132 ) 133 134 if config { 135 id = context.Args().First() 136 if context.NArg() > 1 { 137 return errors.New("with spec config file, only container id should be provided") 138 } 139 } else { 140 id = context.Args().Get(1) 141 ref = context.Args().First() 142 143 if ref == "" { 144 return errors.New("image ref must be provided") 145 } 146 } 147 if id == "" { 148 return errors.New("container id must be provided") 149 } 150 client, ctx, cancel, err := commands.NewClient(context) 151 if err != nil { 152 return err 153 } 154 defer cancel() 155 container, err := NewContainer(ctx, client, context) 156 if err != nil { 157 return err 158 } 159 if context.Bool("rm") && !detach { 160 defer container.Delete(ctx, containerd.WithSnapshotCleanup) 161 } 162 var con console.Console 163 if tty { 164 con = console.Current() 165 defer con.Reset() 166 if err := con.SetRaw(); err != nil { 167 return err 168 } 169 } 170 opts := getNewTaskOpts(context) 171 ioOpts := []cio.Opt{cio.WithFIFODir(context.String("fifo-dir"))} 172 task, err := tasks.NewTask(ctx, client, container, context.String("checkpoint"), con, context.Bool("null-io"), context.String("log-uri"), ioOpts, opts...) 173 if err != nil { 174 return err 175 } 176 var statusC <-chan containerd.ExitStatus 177 if !detach { 178 defer task.Delete(ctx) 179 if statusC, err = task.Wait(ctx); err != nil { 180 return err 181 } 182 } 183 if context.IsSet("pid-file") { 184 if err := commands.WritePidFile(context.String("pid-file"), int(task.Pid())); err != nil { 185 return err 186 } 187 } 188 if err := task.Start(ctx); err != nil { 189 return err 190 } 191 if detach { 192 return nil 193 } 194 if tty { 195 if err := tasks.HandleConsoleResize(ctx, task, con); err != nil { 196 logrus.WithError(err).Error("console resize") 197 } 198 } else { 199 sigc := commands.ForwardAllSignals(ctx, task) 200 defer commands.StopCatch(sigc) 201 } 202 status := <-statusC 203 code, _, err := status.Result() 204 if err != nil { 205 return err 206 } 207 if _, err := task.Delete(ctx); err != nil { 208 return err 209 } 210 if code != 0 { 211 return cli.NewExitError("", int(code)) 212 } 213 return nil 214 }, 215 }