Add tty support from client

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2015-12-14 16:47:42 -08:00
parent b2e649b164
commit d542ece69c
9 changed files with 80 additions and 20 deletions

View file

@ -36,6 +36,7 @@ func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContaine
e.Stdout = c.Stdout e.Stdout = c.Stdout
e.Stderr = c.Stderr e.Stderr = c.Stderr
e.Stdin = c.Stdin e.Stdin = c.Stdin
e.Console = c.Console
if c.Checkpoint != "" { if c.Checkpoint != "" {
e.Checkpoint = &runtime.Checkpoint{ e.Checkpoint = &runtime.Checkpoint{
Name: c.Checkpoint, Name: c.Checkpoint,

View file

@ -11,6 +11,8 @@ import (
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/docker/containerd/api/grpc/types" "github.com/docker/containerd/api/grpc/types"
"github.com/docker/docker/pkg/term"
"github.com/opencontainers/runc/libcontainer"
netcontext "golang.org/x/net/context" netcontext "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -72,6 +74,10 @@ var StartCommand = cli.Command{
Name: "interactive,i", Name: "interactive,i",
Usage: "connect to the stdio of the container", Usage: "connect to the stdio of the container",
}, },
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a tty for use with the container",
},
}, },
Action: func(context *cli.Context) { Action: func(context *cli.Context) {
var ( var (
@ -84,6 +90,11 @@ var StartCommand = cli.Command{
if id == "" { if id == "" {
fatal("container id cannot be empty", 1) fatal("container id cannot be empty", 1)
} }
c := getClient()
events, err := c.Events(netcontext.Background(), &types.EventsRequest{})
if err != nil {
fatal(err.Error(), 1)
}
r := &types.CreateContainerRequest{ r := &types.CreateContainerRequest{
Id: id, Id: id,
BundlePath: path, BundlePath: path,
@ -94,17 +105,57 @@ var StartCommand = cli.Command{
fatal(err.Error(), 1) fatal(err.Error(), 1)
} }
} }
c := getClient() if context.Bool("tty") {
if err := attachTty(r); err != nil {
fatal(err.Error(), 1)
}
}
if _, err := c.CreateContainer(netcontext.Background(), r); err != nil { if _, err := c.CreateContainer(netcontext.Background(), r); err != nil {
fatal(err.Error(), 1) fatal(err.Error(), 1)
} }
if stdin != nil { if stdin != nil {
go func() {
io.Copy(stdin, os.Stdin) io.Copy(stdin, os.Stdin)
if state != nil {
term.RestoreTerminal(os.Stdin.Fd(), state)
}
}()
for {
e, err := events.Recv()
if err != nil {
fatal(err.Error(), 1)
}
if e.Id == id && e.Type == "exit" {
os.Exit(int(e.Status))
}
}
} }
}, },
} }
var stdin io.WriteCloser var (
stdin io.WriteCloser
state *term.State
)
func attachTty(r *types.CreateContainerRequest) error {
console, err := libcontainer.NewConsole(os.Getuid(), os.Getgid())
if err != nil {
return err
}
r.Console = console.Path()
stdin = console
go func() {
io.Copy(os.Stdout, console)
console.Close()
}()
s, err := term.SetRawTerminal(os.Stdin.Fd())
if err != nil {
return err
}
state = s
return nil
}
func attachStdio(r *types.CreateContainerRequest) error { func attachStdio(r *types.CreateContainerRequest) error {
dir, err := ioutil.TempDir("", "ctr-") dir, err := ioutil.TempDir("", "ctr-")

View file

@ -41,6 +41,7 @@ type Event struct {
Stdout string Stdout string
Stderr string Stderr string
Stdin string Stdin string
Console string
Pid int Pid int
Status int Status int
Signal os.Signal Signal os.Signal

2
io.go
View file

@ -53,9 +53,11 @@ type copier struct {
func (l *copier) Close() (err error) { func (l *copier) Close() (err error) {
for _, c := range append(l.closers, l.config.Stdin, l.config.Stdout, l.config.Stderr) { for _, c := range append(l.closers, l.config.Stdin, l.config.Stdout, l.config.Stderr) {
if c != nil {
if cerr := c.Close(); err == nil { if cerr := c.Close(); err == nil {
err = cerr err = cerr
} }
} }
}
return err return err
} }

View file

@ -188,9 +188,18 @@ func (p *libcontainerProcess) Signal(s os.Signal) error {
func (p *libcontainerProcess) Close() error { func (p *libcontainerProcess) Close() error {
// in close we always need to call wait to close/flush any pipes // in close we always need to call wait to close/flush any pipes
_, err := p.process.Wait() _, err := p.process.Wait()
p.process.Stdin.(io.Closer).Close() // explicitly close any open fd on the process
p.process.Stdout.(io.Closer).Close() for _, cl := range []interface{}{
p.process.Stderr.(io.Closer).Close() p.process.Stderr,
p.process.Stdout,
p.process.Stdin,
} {
if cl != nil {
if c, ok := cl.(io.Closer); ok {
c.Close()
}
}
}
return err return err
} }
@ -375,7 +384,7 @@ func (r *libcontainerRuntime) Type() string {
return "libcontainer" return "libcontainer"
} }
func (r *libcontainerRuntime) Create(id, bundlePath string) (runtime.Container, *runtime.IO, error) { func (r *libcontainerRuntime) Create(id, bundlePath, consolePath string) (runtime.Container, *runtime.IO, error) {
spec, rspec, err := r.loadSpec( spec, rspec, err := r.loadSpec(
filepath.Join(bundlePath, "config.json"), filepath.Join(bundlePath, "config.json"),
filepath.Join(bundlePath, "runtime.json"), filepath.Join(bundlePath, "runtime.json"),
@ -397,11 +406,9 @@ func (r *libcontainerRuntime) Create(id, bundlePath string) (runtime.Container,
} }
var rio runtime.IO var rio runtime.IO
if spec.Process.Terminal { if spec.Process.Terminal {
console, err := process.NewConsole(int(spec.Process.User.UID)) if err := process.ConsoleFromPath(consolePath); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
rio.Console = console
} else { } else {
i, err := process.InitializeIO(int(spec.Process.User.UID)) i, err := process.InitializeIO(int(spec.Process.User.UID))
if err != nil { if err != nil {

View file

@ -147,7 +147,7 @@ func (r *runcRuntime) Type() string {
return "runc" return "runc"
} }
func (r *runcRuntime) Create(id, bundlePath string) (runtime.Container, *runtime.IO, error) { func (r *runcRuntime) Create(id, bundlePath, consolePath string) (runtime.Container, *runtime.IO, error) {
var s specs.Spec var s specs.Spec
f, err := os.Open(filepath.Join(bundlePath, "config.json")) f, err := os.Open(filepath.Join(bundlePath, "config.json"))
if err != nil { if err != nil {

View file

@ -35,7 +35,6 @@ type IO struct {
Stdin io.WriteCloser Stdin io.WriteCloser
Stdout io.ReadCloser Stdout io.ReadCloser
Stderr io.ReadCloser Stderr io.ReadCloser
Console Console
} }
func (i *IO) Close() error { func (i *IO) Close() error {
@ -44,7 +43,6 @@ func (i *IO) Close() error {
i.Stdin, i.Stdin,
i.Stdout, i.Stdout,
i.Stderr, i.Stderr,
i.Console,
} { } {
if c != nil { if c != nil {
if err := c.Close(); oerr == nil { if err := c.Close(); oerr == nil {

View file

@ -20,7 +20,7 @@ type Runtime interface {
// Type of the runtime // Type of the runtime
Type() string Type() string
// Create creates a new container initialized but without it starting it // Create creates a new container initialized but without it starting it
Create(id, bundlePath string) (Container, *IO, error) Create(id, bundlePath, consolePath string) (Container, *IO, error)
// StartProcess adds a new process to the container // StartProcess adds a new process to the container
StartProcess(Container, specs.Process) (Process, *IO, error) StartProcess(Container, specs.Process) (Process, *IO, error)
} }

View file

@ -5,7 +5,7 @@ type StartEvent struct {
} }
func (h *StartEvent) Handle(e *Event) error { func (h *StartEvent) Handle(e *Event) error {
container, io, err := h.s.runtime.Create(e.ID, e.BundlePath) container, io, err := h.s.runtime.Create(e.ID, e.BundlePath, e.Console)
if err != nil { if err != nil {
return err return err
} }