diff --git a/cmd/ctr/attach.go b/cmd/ctr/attach.go new file mode 100644 index 000000000..acec373fd --- /dev/null +++ b/cmd/ctr/attach.go @@ -0,0 +1,64 @@ +package main + +import ( + "os" + + "github.com/Sirupsen/logrus" + "github.com/containerd/console" + "github.com/containerd/containerd" + "github.com/urfave/cli" +) + +var attachCommand = cli.Command{ + Name: "attach", + Usage: "attach to the IO of a running container", + ArgsUsage: "CONTAINER", + Action: func(context *cli.Context) error { + ctx, cancel := appContext(context) + defer cancel() + client, err := newClient(context) + if err != nil { + return err + } + container, err := client.LoadContainer(ctx, context.Args().First()) + if err != nil { + return err + } + spec, err := container.Spec() + if err != nil { + return err + } + var ( + con console.Console + tty = spec.Process.Terminal + ) + if tty { + con = console.Current() + defer con.Reset() + if err := con.SetRaw(); err != nil { + return err + } + } + task, err := container.Task(ctx, containerd.WithAttach(os.Stdin, os.Stdout, os.Stderr)) + if err != nil { + return err + } + defer task.Delete(ctx) + if tty { + if err := handleConsoleResize(ctx, task, con); err != nil { + logrus.WithError(err).Error("console resize") + } + } else { + sigc := forwardAllSignals(ctx, task) + defer stopCatch(sigc) + } + status, err := task.Wait(ctx) + if err != nil { + return err + } + if status != 0 { + return cli.NewExitError("", int(status)) + } + return nil + }, +} diff --git a/cmd/ctr/delete.go b/cmd/ctr/delete.go index 34421371c..7acdc40e5 100644 --- a/cmd/ctr/delete.go +++ b/cmd/ctr/delete.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/containerd/containerd" "github.com/urfave/cli" ) @@ -21,9 +22,20 @@ var deleteCommand = cli.Command{ if err != nil { return err } - if _, err := container.Task(ctx, nil); err == nil { - return fmt.Errorf("cannot delete a container with a running task") + task, err := container.Task(ctx, nil) + if err != nil { + return container.Delete(ctx) } - return container.Delete(ctx) + status, err := task.Status(ctx) + if err != nil { + return err + } + if status == containerd.Stopped { + if _, err := task.Delete(ctx); err != nil { + return err + } + return container.Delete(ctx) + } + return fmt.Errorf("cannot delete a container with an existing task") }, } diff --git a/cmd/ctr/main.go b/cmd/ctr/main.go index 44ed6ebda..e0ca777f5 100644 --- a/cmd/ctr/main.go +++ b/cmd/ctr/main.go @@ -52,6 +52,7 @@ containerd CLI }, } app.Commands = append([]cli.Command{ + attachCommand, checkpointCommand, runCommand, deleteCommand, diff --git a/container.go b/container.go index 792af4ed0..73760c05d 100644 --- a/container.go +++ b/container.go @@ -193,11 +193,16 @@ func (c *container) loadTask(ctx context.Context, ioAttach IOAttach) (Task, erro return nil, err } } + // create and close a channel on load as we already have the pid + // and don't want to block calls to Wait(), etc... + ps := make(chan struct{}) + close(ps) t := &task{ client: c.client, io: i, containerID: response.Task.ContainerID, pid: response.Task.Pid, + pidSync: ps, } c.task = t return t, nil diff --git a/task.go b/task.go index 25753a4ae..7d776a16d 100644 --- a/task.go +++ b/task.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "runtime" + "strings" "syscall" "github.com/containerd/containerd/api/services/containers" @@ -127,7 +128,7 @@ func (t *task) Status(ctx context.Context) (TaskStatus, error) { if err != nil { return "", err } - return TaskStatus(r.Task.Status.String()), nil + return TaskStatus(strings.ToLower(r.Task.Status.String())), nil } // Wait is a blocking call that will wait for the task to exit and return the exit status