ctr: move tasks, run to commands package

Signed-off-by: Jess Valarezo <valarezo.jessica@gmail.com>
This commit is contained in:
Jess Valarezo
2017-10-31 11:57:41 -07:00
parent fe3d9a70fa
commit c3b70f1d0b
18 changed files with 271 additions and 244 deletions

248
cmd/ctr/commands/run/run.go Normal file
View File

@@ -0,0 +1,248 @@
package run
import (
gocontext "context"
"encoding/csv"
"fmt"
"runtime"
"strings"
"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
"github.com/containerd/containerd/containers"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
func withEnv(context *cli.Context) containerd.SpecOpts {
return func(_ gocontext.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error {
env := context.StringSlice("env")
if len(env) > 0 {
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, env)
}
return nil
}
}
func withMounts(context *cli.Context) containerd.SpecOpts {
return func(_ gocontext.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error {
for _, mount := range context.StringSlice("mount") {
m, err := parseMountFlag(mount)
if err != nil {
return err
}
s.Mounts = append(s.Mounts, m)
}
return nil
}
}
// parseMountFlag parses a mount string in the form "type=foo,source=/path,destination=/target,options=rbind:rw"
func parseMountFlag(m string) (specs.Mount, error) {
mount := specs.Mount{}
r := csv.NewReader(strings.NewReader(m))
fields, err := r.Read()
if err != nil {
return mount, err
}
for _, field := range fields {
v := strings.Split(field, "=")
if len(v) != 2 {
return mount, fmt.Errorf("invalid mount specification: expected key=val")
}
key := v[0]
val := v[1]
switch key {
case "type":
mount.Type = val
case "source", "src":
mount.Source = val
case "destination", "dst":
mount.Destination = val
case "options":
mount.Options = strings.Split(val, ":")
default:
return mount, fmt.Errorf("mount option %q not supported", key)
}
}
return mount, nil
}
// replaceOrAppendEnvValues returns the defaults with the overrides either
// replaced by env key or appended to the list
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
cache := make(map[string]int, len(defaults))
for i, e := range defaults {
parts := strings.SplitN(e, "=", 2)
cache[parts[0]] = i
}
for _, value := range overrides {
// Values w/o = means they want this env to be removed/unset.
if !strings.Contains(value, "=") {
if i, exists := cache[value]; exists {
defaults[i] = "" // Used to indicate it should be removed
}
continue
}
// Just do a normal set/update
parts := strings.SplitN(value, "=", 2)
if i, exists := cache[parts[0]]; exists {
defaults[i] = value
} else {
defaults = append(defaults, value)
}
}
// Now remove all entries that we want to "unset"
for i := 0; i < len(defaults); i++ {
if defaults[i] == "" {
defaults = append(defaults[:i], defaults[i+1:]...)
i--
}
}
return defaults
}
// Command runs a container
var Command = cli.Command{
Name: "run",
Usage: "run a container",
ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]",
Flags: append([]cli.Flag{
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a TTY for the container",
},
cli.StringFlag{
Name: "runtime",
Usage: "runtime name (io.containerd.runtime.v1.linux, io.containerd.runtime.v1.windows, io.containerd.runtime.v1.com.vmware.linux)",
Value: fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS),
},
cli.BoolFlag{
Name: "readonly",
Usage: "set the containers filesystem as readonly",
},
cli.BoolFlag{
Name: "net-host",
Usage: "enable host networking for the container",
},
cli.StringSliceFlag{
Name: "mount",
Usage: "specify additional container mount (ex: type=bind,src=/tmp,dest=/host,options=rbind:ro)",
},
cli.StringSliceFlag{
Name: "env",
Usage: "specify additional container environment variables (i.e. FOO=bar)",
},
cli.StringSliceFlag{
Name: "label",
Usage: "specify additional labels (foo=bar)",
},
cli.BoolFlag{
Name: "rm",
Usage: "remove the container after running",
},
cli.StringFlag{
Name: "checkpoint",
Usage: "provide the checkpoint digest to restore the container",
},
cli.StringFlag{
Name: "cwd",
Usage: "specify the working directory of the process",
},
cli.BoolFlag{
Name: "null-io",
Usage: "send all IO to /dev/null",
},
cli.BoolFlag{
Name: "detach,d",
Usage: "detach from the task after it has started execution",
},
}, commands.SnapshotterFlags...),
Action: func(context *cli.Context) error {
var (
err error
id = context.Args().Get(1)
imageRef = context.Args().First()
tty = context.Bool("tty")
detach = context.Bool("detach")
)
if imageRef == "" {
return errors.New("image ref must be provided")
}
if id == "" {
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := newContainer(ctx, client, context)
if err != nil {
return err
}
if context.Bool("rm") && !detach {
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
}
task, err := tasks.NewTask(ctx, client, container, context.String("checkpoint"), tty, context.Bool("null-io"))
if err != nil {
return err
}
var statusC <-chan containerd.ExitStatus
if !detach {
defer task.Delete(ctx)
if statusC, err = task.Wait(ctx); err != nil {
return err
}
}
var con console.Console
if tty {
con = console.Current()
defer con.Reset()
if err := con.SetRaw(); err != nil {
return err
}
}
if err := task.Start(ctx); err != nil {
return err
}
if detach {
return nil
}
if tty {
if err := tasks.HandleConsoleResize(ctx, task, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := commands.ForwardAllSignals(ctx, task)
defer commands.StopCatch(sigc)
}
status := <-statusC
code, _, err := status.Result()
if err != nil {
return err
}
if _, err := task.Delete(ctx); err != nil {
return err
}
if code != 0 {
return cli.NewExitError("", int(code))
}
return nil
},
}

View File

@@ -0,0 +1,84 @@
// +build !windows
package run
import (
gocontext "context"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
func init() {
Command.Flags = append(Command.Flags, cli.BoolFlag{
Name: "rootfs",
Usage: "use custom rootfs that is not managed by containerd snapshotter",
})
}
func withTTY() containerd.SpecOpts {
return containerd.WithTTY
}
func setHostNetworking() containerd.SpecOpts {
return containerd.WithHostNamespace(specs.NetworkNamespace)
}
func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
var (
ref = context.Args().First()
id = context.Args().Get(1)
args = context.Args()[2:]
)
if raw := context.String("checkpoint"); raw != "" {
im, err := client.GetImage(ctx, raw)
if err != nil {
return nil, err
}
return client.NewContainer(ctx, id, containerd.WithCheckpoint(im, id))
}
var (
opts []containerd.SpecOpts
cOpts []containerd.NewContainerOpts
)
cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
if context.Bool("rootfs") {
opts = append(opts, containerd.WithRootFSPath(ref))
} else {
image, err := client.GetImage(ctx, ref)
if err != nil {
return nil, err
}
opts = append(opts, containerd.WithImageConfig(image))
cOpts = append(cOpts, containerd.WithImage(image))
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
// Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
// after creating some mount points on demand.
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
}
if context.Bool("readonly") {
opts = append(opts, containerd.WithRootFSReadonly())
}
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
opts = append(opts, withEnv(context), withMounts(context))
if len(args) > 0 {
opts = append(opts, containerd.WithProcessArgs(args...))
}
if cwd := context.String("cwd"); cwd != "" {
opts = append(opts, containerd.WithProcessCwd(cwd))
}
if context.Bool("tty") {
opts = append(opts, withTTY())
}
if context.Bool("net-host") {
opts = append(opts, setHostNetworking(), containerd.WithHostHostsFile, containerd.WithHostResolvconf)
}
cOpts = append([]containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}, cOpts...)
return client.NewContainer(ctx, id, cOpts...)
}

View File

@@ -0,0 +1,89 @@
package run
import (
gocontext "context"
"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const pipeRoot = `\\.\pipe`
func init() {
Command.Flags = append(Command.Flags, cli.StringSliceFlag{
Name: "layer",
Usage: "HCSSHIM Layers to be used",
})
}
func withLayers(context *cli.Context) containerd.SpecOpts {
return func(ctx gocontext.Context, client *containerd.Client, c *containers.Container, s *specs.Spec) error {
l := context.StringSlice("layer")
if l == nil {
return errors.Wrap(errdefs.ErrInvalidArgument, "base layers must be specified with `--layer`")
}
s.Windows.LayerFolders = l
return nil
}
}
func withTTY(terminal bool) containerd.SpecOpts {
if !terminal {
return func(ctx gocontext.Context, client *containerd.Client, c *containers.Container, s *specs.Spec) error {
s.Process.Terminal = false
return nil
}
}
con := console.Current()
size, err := con.Size()
if err != nil {
logrus.WithError(err).Error("console size")
}
return containerd.WithTTY(int(size.Width), int(size.Height))
}
func setHostNetworking() containerd.SpecOpts {
return nil
}
func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
var (
// ref = context.Args().First()
id = context.Args().Get(1)
args = context.Args()[2:]
tty = context.Bool("tty")
labelStrings = context.StringSlice("label")
)
labels := commands.LabelArgs(labelStrings)
// TODO(mlaventure): get base image once we have a snapshotter
opts := []containerd.SpecOpts{
// TODO(mlaventure): use containerd.WithImageConfig once we have a snapshotter
withLayers(context),
withEnv(context),
withMounts(context),
withTTY(tty),
}
if len(args) > 0 {
opts = append(opts, containerd.WithProcessArgs(args...))
}
if cwd := context.String("cwd"); cwd != "" {
opts = append(opts, containerd.WithProcessCwd(cwd))
}
return client.NewContainer(ctx, id,
containerd.WithNewSpec(opts...),
containerd.WithContainerLabels(labels),
containerd.WithRuntime(context.String("runtime"), nil),
// TODO(mlaventure): containerd.WithImage(image),
)
}

View File

@@ -0,0 +1,72 @@
package tasks
import (
"os"
"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/sirupsen/logrus"
"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 {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
spec, err := container.Spec(ctx)
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)
statusC, err := task.Wait(ctx)
if err != nil {
return err
}
if tty {
if err := HandleConsoleResize(ctx, task, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := commands.ForwardAllSignals(ctx, task)
defer commands.StopCatch(sigc)
}
ec := <-statusC
code, _, err := ec.Result()
if err != nil {
return err
}
if code != 0 {
return cli.NewExitError("", int(code))
}
return nil
},
}

View File

@@ -0,0 +1,51 @@
package tasks
import (
"fmt"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var checkpointCommand = cli.Command{
Name: "checkpoint",
Usage: "checkpoint a container",
ArgsUsage: "[flags] CONTAINER",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "exit",
Usage: "stop the container after the checkpoint",
},
},
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
var opts []containerd.CheckpointTaskOpts
if context.Bool("exit") {
opts = append(opts, containerd.WithExit)
}
checkpoint, err := task.Checkpoint(ctx, opts...)
if err != nil {
return err
}
fmt.Println(checkpoint.Name())
return nil
},
}

View File

@@ -0,0 +1,35 @@
package tasks
import (
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/urfave/cli"
)
var deleteCommand = cli.Command{
Name: "delete",
Usage: "delete a task",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
status, err := task.Delete(ctx)
if err != nil {
return err
}
if ec := status.ExitCode(); ec != 0 {
return cli.NewExitError("", int(ec))
}
return nil
},
}

View File

@@ -0,0 +1,108 @@
package tasks
import (
"errors"
"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
//TODO:(jessvalarezo) exec-id is optional here, update to required arg
var execCommand = cli.Command{
Name: "exec",
Usage: "execute additional processes in an existing container",
ArgsUsage: "[flags] CONTAINER CMD [ARG...]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "cwd",
Usage: "working directory of the new process",
},
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a TTY for the container",
},
cli.StringFlag{
Name: "exec-id",
Usage: "exec specific id for the process",
},
},
Action: func(context *cli.Context) error {
var (
id = context.Args().First()
args = context.Args().Tail()
tty = context.Bool("tty")
)
if id == "" {
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
spec, err := container.Spec(ctx)
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
pspec := spec.Process
pspec.Terminal = tty
pspec.Args = args
io := containerd.Stdio
if tty {
io = containerd.StdioTerminal
}
process, err := task.Exec(ctx, context.String("exec-id"), pspec, io)
if err != nil {
return err
}
defer process.Delete(ctx)
statusC, err := process.Wait(ctx)
if err != nil {
return err
}
var con console.Console
if tty {
con = console.Current()
defer con.Reset()
if err := con.SetRaw(); err != nil {
return err
}
}
if tty {
if err := HandleConsoleResize(ctx, process, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := commands.ForwardAllSignals(ctx, process)
defer commands.StopCatch(sigc)
}
if err := process.Start(ctx); err != nil {
return err
}
status := <-statusC
code, _, err := status.Result()
if err != nil {
return err
}
if code != 0 {
return cli.NewExitError("", int(code))
}
return nil
},
}

View File

@@ -0,0 +1,62 @@
package tasks
import (
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
// TODO:(jessvalarezo) the pid flag is not used here
// update to be able to signal given pid
var killCommand = cli.Command{
Name: "kill",
Usage: "signal a container (default: SIGTERM)",
ArgsUsage: "[flags] CONTAINER",
Flags: []cli.Flag{
cli.StringFlag{
Name: "signal, s",
Value: "SIGTERM",
Usage: "signal to send to the container",
},
cli.IntFlag{
Name: "pid",
Usage: "pid to kill",
Value: 0,
},
cli.BoolFlag{
Name: "all, a",
Usage: "send signal to all processes inside the container",
},
},
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
signal, err := commands.ParseSignal(context.String("signal"))
if err != nil {
return err
}
var (
pid = context.Int("pid")
all = context.Bool("all")
)
if pid > 0 && all {
return errors.New("enter a pid or all; not both")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
return task.Kill(ctx, signal)
},
}

View File

@@ -0,0 +1,55 @@
package tasks
import (
"fmt"
"os"
"text/tabwriter"
tasks "github.com/containerd/containerd/api/services/tasks/v1"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/urfave/cli"
)
var listCommand = cli.Command{
Name: "list",
Usage: "list tasks",
Aliases: []string{"ls"},
ArgsUsage: "[flags]",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "quiet, q",
Usage: "print only the task id & pid",
},
},
Action: func(context *cli.Context) error {
quiet := context.Bool("quiet")
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
s := client.TaskService()
response, err := s.List(ctx, &tasks.ListTasksRequest{})
if err != nil {
return err
}
if quiet {
for _, task := range response.Tasks {
fmt.Println(task.ID)
}
return nil
}
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
fmt.Fprintln(w, "TASK\tPID\tSTATUS\t")
for _, task := range response.Tasks {
if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n",
task.ID,
task.Pid,
task.Status.String(),
); err != nil {
return err
}
}
return w.Flush()
},
}

View File

@@ -0,0 +1,28 @@
package tasks
import (
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/urfave/cli"
)
var pauseCommand = cli.Command{
Name: "pause",
Usage: "pause an existing container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
return task.Pause(ctx)
},
}

View File

@@ -0,0 +1,59 @@
package tasks
import (
"fmt"
"os"
"text/tabwriter"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/windows/hcsshimtypes"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var psCommand = cli.Command{
Name: "ps",
Usage: "list processes for container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
processes, err := task.Pids(ctx)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprintln(w, "PID\tINFO")
for _, ps := range processes {
if ps.Info != nil {
var details hcsshimtypes.ProcessDetails
if err := details.Unmarshal(ps.Info.Value); err == nil {
if _, err := fmt.Fprintf(w, "%d\t%+v\n", ps.Pid, details); err != nil {
return err
}
}
} else {
if _, err := fmt.Fprintf(w, "%d\t-\n", ps.Pid); err != nil {
return err
}
}
}
return w.Flush()
},
}

View File

@@ -0,0 +1,28 @@
package tasks
import (
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/urfave/cli"
)
var resumeCommand = cli.Command{
Name: "resume",
Usage: "resume a paused container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
return task.Resume(ctx)
},
}

View File

@@ -0,0 +1,90 @@
package tasks
import (
"github.com/containerd/console"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var startCommand = cli.Command{
Name: "start",
Usage: "start a container that have been created",
ArgsUsage: "CONTAINER",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "null-io",
Usage: "send all IO to /dev/null",
},
},
Action: func(context *cli.Context) error {
var (
err error
id = context.Args().Get(0)
)
if id == "" {
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
spec, err := container.Spec(ctx)
if err != nil {
return err
}
tty := spec.Process.Terminal
task, err := NewTask(ctx, client, container, "", tty, context.Bool("null-io"))
if err != nil {
return err
}
defer task.Delete(ctx)
statusC, err := task.Wait(ctx)
if err != nil {
return err
}
var con console.Console
if tty {
con = console.Current()
defer con.Reset()
if err := con.SetRaw(); err != nil {
return err
}
}
if err := task.Start(ctx); err != nil {
return err
}
if tty {
if err := HandleConsoleResize(ctx, task, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := commands.ForwardAllSignals(ctx, task)
defer commands.StopCatch(sigc)
}
status := <-statusC
code, _, err := status.Result()
if err != nil {
return err
}
if _, err := task.Delete(ctx); err != nil {
return err
}
if code != 0 {
return cli.NewExitError("", int(code))
}
return nil
},
}

View File

@@ -0,0 +1,30 @@
package tasks
import (
gocontext "context"
"github.com/urfave/cli"
)
type resizer interface {
Resize(ctx gocontext.Context, w, h uint32) error
}
// Command is the cli command for managing tasks
var Command = cli.Command{
Name: "tasks",
Usage: "manage tasks",
Aliases: []string{"t"},
Subcommands: []cli.Command{
attachCommand,
checkpointCommand,
deleteCommand,
execCommand,
listCommand,
killCommand,
pauseCommand,
psCommand,
resumeCommand,
startCommand,
},
}

View File

@@ -0,0 +1,64 @@
// +build !windows
package tasks
import (
gocontext "context"
"os"
"os/signal"
"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// HandleConsoleResize resizes the console
func HandleConsoleResize(ctx gocontext.Context, task resizer, con console.Console) error {
// do an initial resize of the console
size, err := con.Size()
if err != nil {
return err
}
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
log.G(ctx).WithError(err).Error("resize pty")
}
s := make(chan os.Signal, 16)
signal.Notify(s, unix.SIGWINCH)
go func() {
for range s {
size, err := con.Size()
if err != nil {
log.G(ctx).WithError(err).Error("get pty size")
continue
}
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
log.G(ctx).WithError(err).Error("resize pty")
}
}
}()
return nil
}
// NewTask creates a new task
func NewTask(ctx gocontext.Context, client *containerd.Client, container containerd.Container, checkpoint string, tty, nullIO bool) (containerd.Task, error) {
if checkpoint == "" {
io := containerd.Stdio
if tty {
io = containerd.StdioTerminal
}
if nullIO {
if tty {
return nil, errors.New("tty and null-io cannot be used together")
}
io = containerd.NullIO
}
return container.NewTask(ctx, io)
}
im, err := client.GetImage(ctx, checkpoint)
if err != nil {
return nil, err
}
return container.NewTask(ctx, containerd.Stdio, containerd.WithTaskCheckpoint(im))
}

View File

@@ -0,0 +1,55 @@
package tasks
import (
gocontext "context"
"time"
"github.com/containerd/console"
"github.com/containerd/containerd"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
)
// HandleConsoleResize resizes the console
func HandleConsoleResize(ctx gocontext.Context, task resizer, con console.Console) error {
// do an initial resize of the console
size, err := con.Size()
if err != nil {
return err
}
go func() {
prevSize := size
for {
time.Sleep(time.Millisecond * 250)
size, err := con.Size()
if err != nil {
log.G(ctx).WithError(err).Error("get pty size")
continue
}
if size.Width != prevSize.Width || size.Height != prevSize.Height {
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
log.G(ctx).WithError(err).Error("resize pty")
}
prevSize = size
}
}
}()
return nil
}
// NewTask creates a new task
func NewTask(ctx gocontext.Context, client *containerd.Client, container containerd.Container, _ string, tty, nullIO bool) (containerd.Task, error) {
io := containerd.Stdio
if tty {
io = containerd.StdioTerminal
}
if nullIO {
if tty {
return nil, errors.New("tty and null-io cannot be used together")
}
io = containerd.NullIO
}
return container.NewTask(ctx, io)
}