github.com/containerd/Containerd@v1.4.13/cmd/ctr/commands/shim/shim.go (about)

     1  // +build !windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package shim
    20  
    21  import (
    22  	gocontext "context"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/containerd/console"
    30  	"github.com/containerd/containerd/cmd/ctr/commands"
    31  	"github.com/containerd/containerd/namespaces"
    32  	"github.com/containerd/containerd/runtime/v2/shim"
    33  	"github.com/containerd/containerd/runtime/v2/task"
    34  	"github.com/containerd/ttrpc"
    35  	"github.com/containerd/typeurl"
    36  	ptypes "github.com/gogo/protobuf/types"
    37  	"github.com/opencontainers/runtime-spec/specs-go"
    38  	"github.com/pkg/errors"
    39  	"github.com/sirupsen/logrus"
    40  	"github.com/urfave/cli"
    41  )
    42  
    43  var fifoFlags = []cli.Flag{
    44  	cli.StringFlag{
    45  		Name:  "stdin",
    46  		Usage: "specify the path to the stdin fifo",
    47  	},
    48  	cli.StringFlag{
    49  		Name:  "stdout",
    50  		Usage: "specify the path to the stdout fifo",
    51  	},
    52  	cli.StringFlag{
    53  		Name:  "stderr",
    54  		Usage: "specify the path to the stderr fifo",
    55  	},
    56  	cli.BoolFlag{
    57  		Name:  "tty,t",
    58  		Usage: "enable tty support",
    59  	},
    60  }
    61  
    62  // Command is the cli command for interacting with a task
    63  var Command = cli.Command{
    64  	Name:  "shim",
    65  	Usage: "interact with a shim directly",
    66  	Flags: []cli.Flag{
    67  		cli.StringFlag{
    68  			Name:  "id",
    69  			Usage: "container id",
    70  		},
    71  	},
    72  	Subcommands: []cli.Command{
    73  		deleteCommand,
    74  		execCommand,
    75  		startCommand,
    76  		stateCommand,
    77  	},
    78  }
    79  
    80  var startCommand = cli.Command{
    81  	Name:  "start",
    82  	Usage: "start a container with a task",
    83  	Action: func(context *cli.Context) error {
    84  		service, err := getTaskService(context)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		_, err = service.Start(gocontext.Background(), &task.StartRequest{
    89  			ID: context.Args().First(),
    90  		})
    91  		return err
    92  	},
    93  }
    94  
    95  var deleteCommand = cli.Command{
    96  	Name:  "delete",
    97  	Usage: "delete a container with a task",
    98  	Action: func(context *cli.Context) error {
    99  		service, err := getTaskService(context)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		r, err := service.Delete(gocontext.Background(), &task.DeleteRequest{
   104  			ID: context.Args().First(),
   105  		})
   106  		if err != nil {
   107  			return err
   108  		}
   109  		fmt.Printf("container deleted and returned exit status %d\n", r.ExitStatus)
   110  		return nil
   111  	},
   112  }
   113  
   114  var stateCommand = cli.Command{
   115  	Name:  "state",
   116  	Usage: "get the state of all the processes of the task",
   117  	Action: func(context *cli.Context) error {
   118  		service, err := getTaskService(context)
   119  		if err != nil {
   120  			return err
   121  		}
   122  		r, err := service.State(gocontext.Background(), &task.StateRequest{
   123  			ID: context.GlobalString("id"),
   124  		})
   125  		if err != nil {
   126  			return err
   127  		}
   128  		commands.PrintAsJSON(r)
   129  		return nil
   130  	},
   131  }
   132  
   133  var execCommand = cli.Command{
   134  	Name:  "exec",
   135  	Usage: "exec a new process in the task's container",
   136  	Flags: append(fifoFlags,
   137  		cli.BoolFlag{
   138  			Name:  "attach,a",
   139  			Usage: "stay attached to the container and open the fifos",
   140  		},
   141  		cli.StringSliceFlag{
   142  			Name:  "env,e",
   143  			Usage: "add environment vars",
   144  			Value: &cli.StringSlice{},
   145  		},
   146  		cli.StringFlag{
   147  			Name:  "cwd",
   148  			Usage: "current working directory",
   149  		},
   150  		cli.StringFlag{
   151  			Name:  "spec",
   152  			Usage: "runtime spec",
   153  		},
   154  	),
   155  	Action: func(context *cli.Context) error {
   156  		service, err := getTaskService(context)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		var (
   161  			id  = context.Args().First()
   162  			ctx = gocontext.Background()
   163  		)
   164  
   165  		if id == "" {
   166  			return errors.New("exec id must be provided")
   167  		}
   168  
   169  		tty := context.Bool("tty")
   170  		wg, err := prepareStdio(context.String("stdin"), context.String("stdout"), context.String("stderr"), tty)
   171  		if err != nil {
   172  			return err
   173  		}
   174  
   175  		// read spec file and extract Any object
   176  		spec, err := ioutil.ReadFile(context.String("spec"))
   177  		if err != nil {
   178  			return err
   179  		}
   180  		url, err := typeurl.TypeURL(specs.Process{})
   181  		if err != nil {
   182  			return err
   183  		}
   184  
   185  		rq := &task.ExecProcessRequest{
   186  			ID: id,
   187  			Spec: &ptypes.Any{
   188  				TypeUrl: url,
   189  				Value:   spec,
   190  			},
   191  			Stdin:    context.String("stdin"),
   192  			Stdout:   context.String("stdout"),
   193  			Stderr:   context.String("stderr"),
   194  			Terminal: tty,
   195  		}
   196  		if _, err := service.Exec(ctx, rq); err != nil {
   197  			return err
   198  		}
   199  		r, err := service.Start(ctx, &task.StartRequest{
   200  			ID: id,
   201  		})
   202  		if err != nil {
   203  			return err
   204  		}
   205  		fmt.Printf("exec running with pid %d\n", r.Pid)
   206  		if context.Bool("attach") {
   207  			logrus.Info("attaching")
   208  			if tty {
   209  				current := console.Current()
   210  				defer current.Reset()
   211  				if err := current.SetRaw(); err != nil {
   212  					return err
   213  				}
   214  				size, err := current.Size()
   215  				if err != nil {
   216  					return err
   217  				}
   218  				if _, err := service.ResizePty(ctx, &task.ResizePtyRequest{
   219  					ID:     id,
   220  					Width:  uint32(size.Width),
   221  					Height: uint32(size.Height),
   222  				}); err != nil {
   223  					return err
   224  				}
   225  			}
   226  			wg.Wait()
   227  		}
   228  		return nil
   229  	},
   230  }
   231  
   232  func getTaskService(context *cli.Context) (task.TaskService, error) {
   233  	id := context.GlobalString("id")
   234  	if id == "" {
   235  		return nil, fmt.Errorf("container id must be specified")
   236  	}
   237  	ns := context.GlobalString("namespace")
   238  
   239  	// /containerd-shim/ns/id/shim.sock is the old way to generate shim socket,
   240  	// compatible it
   241  	s1 := filepath.Join(string(filepath.Separator), "containerd-shim", ns, id, "shim.sock")
   242  	// this should not error, ctr always get a default ns
   243  	ctx := namespaces.WithNamespace(gocontext.Background(), ns)
   244  	s2, _ := shim.SocketAddress(ctx, context.GlobalString("address"), id)
   245  	s2 = strings.TrimPrefix(s2, "unix://")
   246  
   247  	for _, socket := range []string{s2, "\x00" + s1} {
   248  		conn, err := net.Dial("unix", socket)
   249  		if err == nil {
   250  			client := ttrpc.NewClient(conn)
   251  
   252  			// TODO(stevvooe): This actually leaks the connection. We were leaking it
   253  			// before, so may not be a huge deal.
   254  
   255  			return task.NewTaskClient(client), nil
   256  		}
   257  	}
   258  
   259  	return nil, fmt.Errorf("fail to connect to container %s's shim", id)
   260  }