containerd/cmd/ctr/shim.go
Michael Crosby 63878d14ea Add create/start to exec processes in shim
This splits up the create and start of an exec process in the shim to
have two separate steps like the initial process.  This will allow
better state reporting for individual process along with a more robust
wait for execs.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2017-08-02 13:50:08 -04:00

322 lines
7.1 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(), &shim.StartRequest{
ID: context.Args().First(),
})
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,
}
if _, err := service.Exec(ctx, rq); err != nil {
return err
}
r, err := service.Start(ctx, &shim.StartRequest{
ID: id,
})
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
}