Add tty support from client
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
b2e649b164
commit
d542ece69c
9 changed files with 80 additions and 20 deletions
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
io.Copy(stdin, os.Stdin)
|
go func() {
|
||||||
|
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-")
|
||||||
|
|
1
event.go
1
event.go
|
@ -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
|
||||||
|
|
6
io.go
6
io.go
|
@ -53,8 +53,10 @@ 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 cerr := c.Close(); err == nil {
|
if c != nil {
|
||||||
err = cerr
|
if cerr := c.Close(); err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -32,10 +32,9 @@ type Console interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IO struct {
|
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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
2
start.go
2
start.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue