315 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build !windows
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"time"
 | |
| 
 | |
| 	gocontext "context"
 | |
| 
 | |
| 	"google.golang.org/grpc"
 | |
| 	"google.golang.org/grpc/grpclog"
 | |
| 
 | |
| 	"github.com/containerd/console"
 | |
| 	shim "github.com/containerd/containerd/linux/shim/v1"
 | |
| 	"github.com/containerd/containerd/typeurl"
 | |
| 	protobuf "github.com/gogo/protobuf/types"
 | |
| 	google_protobuf "github.com/golang/protobuf/ptypes/empty"
 | |
| 	"github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 	"github.com/urfave/cli"
 | |
| )
 | |
| 
 | |
| var empty = &google_protobuf.Empty{}
 | |
| 
 | |
| var fifoFlags = []cli.Flag{
 | |
| 	cli.StringFlag{
 | |
| 		Name:  "stdin",
 | |
| 		Usage: "specify the path to the stdin fifo",
 | |
| 	},
 | |
| 	cli.StringFlag{
 | |
| 		Name:  "stdout",
 | |
| 		Usage: "specify the path to the stdout fifo",
 | |
| 	},
 | |
| 	cli.StringFlag{
 | |
| 		Name:  "stderr",
 | |
| 		Usage: "specify the path to the stderr fifo",
 | |
| 	},
 | |
| 	cli.BoolFlag{
 | |
| 		Name:  "tty,t",
 | |
| 		Usage: "enable tty support",
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var shimCommand = cli.Command{
 | |
| 	Name:  "shim",
 | |
| 	Usage: "interact with a shim directly",
 | |
| 	Flags: []cli.Flag{
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "socket",
 | |
| 			Usage: "socket on which to connect to the shim",
 | |
| 		},
 | |
| 	},
 | |
| 	Subcommands: []cli.Command{
 | |
| 		shimCreateCommand,
 | |
| 		shimStartCommand,
 | |
| 		shimDeleteCommand,
 | |
| 		shimStateCommand,
 | |
| 		shimExecCommand,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var shimCreateCommand = cli.Command{
 | |
| 	Name:  "create",
 | |
| 	Usage: "create a container with a shim",
 | |
| 	Flags: append(fifoFlags,
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "bundle",
 | |
| 			Usage: "bundle path for the container",
 | |
| 		},
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "runtime",
 | |
| 			Value: "runc",
 | |
| 			Usage: "runtime to use for the container",
 | |
| 		},
 | |
| 		cli.BoolFlag{
 | |
| 			Name:  "attach,a",
 | |
| 			Usage: "stay attached to the container and open the fifos",
 | |
| 		},
 | |
| 	),
 | |
| 	Action: func(context *cli.Context) error {
 | |
| 		var (
 | |
| 			id  = context.Args().First()
 | |
| 			ctx = gocontext.Background()
 | |
| 		)
 | |
| 
 | |
| 		if id == "" {
 | |
| 			return errors.New("container id must be provided")
 | |
| 		}
 | |
| 		service, err := getShimService(context)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		tty := context.Bool("tty")
 | |
| 		wg, err := prepareStdio(context.String("stdin"), context.String("stdout"), context.String("stderr"), tty)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		r, err := service.Create(ctx, &shim.CreateTaskRequest{
 | |
| 			ID:       id,
 | |
| 			Bundle:   context.String("bundle"),
 | |
| 			Runtime:  context.String("runtime"),
 | |
| 			Stdin:    context.String("stdin"),
 | |
| 			Stdout:   context.String("stdout"),
 | |
| 			Stderr:   context.String("stderr"),
 | |
| 			Terminal: tty,
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		fmt.Printf("container created with id %s and pid %d\n", id, r.Pid)
 | |
| 		if context.Bool("attach") {
 | |
| 			if tty {
 | |
| 				current := console.Current()
 | |
| 				defer current.Reset()
 | |
| 				if err := current.SetRaw(); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				size, err := current.Size()
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				if _, err := service.ResizePty(ctx, &shim.ResizePtyRequest{
 | |
| 					ID:     id,
 | |
| 					Width:  uint32(size.Width),
 | |
| 					Height: uint32(size.Height),
 | |
| 				}); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			wg.Wait()
 | |
| 		}
 | |
| 		return nil
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var shimStartCommand = cli.Command{
 | |
| 	Name:  "start",
 | |
| 	Usage: "start a container with a shim",
 | |
| 	Action: func(context *cli.Context) error {
 | |
| 		service, err := getShimService(context)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		_, err = service.Start(gocontext.Background(), empty)
 | |
| 		return err
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var shimDeleteCommand = cli.Command{
 | |
| 	Name:  "delete",
 | |
| 	Usage: "delete a container with a shim",
 | |
| 	Action: func(context *cli.Context) error {
 | |
| 		service, err := getShimService(context)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		r, err := service.Delete(gocontext.Background(), empty)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		fmt.Printf("container deleted and returned exit status %d\n", r.ExitStatus)
 | |
| 		return nil
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var shimStateCommand = cli.Command{
 | |
| 	Name:  "state",
 | |
| 	Usage: "get the state of all the processes of the shim",
 | |
| 	Action: func(context *cli.Context) error {
 | |
| 		service, err := getShimService(context)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		r, err := service.State(gocontext.Background(), &shim.StateRequest{
 | |
| 			ID: context.Args().First(),
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		data, err := json.Marshal(r)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		buf := bytes.NewBuffer(nil)
 | |
| 		if err := json.Indent(buf, data, " ", "    "); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		buf.WriteTo(os.Stdout)
 | |
| 		return nil
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var shimExecCommand = cli.Command{
 | |
| 	Name:  "exec",
 | |
| 	Usage: "exec a new process in the shim's container",
 | |
| 	Flags: append(fifoFlags,
 | |
| 		cli.BoolFlag{
 | |
| 			Name:  "attach,a",
 | |
| 			Usage: "stay attached to the container and open the fifos",
 | |
| 		},
 | |
| 		cli.StringSliceFlag{
 | |
| 			Name:  "env,e",
 | |
| 			Usage: "add environment vars",
 | |
| 			Value: &cli.StringSlice{},
 | |
| 		},
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "cwd",
 | |
| 			Usage: "current working directory",
 | |
| 		},
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "spec",
 | |
| 			Usage: "runtime spec",
 | |
| 		},
 | |
| 	),
 | |
| 	Action: func(context *cli.Context) error {
 | |
| 		service, err := getShimService(context)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		var (
 | |
| 			id  = context.Args().First()
 | |
| 			ctx = gocontext.Background()
 | |
| 		)
 | |
| 
 | |
| 		if id == "" {
 | |
| 			return errors.New("exec id must be provided")
 | |
| 		}
 | |
| 
 | |
| 		tty := context.Bool("tty")
 | |
| 		wg, err := prepareStdio(context.String("stdin"), context.String("stdout"), context.String("stderr"), tty)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		// read spec file and extract Any object
 | |
| 		spec, err := ioutil.ReadFile(context.String("spec"))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		url, err := typeurl.TypeURL(specs.Process{})
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		rq := &shim.ExecProcessRequest{
 | |
| 			ID: id,
 | |
| 			Spec: &protobuf.Any{
 | |
| 				TypeUrl: url,
 | |
| 				Value:   spec,
 | |
| 			},
 | |
| 			Stdin:    context.String("stdin"),
 | |
| 			Stdout:   context.String("stdout"),
 | |
| 			Stderr:   context.String("stderr"),
 | |
| 			Terminal: tty,
 | |
| 		}
 | |
| 		r, err := service.Exec(ctx, rq)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		fmt.Printf("exec running with pid %d\n", r.Pid)
 | |
| 		if context.Bool("attach") {
 | |
| 			logrus.Info("attaching")
 | |
| 			if tty {
 | |
| 				current := console.Current()
 | |
| 				defer current.Reset()
 | |
| 				if err := current.SetRaw(); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				size, err := current.Size()
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				if _, err := service.ResizePty(ctx, &shim.ResizePtyRequest{
 | |
| 					ID:     id,
 | |
| 					Width:  uint32(size.Width),
 | |
| 					Height: uint32(size.Height),
 | |
| 				}); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			wg.Wait()
 | |
| 		}
 | |
| 		return nil
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func getShimService(context *cli.Context) (shim.ShimClient, error) {
 | |
| 	bindSocket := context.GlobalString("socket")
 | |
| 	if bindSocket == "" {
 | |
| 		return nil, errors.New("socket path must be specified")
 | |
| 	}
 | |
| 
 | |
| 	// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
 | |
| 	grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
 | |
| 	dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(100 * time.Second)}
 | |
| 	dialOpts = append(dialOpts,
 | |
| 		grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
 | |
| 			return net.DialTimeout("unix", "\x00"+bindSocket, timeout)
 | |
| 		},
 | |
| 		))
 | |
| 	conn, err := grpc.Dial(fmt.Sprintf("unix://%s", bindSocket), dialOpts...)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return shim.NewShimClient(conn), nil
 | |
| }
 | 
