github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/cmd/ctr/commands/tasks/exec.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 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/sirupsen/logrus"
    30  	"github.com/urfave/cli"
    31  )
    32  
    33  //TODO:(jessvalarezo) exec-id is optional here, update to required arg
    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  			Usage: "exec specific id for the process",
    55  		},
    56  		cli.StringFlag{
    57  			Name:  "fifo-dir",
    58  			Usage: "directory used for storing IO FIFOs",
    59  		},
    60  		cli.StringFlag{
    61  			Name:  "log-uri",
    62  			Usage: "log uri for custom shim logging",
    63  		},
    64  	},
    65  	Action: func(context *cli.Context) error {
    66  		var (
    67  			id     = context.Args().First()
    68  			args   = context.Args().Tail()
    69  			tty    = context.Bool("tty")
    70  			detach = context.Bool("detach")
    71  		)
    72  		if id == "" {
    73  			return errors.New("container id must be provided")
    74  		}
    75  		client, ctx, cancel, err := commands.NewClient(context)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		defer cancel()
    80  		container, err := client.LoadContainer(ctx, id)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		spec, err := container.Spec(ctx)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		task, err := container.Task(ctx, nil)
    89  		if err != nil {
    90  			return err
    91  		}
    92  
    93  		pspec := spec.Process
    94  		pspec.Terminal = tty
    95  		pspec.Args = args
    96  
    97  		var (
    98  			ioCreator cio.Creator
    99  			stdinC    = &stdinCloser{
   100  				stdin: os.Stdin,
   101  			}
   102  		)
   103  
   104  		if logURI := context.String("log-uri"); logURI != "" {
   105  			uri, err := url.Parse(logURI)
   106  			if err != nil {
   107  				return err
   108  			}
   109  
   110  			if dir := context.String("fifo-dir"); dir != "" {
   111  				return errors.New("can't use log-uri with fifo-dir")
   112  			}
   113  
   114  			if tty {
   115  				return errors.New("can't use log-uri with tty")
   116  			}
   117  
   118  			ioCreator = cio.LogURI(uri)
   119  		} else {
   120  			cioOpts := []cio.Opt{cio.WithStreams(stdinC, os.Stdout, os.Stderr), cio.WithFIFODir(context.String("fifo-dir"))}
   121  			if tty {
   122  				cioOpts = append(cioOpts, cio.WithTerminal)
   123  			}
   124  			ioCreator = cio.NewCreator(cioOpts...)
   125  		}
   126  
   127  		process, err := task.Exec(ctx, context.String("exec-id"), pspec, ioCreator)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		stdinC.closer = func() {
   132  			process.CloseIO(ctx, containerd.WithStdinCloser)
   133  		}
   134  		// if detach, we should not call this defer
   135  		if !detach {
   136  			defer process.Delete(ctx)
   137  		}
   138  
   139  		statusC, err := process.Wait(ctx)
   140  		if err != nil {
   141  			return err
   142  		}
   143  
   144  		var con console.Console
   145  		if tty {
   146  			con = console.Current()
   147  			defer con.Reset()
   148  			if err := con.SetRaw(); err != nil {
   149  				return err
   150  			}
   151  		}
   152  		if !detach {
   153  			if tty {
   154  				if err := HandleConsoleResize(ctx, process, con); err != nil {
   155  					logrus.WithError(err).Error("console resize")
   156  				}
   157  			} else {
   158  				sigc := commands.ForwardAllSignals(ctx, process)
   159  				defer commands.StopCatch(sigc)
   160  			}
   161  		}
   162  
   163  		if err := process.Start(ctx); err != nil {
   164  			return err
   165  		}
   166  		if detach {
   167  			return nil
   168  		}
   169  		status := <-statusC
   170  		code, _, err := status.Result()
   171  		if err != nil {
   172  			return err
   173  		}
   174  		if code != 0 {
   175  			return cli.NewExitError("", int(code))
   176  		}
   177  		return nil
   178  	},
   179  }
   180  
   181  type stdinCloser struct {
   182  	stdin  *os.File
   183  	closer func()
   184  }
   185  
   186  func (s *stdinCloser) Read(p []byte) (int, error) {
   187  	n, err := s.stdin.Read(p)
   188  	if err == io.EOF {
   189  		if s.closer != nil {
   190  			s.closer()
   191  		}
   192  	}
   193  	return n, err
   194  }