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  }