ctr: move tasks, run to commands package
Signed-off-by: Jess Valarezo <valarezo.jessica@gmail.com>
This commit is contained in:
248
cmd/ctr/commands/run/run.go
Normal file
248
cmd/ctr/commands/run/run.go
Normal 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
|
||||
},
|
||||
}
|
||||
84
cmd/ctr/commands/run/run_unix.go
Normal file
84
cmd/ctr/commands/run/run_unix.go
Normal 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...)
|
||||
}
|
||||
89
cmd/ctr/commands/run/run_windows.go
Normal file
89
cmd/ctr/commands/run/run_windows.go
Normal 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),
|
||||
)
|
||||
}
|
||||
72
cmd/ctr/commands/tasks/attach.go
Normal file
72
cmd/ctr/commands/tasks/attach.go
Normal 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
|
||||
},
|
||||
}
|
||||
51
cmd/ctr/commands/tasks/checkpoint.go
Normal file
51
cmd/ctr/commands/tasks/checkpoint.go
Normal 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
|
||||
},
|
||||
}
|
||||
35
cmd/ctr/commands/tasks/delete.go
Normal file
35
cmd/ctr/commands/tasks/delete.go
Normal 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
|
||||
},
|
||||
}
|
||||
108
cmd/ctr/commands/tasks/exec.go
Normal file
108
cmd/ctr/commands/tasks/exec.go
Normal 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
|
||||
},
|
||||
}
|
||||
62
cmd/ctr/commands/tasks/kill.go
Normal file
62
cmd/ctr/commands/tasks/kill.go
Normal 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)
|
||||
},
|
||||
}
|
||||
55
cmd/ctr/commands/tasks/list.go
Normal file
55
cmd/ctr/commands/tasks/list.go
Normal 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()
|
||||
},
|
||||
}
|
||||
28
cmd/ctr/commands/tasks/pause.go
Normal file
28
cmd/ctr/commands/tasks/pause.go
Normal 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)
|
||||
},
|
||||
}
|
||||
59
cmd/ctr/commands/tasks/ps.go
Normal file
59
cmd/ctr/commands/tasks/ps.go
Normal 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()
|
||||
},
|
||||
}
|
||||
28
cmd/ctr/commands/tasks/resume.go
Normal file
28
cmd/ctr/commands/tasks/resume.go
Normal 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)
|
||||
},
|
||||
}
|
||||
90
cmd/ctr/commands/tasks/start.go
Normal file
90
cmd/ctr/commands/tasks/start.go
Normal 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
|
||||
},
|
||||
}
|
||||
30
cmd/ctr/commands/tasks/tasks.go
Normal file
30
cmd/ctr/commands/tasks/tasks.go
Normal 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,
|
||||
},
|
||||
}
|
||||
64
cmd/ctr/commands/tasks/tasks_unix.go
Normal file
64
cmd/ctr/commands/tasks/tasks_unix.go
Normal 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))
|
||||
}
|
||||
55
cmd/ctr/commands/tasks/tasks_windows.go
Normal file
55
cmd/ctr/commands/tasks/tasks_windows.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user