Port ctr to use client

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2017-06-05 16:17:37 -07:00
parent bdf9f5f738
commit 4c1af8fdd8
26 changed files with 526 additions and 1179 deletions

View File

@ -17,6 +17,7 @@ import (
imagesapi "github.com/containerd/containerd/api/services/images"
namespacesapi "github.com/containerd/containerd/api/services/namespaces"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
versionservice "github.com/containerd/containerd/api/services/version"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/remotes"
@ -28,6 +29,7 @@ import (
imagesservice "github.com/containerd/containerd/services/images"
snapshotservice "github.com/containerd/containerd/services/snapshot"
"github.com/containerd/containerd/snapshot"
pempty "github.com/golang/protobuf/ptypes/empty"
"github.com/opencontainers/image-spec/identity"
"github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -462,3 +464,23 @@ func (c *Client) DiffService() diff.DiffService {
func (c *Client) HealthService() grpc_health_v1.HealthClient {
return grpc_health_v1.NewHealthClient(c.conn)
}
func (c *Client) VersionService() versionservice.VersionClient {
return versionservice.NewVersionClient(c.conn)
}
type Version struct {
Version string
Revision string
}
func (c *Client) Version(ctx context.Context) (Version, error) {
response, err := c.VersionService().Version(ctx, &pempty.Empty{})
if err != nil {
return Version{}, err
}
return Version{
Version: response.Version,
Revision: response.Revision,
}, nil
}

View File

@ -1,22 +1,9 @@
package main
import (
"bytes"
gocontext "context"
"encoding/json"
"fmt"
"io"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/descriptor"
"github.com/containerd/containerd/archive"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/rootfs"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/containerd/containerd"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -25,169 +12,42 @@ var checkpointCommand = cli.Command{
Name: "checkpoint",
Usage: "checkpoint a container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Usage: "id of the container",
},
cli.BoolFlag{
Name: "exit",
Usage: "stop the container after the checkpoint",
},
cli.BoolFlag{
Name: "binds",
Usage: "checkpoint bind mounts with the checkpoint",
},
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
ctx, cancel = appContext(context)
id = context.Args().First()
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
tasks, err := getTasksService(context)
client, err := newClient(context)
if err != nil {
return err
}
content, err := getContentStore(context)
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
imageStore, err := getImageStore(context)
if err != nil {
return errors.Wrap(err, "failed resolving image store")
}
var spec specs.Spec
info, err := tasks.Info(ctx, &execution.InfoRequest{
ContainerID: id,
})
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
if err := json.Unmarshal(info.Task.Spec.Value, &spec); err != nil {
return err
var opts []containerd.CheckpointOpts
if context.Bool("exit") {
opts = append(opts, containerd.WithExit)
}
stopped := context.Bool("exit")
// if the container will still be running after the checkpoint make sure that
// we pause the container and give us time to checkpoint the filesystem before
// it resumes execution
if !stopped {
if _, err := tasks.Pause(ctx, &execution.PauseRequest{
ContainerID: id,
}); err != nil {
return err
}
defer func() {
if _, err := tasks.Resume(ctx, &execution.ResumeRequest{
ContainerID: id,
}); err != nil {
logrus.WithError(err).Error("ctr: unable to resume container")
}
}()
}
checkpoint, err := tasks.Checkpoint(ctx, &execution.CheckpointRequest{
ContainerID: id,
Exit: context.Bool("exit"),
})
checkpoint, err := task.Checkpoint(ctx, opts...)
if err != nil {
return err
}
image, err := imageStore.Get(ctx, spec.Annotations["image"])
if err != nil {
return err
}
var additionalDescriptors []*descriptor.Descriptor
if context.Bool("binds") {
if additionalDescriptors, err = checkpointBinds(ctx, &spec, content); err != nil {
return err
}
}
var index ocispec.Index
for _, d := range append(checkpoint.Descriptors, additionalDescriptors...) {
index.Manifests = append(index.Manifests, ocispec.Descriptor{
MediaType: d.MediaType,
Size: d.Size_,
Digest: d.Digest,
Platform: &ocispec.Platform{
OS: runtime.GOOS,
Architecture: runtime.GOARCH,
},
})
}
// add image to the index
index.Manifests = append(index.Manifests, image.Target)
// checkpoint rw layer
snapshotter, err := getSnapshotter(context)
if err != nil {
return err
}
differ, err := getDiffService(context)
if err != nil {
return err
}
rw, err := rootfs.Diff(ctx, id, fmt.Sprintf("checkpoint-rw-%s", id), snapshotter, differ)
if err != nil {
return err
}
rw.Platform = &ocispec.Platform{
OS: runtime.GOOS,
Architecture: runtime.GOARCH,
}
index.Manifests = append(index.Manifests, rw)
data, err := json.Marshal(index)
if err != nil {
return err
}
// write the index to the content store
buf := bytes.NewReader(data)
desc, err := writeContent(ctx, content, ocispec.MediaTypeImageIndex, id, buf)
if err != nil {
return err
}
fmt.Println(desc.Digest.String())
fmt.Println(checkpoint.Digest.String())
return nil
},
}
func checkpointBinds(ctx gocontext.Context, s *specs.Spec, store content.Store) ([]*descriptor.Descriptor, error) {
var out []*descriptor.Descriptor
for _, m := range s.Mounts {
if m.Type != "bind" {
continue
}
tar := archive.Diff(ctx, "", m.Source)
d, err := writeContent(ctx, store, images.MediaTypeContainerd1Resource, m.Source, tar)
if err := tar.Close(); err != nil {
return nil, err
}
if err != nil {
return nil, err
}
out = append(out, d)
}
return out, nil
}
func writeContent(ctx gocontext.Context, store content.Store, mediaType, ref string, r io.Reader) (*descriptor.Descriptor, error) {
writer, err := store.Writer(ctx, ref, 0, "")
if err != nil {
return nil, err
}
defer writer.Close()
size, err := io.Copy(writer, r)
if err != nil {
return nil, err
}
if err := writer.Commit(0, ""); err != nil {
return nil, err
}
return &descriptor.Descriptor{
MediaType: mediaType,
Digest: writer.Digest(),
Size_: size,
}, nil
}

View File

@ -1,14 +1,8 @@
package main
import (
"runtime"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -19,49 +13,17 @@ var deleteCommand = cli.Command{
Action: func(context *cli.Context) error {
ctx, cancel := appContext(context)
defer cancel()
containers, err := getContainersService(context)
client, err := newClient(context)
if err != nil {
return err
}
tasks, err := getTasksService(context)
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
snapshotter, err := getSnapshotter(context)
if err != nil {
return err
if _, err := container.Task(ctx, nil); err == nil {
return fmt.Errorf("cannot delete a container with a running task")
}
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
_, err = containers.Delete(ctx, &containersapi.DeleteContainerRequest{
ID: id,
})
if err != nil {
return errors.Wrap(err, "failed to delete container")
}
_, err = tasks.Delete(ctx, &execution.DeleteRequest{
ContainerID: id,
})
if err != nil {
// Ignore error if task has already been removed, task is
// removed by default after run
if grpc.Code(errors.Cause(err)) != codes.NotFound {
return errors.Wrap(err, "failed to task container")
}
}
if runtime.GOOS != "windows" {
if err := snapshotter.Remove(ctx, id); err != nil {
return errors.Wrapf(err, "failed to remove snapshot %q", id)
}
}
return nil
return container.Delete(ctx)
},
}

View File

@ -1,48 +0,0 @@
package main
import (
"fmt"
"os"
"text/tabwriter"
"github.com/containerd/containerd/api/services/execution"
"github.com/urfave/cli"
)
var eventsCommand = cli.Command{
Name: "events",
Usage: "display containerd events",
Action: func(context *cli.Context) error {
ctx, cancel := appContext(context)
defer cancel()
tasks, err := getTasksService(context)
if err != nil {
return err
}
events, err := tasks.Events(ctx, &execution.EventsRequest{})
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprintln(w, "TYPE\tID\tPID\tEXIT_STATUS")
for {
e, err := events.Recv()
if err != nil {
return err
}
if _, err := fmt.Fprintf(w,
"%s\t%s\t%d\t%d\n",
e.Type.String(),
e.ID,
e.Pid,
e.ExitStatus,
); err != nil {
return err
}
if err := w.Flush(); err != nil {
return err
}
}
},
}

View File

@ -1,12 +1,11 @@
package main
import (
"os"
"errors"
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/containerd/containerd"
"github.com/urfave/cli"
)
@ -14,10 +13,6 @@ var execCommand = cli.Command{
Name: "exec",
Usage: "execute additional processes in an existing container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Usage: "id of the container",
},
cli.StringFlag{
Name: "cwd",
Usage: "working directory of the new process",
@ -29,61 +24,75 @@ var execCommand = cli.Command{
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
ctx, cancel = appContext(context)
id = context.Args().First()
args = context.Args().Tail()
tty = context.Bool("tty")
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
client, err := newClient(context)
if err != nil {
return err
}
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
spec, err := container.Spec()
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
tasks, err := getTasksService(context)
if err != nil {
return err
}
events, err := tasks.Events(ctx, &execution.EventsRequest{})
if err != nil {
return err
}
tmpDir, err := getTempDir(id)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
request, err := newExecRequest(context, tmpDir, id)
pspec := &spec.Process
pspec.Terminal = tty
pspec.Args = args
io := containerd.Stdio
if tty {
io = containerd.StdioTerminal
}
process, err := task.Exec(ctx, pspec, io)
if err != nil {
return err
}
defer process.Delete()
statusC := make(chan uint32, 1)
go func() {
status, err := process.Wait(ctx)
if err != nil {
logrus.WithError(err).Error("wait process")
}
statusC <- status
}()
var con console.Console
if request.Terminal {
if tty {
con = console.Current()
defer con.Reset()
if err := con.SetRaw(); err != nil {
return err
}
}
fwg, err := prepareStdio(request.Stdin, request.Stdout, request.Stderr, request.Terminal)
if err != nil {
if err := process.Start(ctx); err != nil {
return err
}
response, err := tasks.Exec(ctx, request)
if err != nil {
return err
}
if request.Terminal {
if err := handleConsoleResize(ctx, tasks, id, response.Pid, con); err != nil {
if tty {
if err := handleConsoleResize(ctx, process, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := forwardAllSignals(ctx, process)
defer stopCatch(sigc)
}
// Ensure we read all io only if container started successfully.
defer fwg.Wait()
status, err := waitContainer(events, id, response.Pid)
if err != nil {
return err
}
status := <-statusC
if status != 0 {
return cli.NewExitError("", int(status))
}

View File

@ -1,69 +0,0 @@
// +build !windows
package main
import (
"encoding/json"
"path/filepath"
"github.com/containerd/containerd/api/services/execution"
protobuf "github.com/gogo/protobuf/types"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
func createProcessSpec(args []string, cwd string, tty bool) specs.Process {
env := []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
if tty {
env = append(env, "TERM=xterm")
}
if cwd == "" {
cwd = "/"
}
return specs.Process{
Args: args,
Env: env,
Terminal: tty,
Cwd: cwd,
NoNewPrivileges: true,
User: specs.User{
UID: 0,
GID: 0,
},
Capabilities: &specs.LinuxCapabilities{
Bounding: capabilities,
Permitted: capabilities,
Inheritable: capabilities,
Effective: capabilities,
Ambient: capabilities,
},
Rlimits: []specs.LinuxRlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
}
}
func newExecRequest(context *cli.Context, tmpDir, id string) (*execution.ExecRequest, error) {
process := createProcessSpec(context.Args(), context.String("cwd"), context.Bool("tty"))
data, err := json.Marshal(process)
if err != nil {
return nil, err
}
return &execution.ExecRequest{
ContainerID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: data,
},
Terminal: context.Bool("tty"),
Stdin: filepath.Join(tmpDir, "stdin"),
Stdout: filepath.Join(tmpDir, "stdout"),
Stderr: filepath.Join(tmpDir, "stderr"),
}, nil
}

View File

@ -1,40 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/containerd/containerd/api/services/execution"
protobuf "github.com/gogo/protobuf/types"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
func newExecRequest(context *cli.Context, tmpDir, id string) (*execution.ExecRequest, error) {
process := specs.Process{
Args: context.Args(),
Terminal: context.Bool("tty"),
Cwd: context.String("cwd"),
}
data, err := json.Marshal(process)
if err != nil {
return nil, err
}
now := time.Now().UnixNano()
request := &execution.ExecRequest{
ContainerID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: data,
},
Terminal: context.Bool("tty"),
Stdin: fmt.Sprintf(`%s\ctr-%s-stdin-%d`, pipeRoot, id, now),
Stdout: fmt.Sprintf(`%s\ctr-%s-stdout-%d`, pipeRoot, id, now),
}
if !request.Terminal {
request.Stderr = fmt.Sprintf(`%s\ctr-%s-stderr-%d`, pipeRoot, id, now)
}
return request, nil
}

View File

@ -3,11 +3,8 @@ package main
import (
"encoding/json"
gocontext "context"
"fmt"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -15,55 +12,29 @@ import (
var infoCommand = cli.Command{
Name: "info",
Usage: "get info about a container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Usage: "id of the container",
},
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
ctx, cancel = appContext(context)
id = context.Args().First()
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
containers, err := getContainersService(context)
if err != nil {
return err
}
tasks, err := getTasksService(context)
client, err := newClient(context)
if err != nil {
return err
}
containerResponse, err := containers.Get(ctx, &containersapi.GetContainerRequest{ID: id})
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
// TODO(stevvooe): Just dumping the container and the task, for now. We
// should split this into two separate commands.
cjson, err := json.MarshalIndent(containerResponse, "", " ")
cjson, err := json.MarshalIndent(container, "", " ")
if err != nil {
return err
}
fmt.Println(string(cjson))
response, err := tasks.Info(gocontext.Background(), &execution.InfoRequest{ContainerID: id})
if err != nil {
return err
}
json, err := json.MarshalIndent(response, "", " ")
if err != nil {
return err
}
fmt.Println(string(json))
return nil
},
}

View File

@ -1,7 +1,6 @@
package main
import (
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -11,8 +10,9 @@ var killCommand = cli.Command{
Usage: "signal a container (default: SIGTERM)",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Usage: "id of the container",
Name: "signal, s",
Value: "SIGTERM",
Usage: "signal to send to the container",
},
cli.IntFlag{
Name: "pid",
@ -26,53 +26,36 @@ var killCommand = cli.Command{
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
id = context.Args().First()
ctx, cancel = appContext(context)
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
sigstr := context.Args().First()
if sigstr == "" {
sigstr = "SIGTERM"
}
signal, err := parseSignal(sigstr)
signal, err := parseSignal(context.String("signal"))
if err != nil {
return err
}
pid := context.Int("pid")
all := context.Bool("all")
var (
pid = context.Int("pid")
all = context.Bool("all")
)
if pid > 0 && all {
return errors.New("enter a pid or all; not both")
}
killRequest := &execution.KillRequest{
ContainerID: id,
Signal: uint32(signal),
PidOrAll: &execution.KillRequest_Pid{
Pid: uint32(pid),
},
}
if all {
killRequest.PidOrAll = &execution.KillRequest_All{
All: true,
}
}
tasks, err := getTasksService(context)
client, err := newClient(context)
if err != nil {
return err
}
_, err = tasks.Kill(ctx, killRequest)
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
return nil
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
return task.Kill(ctx, signal)
},
}

View File

@ -1,14 +1,11 @@
package main
import (
gocontext "context"
"fmt"
"os"
"text/tabwriter"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
tasktypes "github.com/containerd/containerd/api/types/task"
"github.com/containerd/containerd"
"github.com/urfave/cli"
)
@ -33,69 +30,59 @@ var listCommand = cli.Command{
)
defer cancel()
tasks, err := getTasksService(context)
client, err := newClient(context)
if err != nil {
return err
}
containers, err := getContainersService(context)
containers, err := client.Containers(ctx)
if err != nil {
return err
}
response, err := containers.List(gocontext.Background(), &containersapi.ListContainersRequest{})
if err != nil {
return err
}
if quiet {
for _, c := range response.Containers {
fmt.Println(c.ID)
for _, c := range containers {
fmt.Println(c.ID())
}
} else {
return nil
}
tasksResponse, err := tasks.List(ctx, &execution.ListRequest{})
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprintln(w, "ID\tIMAGE\tPID\tSTATUS")
for _, c := range containers {
image, err := c.Image(ctx)
if err != nil {
return err
}
// Join with tasks to get status.
tasksByContainerID := map[string]*tasktypes.Task{}
for _, task := range tasksResponse.Tasks {
task.Descriptor()
tasksByContainerID[task.ContainerID] = task
var (
status string
pid uint32
)
task, err := c.Task(ctx, nil)
if err == nil {
s, err := task.Status(ctx)
if err != nil {
return err
}
status = string(s)
pid = task.Pid()
} else {
if err != containerd.ErrNoRunningTask {
return err
}
status = string(containerd.Stopped)
pid = 0
}
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprintln(w, "ID\tIMAGE\tPID\tSTATUS")
for _, c := range response.Containers {
var (
status string
pid uint32
)
task, ok := tasksByContainerID[c.ID]
if ok {
status = task.Status.String()
pid = task.Pid
} else {
status = "STOPPED" // TODO(stevvooe): Is this assumption correct?
pid = 0
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n",
c.ID,
c.Image,
pid,
status,
); err != nil {
return err
}
if err := w.Flush(); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n",
c.ID(),
image.Name(),
pid,
status,
); err != nil {
return err
}
if err := w.Flush(); err != nil {
return err
}
}
return nil
},
}

View File

@ -9,9 +9,7 @@ import (
"github.com/urfave/cli"
)
var (
extraCmds = []cli.Command{}
)
var extraCmds = []cli.Command{}
func init() {
cli.VersionPrinter = func(c *cli.Context) {
@ -30,7 +28,7 @@ func main() {
/ /__/ /_/ /
\___/\__/_/
containerd client
containerd CLI
`
app.Flags = []cli.Flag{
cli.BoolFlag{
@ -53,10 +51,9 @@ containerd client
EnvVar: "CONTAINERD_NAMESPACE",
},
}
app.Commands = []cli.Command{
app.Commands = append([]cli.Command{
checkpointCommand,
runCommand,
eventsCommand,
deleteCommand,
namespacesCommand,
listCommand,
@ -69,8 +66,7 @@ containerd client
snapshotCommand,
versionCommand,
psCommand,
}
app.Commands = append(app.Commands, extraCmds...)
}, extraCmds...)
app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)

View File

@ -1,11 +1,6 @@
package main
import (
"errors"
"github.com/containerd/containerd/api/services/execution"
"github.com/urfave/cli"
)
import "github.com/urfave/cli"
var pauseCommand = cli.Command{
Name: "pause",
@ -15,17 +10,18 @@ var pauseCommand = cli.Command{
ctx, cancel := appContext(context)
defer cancel()
tasks, err := getTasksService(context)
client, err := newClient(context)
if err != nil {
return err
}
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
_, err = tasks.Pause(ctx, &execution.PauseRequest{
ContainerID: id,
})
return err
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
return task.Pause(ctx)
},
}

View File

@ -5,7 +5,6 @@ import (
"os"
"text/tabwriter"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
@ -13,15 +12,9 @@ import (
var psCommand = cli.Command{
Name: "ps",
Usage: "list processes for container",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Usage: "id of the container",
},
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
id = context.Args().First()
ctx, cancel = appContext(context)
)
defer cancel()
@ -29,34 +22,33 @@ var psCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
pr := &execution.ProcessesRequest{
ContainerID: id,
client, err := newClient(context)
if err != nil {
return err
}
tasks, err := getTasksService(context)
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
resp, err := tasks.Processes(ctx, pr)
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
processes, err := task.Processes(ctx)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprintln(w, "PID")
for _, ps := range resp.Processes {
if _, err := fmt.Fprintf(w, "%d\n",
ps.Pid,
); err != nil {
for _, ps := range processes {
if _, err := fmt.Fprintf(w, "%d\n", ps); err != nil {
return err
}
}
if err := w.Flush(); err != nil {
return err
}
return nil
},
}

View File

@ -1,11 +1,6 @@
package main
import (
"errors"
"github.com/containerd/containerd/api/services/execution"
"github.com/urfave/cli"
)
import "github.com/urfave/cli"
var resumeCommand = cli.Command{
Name: "resume",
@ -15,17 +10,18 @@ var resumeCommand = cli.Command{
ctx, cancel := appContext(context)
defer cancel()
tasks, err := getTasksService(context)
client, err := newClient(context)
if err != nil {
return err
}
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
container, err := client.LoadContainer(ctx, context.Args().First())
if err != nil {
return err
}
_, err = tasks.Resume(ctx, &execution.ResumeRequest{
ContainerID: id,
})
return err
task, err := container.Task(ctx, nil)
if err != nil {
return err
}
return task.Resume(ctx)
},
}

View File

@ -1,52 +1,63 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"runtime"
gocontext "context"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/snapshot"
"github.com/containerd/containerd"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/identity"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
type resizer interface {
Resize(ctx gocontext.Context, w, h uint32) error
}
type killer interface {
Kill(gocontext.Context, syscall.Signal) error
}
func withEnv(context *cli.Context) containerd.SpecOpts {
return func(s *specs.Spec) error {
env := context.StringSlice("env")
if len(env) > 0 {
s.Process.Env = append(s.Process.Env, env...)
}
return nil
}
}
func withMounts(context *cli.Context) containerd.SpecOpts {
return func(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
}
}
var runCommand = cli.Command{
Name: "run",
Usage: "run a container",
ArgsUsage: "IMAGE [COMMAND] [ARG...]",
ArgsUsage: "IMAGE ID [COMMAND] [ARG...]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Usage: "id of the container",
},
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a TTY for the container",
},
cli.StringFlag{
Name: "rootfs",
Usage: "path to rootfs",
},
cli.StringFlag{
Name: "runtime",
Usage: "runtime name (linux, windows, vmware-linux)",
Value: "linux",
},
cli.StringFlag{
Name: "runtime-config",
Usage: "set the OCI config file for the container",
},
cli.BoolFlag{
Name: "readonly",
Usage: "set the containers filesystem as readonly",
@ -74,237 +85,72 @@ var runCommand = cli.Command{
},
Action: func(context *cli.Context) error {
var (
err error
mounts []mount.Mount
imageConfig ocispec.Image
err error
checkpointIndex digest.Digest
ctx, cancel = appContext(context)
id = context.String("id")
id = context.Args().Get(1)
tty = context.Bool("tty")
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
containers, err := getContainersService(context)
if err != nil {
return err
}
tasks, err := getTasksService(context)
if err != nil {
return err
}
tmpDir, err := getTempDir(id)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
events, err := tasks.Events(ctx, &execution.EventsRequest{})
if err != nil {
return err
}
content, err := getContentStore(context)
if err != nil {
return err
}
snapshotter, err := getSnapshotter(context)
if err != nil {
return err
}
imageStore, err := getImageStore(context)
if err != nil {
return errors.Wrap(err, "failed resolving image store")
}
differ, err := getDiffService(context)
if err != nil {
return err
}
var (
checkpoint *ocispec.Descriptor
checkpointIndex digest.Digest
ref = context.Args().First()
)
if raw := context.String("checkpoint"); raw != "" {
if checkpointIndex, err = digest.Parse(raw); err != nil {
return err
}
}
var spec []byte
if checkpointIndex != "" {
var index ocispec.Index
r, err := content.Reader(ctx, checkpointIndex)
client, err := newClient(context)
if err != nil {
return err
}
container, err := newContainer(ctx, client, context)
if err != nil {
return err
}
if context.Bool("rm") {
defer container.Delete(ctx)
}
task, err := newTask(ctx, container, checkpointIndex, tty)
if err != nil {
return err
}
defer task.Delete(ctx)
statusC := make(chan uint32, 1)
go func() {
status, err := task.Wait(ctx)
if err != nil {
return err
logrus.WithError(err).Error("wait process")
}
err = json.NewDecoder(r).Decode(&index)
r.Close()
if err != nil {
return err
}
var rw ocispec.Descriptor
for _, m := range index.Manifests {
switch m.MediaType {
case images.MediaTypeContainerd1Checkpoint:
fkingo := m
checkpoint = &fkingo
case images.MediaTypeContainerd1CheckpointConfig:
if r, err = content.Reader(ctx, m.Digest); err != nil {
return err
}
spec, err = ioutil.ReadAll(r)
r.Close()
if err != nil {
return err
}
case images.MediaTypeDockerSchema2Manifest:
// make sure we have the original image that was used during checkpoint
diffIDs, err := images.RootFS(ctx, content, m)
if err != nil {
return err
}
if _, err := snapshotter.Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil {
if !snapshot.IsExist(err) {
return err
}
}
case ocispec.MediaTypeImageLayer:
rw = m
}
}
if mounts, err = snapshotter.Mounts(ctx, id); err != nil {
return err
}
if _, err := differ.Apply(ctx, rw, mounts); err != nil {
return err
}
} else {
if runtime.GOOS != "windows" && context.String("rootfs") == "" {
image, err := imageStore.Get(ctx, ref)
if err != nil {
return errors.Wrapf(err, "could not resolve %q", ref)
}
// let's close out our db and tx so we don't hold the lock whilst running.
diffIDs, err := image.RootFS(ctx, content)
if err != nil {
return err
}
if context.Bool("readonly") {
mounts, err = snapshotter.View(ctx, id, identity.ChainID(diffIDs).String())
} else {
mounts, err = snapshotter.Prepare(ctx, id, identity.ChainID(diffIDs).String())
}
defer func() {
if err != nil || context.Bool("rm") {
if err := snapshotter.Remove(ctx, id); err != nil {
logrus.WithError(err).Errorf("failed to remove snapshot %q", id)
}
}
}()
if err != nil {
if !snapshot.IsExist(err) {
return err
}
mounts, err = snapshotter.Mounts(ctx, id)
if err != nil {
return err
}
}
ic, err := image.Config(ctx, content)
if err != nil {
return err
}
switch ic.MediaType {
case ocispec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
r, err := content.Reader(ctx, ic.Digest)
if err != nil {
return err
}
if err := json.NewDecoder(r).Decode(&imageConfig); err != nil {
r.Close()
return err
}
r.Close()
default:
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
}
} else {
// TODO: get the image / rootfs through the API once windows has a snapshotter
}
}
if err != nil {
return err
}
if len(spec) == 0 {
if spec, err = newContainerSpec(context, &imageConfig.Config, ref); err != nil {
return err
}
}
createContainer, err := newCreateContainerRequest(context, id, id, ref, spec)
if err != nil {
return err
}
_, err = containers.Create(ctx, createContainer)
if err != nil {
return err
}
create, err := newCreateTaskRequest(context, id, tmpDir, checkpoint, mounts)
if err != nil {
return err
}
statusC <- status
}()
var con console.Console
if create.Terminal {
if tty {
con = console.Current()
defer con.Reset()
if err := con.SetRaw(); err != nil {
return err
}
}
fwg, err := prepareStdio(create.Stdin, create.Stdout, create.Stderr, create.Terminal)
if err != nil {
if err := task.Start(ctx); err != nil {
return err
}
response, err := tasks.Create(ctx, create)
if err != nil {
return err
}
pid := response.Pid
if create.Terminal {
if err := handleConsoleResize(ctx, tasks, id, pid, con); err != nil {
if tty {
if err := handleConsoleResize(ctx, task, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := forwardAllSignals(tasks, id)
sigc := forwardAllSignals(ctx, task)
defer stopCatch(sigc)
}
if checkpoint == nil {
if _, err := tasks.Start(ctx, &execution.StartRequest{
ContainerID: id,
}); err != nil {
return err
}
}
// Ensure we read all io only if container started successfully.
defer fwg.Wait()
status, err := waitContainer(events, id, pid)
if err != nil {
status := <-statusC
if _, err := task.Delete(ctx); err != nil {
return err
}
if _, err := tasks.Delete(ctx, &execution.DeleteRequest{
ContainerID: response.ContainerID,
}); err != nil {
return err
}
if context.Bool("rm") {
if _, err := containers.Delete(ctx, &containersapi.DeleteContainerRequest{ID: id}); err != nil {
return err
}
}
if status != 0 {
return cli.NewExitError("", int(status))
}

View File

@ -3,343 +3,29 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
gocontext "context"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"golang.org/x/sys/unix"
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/descriptor"
"github.com/containerd/containerd/api/types/mount"
mountt "github.com/containerd/containerd/mount"
protobuf "github.com/gogo/protobuf/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/containerd/containerd"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
const (
rwm = "rwm"
defaultRootfsPath = "rootfs"
)
var capabilities = []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
}
func spec(id string, config *ocispec.ImageConfig, context *cli.Context, rootfs string) (*specs.Spec, error) {
defaultEnv := []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
defaultEnv = append(defaultEnv, config.Env...)
cmd := config.Cmd
if v := context.Args().Tail(); len(v) > 0 {
cmd = v
}
var (
// TODO: support overriding entrypoint
args = append(config.Entrypoint, cmd...)
tty = context.Bool("tty")
uid, gid uint32
)
if config.User != "" {
parts := strings.Split(config.User, ":")
switch len(parts) {
case 1:
v, err := strconv.ParseUint(parts[0], 0, 10)
if err != nil {
return nil, err
}
uid, gid = uint32(v), uint32(v)
case 2:
v, err := strconv.ParseUint(parts[0], 0, 10)
if err != nil {
return nil, err
}
uid = uint32(v)
if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil {
return nil, err
}
gid = uint32(v)
default:
return nil, fmt.Errorf("invalid USER value %s", config.User)
}
}
if tty {
defaultEnv = append(defaultEnv, "TERM=xterm")
}
// additional environment vars
env := replaceOrAppendEnvValues(defaultEnv, context.StringSlice("env"))
cwd := config.WorkingDir
if cwd == "" {
cwd = "/"
}
if rootfs == "" {
rootfs = defaultRootfsPath
}
s := &specs.Spec{
Version: specs.Version,
Platform: specs.Platform{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
},
Root: specs.Root{
Path: rootfs,
Readonly: context.Bool("readonly"),
},
Process: specs.Process{
Args: args,
Env: env,
Terminal: tty,
Cwd: cwd,
NoNewPrivileges: true,
User: specs.User{
UID: uid,
GID: gid,
},
Capabilities: &specs.LinuxCapabilities{
Bounding: capabilities,
Permitted: capabilities,
Inheritable: capabilities,
Effective: capabilities,
Ambient: capabilities,
},
Rlimits: []specs.LinuxRlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
},
Mounts: []specs.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev", "ro"},
},
{
Destination: "/run",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/etc/resolv.conf",
Type: "bind",
Source: "/etc/resolv.conf",
Options: []string{"rbind", "ro"},
},
{
Destination: "/etc/hosts",
Type: "bind",
Source: "/etc/hosts",
Options: []string{"rbind", "ro"},
},
{
Destination: "/etc/localtime",
Type: "bind",
Source: "/etc/localtime",
Options: []string{"rbind", "ro"},
},
},
Hostname: id,
Linux: &specs.Linux{
Resources: &specs.LinuxResources{
Devices: []specs.LinuxDeviceCgroup{
{
Allow: false,
Access: rwm,
},
},
},
Namespaces: []specs.LinuxNamespace{
{
Type: "pid",
},
{
Type: "ipc",
},
{
Type: "uts",
},
{
Type: "mount",
},
},
},
}
if !context.Bool("net-host") {
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
Type: "network",
})
}
for _, mount := range context.StringSlice("mount") {
m, err := parseMountFlag(mount)
if err != nil {
return nil, err
}
s.Mounts = append(s.Mounts, m)
}
return s, nil
}
func customSpec(configPath string, rootfs string) (*specs.Spec, error) {
b, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}
var s specs.Spec
if err := json.Unmarshal(b, &s); err != nil {
return nil, err
}
if rootfs == "" {
if s.Root.Path != defaultRootfsPath {
logrus.Warnf("ignoring Root.Path %q, setting %q forcibly", s.Root.Path, defaultRootfsPath)
s.Root.Path = defaultRootfsPath
}
} else {
s.Root.Path = rootfs
}
return &s, nil
}
func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig, rootfs string) (*specs.Spec, error) {
config := context.String("runtime-config")
if config == "" {
return spec(context.String("id"), imageConfig, context, rootfs)
}
return customSpec(config, rootfs)
}
func newContainerSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) {
s, err := getConfig(context, config, context.String("rootfs"))
if err != nil {
return nil, err
}
if s.Annotations == nil {
s.Annotations = make(map[string]string)
}
s.Annotations["image"] = imageRef
return json.Marshal(s)
}
func newCreateContainerRequest(context *cli.Context, id, snapshot, image string, spec []byte) (*containersapi.CreateContainerRequest, error) {
create := &containersapi.CreateContainerRequest{
Container: containersapi.Container{
ID: id,
Image: image,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: spec,
},
Runtime: context.String("runtime"),
RootFS: snapshot,
},
}
return create, nil
}
func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mountt.Mount) (*execution.CreateRequest, error) {
create := &execution.CreateRequest{
ContainerID: id,
Terminal: context.Bool("tty"),
Stdin: filepath.Join(tmpDir, "stdin"),
Stdout: filepath.Join(tmpDir, "stdout"),
Stderr: filepath.Join(tmpDir, "stderr"),
}
for _, m := range mounts {
create.Rootfs = append(create.Rootfs, &mount.Mount{
Type: m.Type,
Source: m.Source,
Options: m.Options,
})
}
if checkpoint != nil {
create.Checkpoint = &descriptor.Descriptor{
MediaType: checkpoint.MediaType,
Size_: checkpoint.Size,
Digest: checkpoint.Digest,
}
}
return create, nil
}
func handleConsoleResize(ctx context.Context, service execution.TasksClient, id string, pid uint32, con console.Console) error {
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 := service.Pty(ctx, &execution.PtyRequest{
ContainerID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
}); err != nil {
return err
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
logrus.WithError(err).Error("resize pty")
}
s := make(chan os.Signal, 16)
signal.Notify(s, unix.SIGWINCH)
@ -350,15 +36,86 @@ func handleConsoleResize(ctx context.Context, service execution.TasksClient, id
logrus.WithError(err).Error("get pty size")
continue
}
if _, err := service.Pty(ctx, &execution.PtyRequest{
ContainerID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
}); err != nil {
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
logrus.WithError(err).Error("resize pty")
}
}
}()
return nil
}
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 (
err error
checkpointIndex digest.Digest
ref = context.Args().First()
id = context.Args().Get(1)
args = context.Args()[2:]
tty = context.Bool("tty")
)
if raw := context.String("checkpoint"); raw != "" {
if checkpointIndex, err = digest.Parse(raw); err != nil {
return nil, err
}
}
image, err := client.GetImage(ctx, ref)
if err != nil {
return nil, err
}
if checkpointIndex == "" {
opts := []containerd.SpecOpts{
containerd.WithImageConfig(ctx, image),
withEnv(context),
withMounts(context),
}
if len(args) > 0 {
opts = append(opts, containerd.WithProcessArgs(args...))
}
if tty {
opts = append(opts, withTTY())
}
if context.Bool("net-host") {
opts = append(opts, setHostNetworking())
}
spec, err := containerd.GenerateSpec(opts...)
if err != nil {
return nil, err
}
var rootfs containerd.NewContainerOpts
if context.Bool("readonly") {
rootfs = containerd.WithNewReadonlyRootFS(id, image)
} else {
rootfs = containerd.WithNewRootFS(id, image)
}
return client.NewContainer(ctx, id,
containerd.WithSpec(spec),
containerd.WithImage(image),
rootfs,
)
}
return client.NewContainer(ctx, id, containerd.WithCheckpoint(v1.Descriptor{
Digest: checkpointIndex,
}, id))
}
func newTask(ctx gocontext.Context, container containerd.Container, checkpoint digest.Digest, tty bool) (containerd.Task, error) {
if checkpoint == "" {
io := containerd.Stdio
if tty {
io = containerd.StdioTerminal
}
return container.NewTask(ctx, io)
}
return container.NewTask(ctx, containerd.Stdio, containerd.WithTaskCheckpoint(v1.Descriptor{
Digest: checkpoint,
}))
}

View File

@ -1,7 +1,7 @@
package main
import (
"context"
gocontext "context"
"encoding/json"
"fmt"
"io/ioutil"
@ -10,6 +10,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
"github.com/containerd/containerd"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/log"
@ -17,6 +18,7 @@ import (
"github.com/containerd/containerd/windows"
"github.com/containerd/containerd/windows/hcs"
protobuf "github.com/gogo/protobuf/types"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
@ -169,7 +171,7 @@ func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *o
return create, nil
}
func handleConsoleResize(ctx context.Context, service execution.TasksClient, id string, pid uint32, con console.Console) error {
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 {
@ -187,13 +189,8 @@ func handleConsoleResize(ctx context.Context, service execution.TasksClient, id
}
if size.Width != prevSize.Width || size.Height != prevSize.Height {
if _, err := service.Pty(ctx, &execution.PtyRequest{
ContainerID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
}); err != nil {
log.G(ctx).WithError(err).Error("resize pty")
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
logrus.WithError(err).Error("resize pty")
}
prevSize = size
}
@ -201,3 +198,68 @@ func handleConsoleResize(ctx context.Context, service execution.TasksClient, id
}()
return nil
}
func withTTY() containerd.SpecOpts {
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 (
err error
ref = context.Args().First()
id = context.Args().Get(1)
args = context.Args()[2:]
tty = context.Bool("tty")
)
image, err := client.GetImage(ctx, ref)
if err != nil {
return nil, err
}
opts := []containerd.SpecOpts{
containerd.WithImageConfig(ctx, image),
withEnv(context),
withMounts(context),
}
if len(args) > 0 {
opts = append(opts, containerd.WithProcessArgs(args...))
}
if tty {
opts = append(opts, withTTY())
}
if context.Bool("net-host") {
opts = append(opts, setHostNetworking())
}
spec, err := containerd.GenerateSpec(opts...)
if err != nil {
return nil, err
}
var rootfs containerd.NewContainerOpts
if context.Bool("readonly") {
rootfs = containerd.WithNewReadonlyRootFS(id, image)
} else {
rootfs = containerd.WithNewRootFS(id, image)
}
return client.NewContainer(ctx, id,
containerd.WithSpec(spec),
containerd.WithImage(image),
rootfs,
)
}
func newTask(ctx gocontext.Context, container containerd.Container, _ digest.Digest, tty bool) (containerd.Task, error) {
io := containerd.Stdio
if tty {
io = containerd.StdioTerminal
}
return container.NewTask(ctx, io)
}

View File

@ -13,6 +13,7 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
"github.com/containerd/containerd"
containersapi "github.com/containerd/containerd/api/services/containers"
contentapi "github.com/containerd/containerd/api/services/content"
diffapi "github.com/containerd/containerd/api/services/diff"
@ -70,6 +71,10 @@ func getNamespacesService(clicontext *cli.Context) (namespaces.Store, error) {
return namespacesservice.NewStoreFromClient(namespacesapi.NewNamespacesClient(conn)), nil
}
func newClient(context *cli.Context) (*containerd.Client, error) {
return containerd.New(context.GlobalString("address"))
}
func getContainersService(context *cli.Context) (containersapi.ContainersClient, error) {
conn, err := getGRPCConnection(context)
if err != nil {
@ -153,23 +158,14 @@ func waitContainer(events execution.Tasks_EventsClient, id string, pid uint32) (
}
}
func forwardAllSignals(containers execution.TasksClient, id string) chan os.Signal {
func forwardAllSignals(ctx gocontext.Context, task killer) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.Notify(sigc)
go func() {
for s := range sigc {
logrus.Debug("Forwarding signal ", s)
killRequest := &execution.KillRequest{
ContainerID: id,
Signal: uint32(s.(syscall.Signal)),
PidOrAll: &execution.KillRequest_All{
All: false,
},
}
_, err := containers.Kill(gocontext.Background(), killRequest)
if err != nil {
logrus.Fatalln(err)
logrus.Debug("forwarding signal ", s)
if err := task.Kill(ctx, s.(syscall.Signal)); err != nil {
logrus.WithError(err).Errorf("forward signal %s", s)
}
}
}()

View File

@ -6,7 +6,6 @@ import (
"os"
"github.com/containerd/containerd/version"
empty "github.com/golang/protobuf/ptypes/empty"
"github.com/urfave/cli"
)
@ -21,14 +20,15 @@ var versionCommand = cli.Command{
fmt.Printf(" Version: %s\n", version.Version)
fmt.Printf(" Revision: %s\n", version.Revision)
fmt.Println("")
vs, err := getVersionService(context)
client, err := newClient(context)
if err != nil {
return err
}
v, err := vs.Version(gocontext.Background(), &empty.Empty{})
v, err := client.Version(gocontext.Background())
if err != nil {
return err
}
fmt.Println("Server:")
fmt.Printf(" Version: %s\n", v.Version)
fmt.Printf(" Revision: %s\n", v.Revision)

View File

@ -3,21 +3,30 @@ package containerd
import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"sync"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/mount"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
var ErrNoRunningTask = errors.New("no running task")
type Container interface {
ID() string
Proto() containers.Container
Delete(context.Context) error
NewTask(context.Context, IOCreation, ...NewTaskOpts) (Task, error)
Spec() (*specs.Spec, error)
Task() Task
LoadTask(context.Context, IOAttach) (Task, error)
Task(context.Context, IOAttach) (Task, error)
Image(context.Context) (Image, error)
}
func containerFromProto(client *Client, c containers.Container) *container {
@ -30,10 +39,11 @@ func containerFromProto(client *Client, c containers.Container) *container {
var _ = (Container)(&container{})
type container struct {
client *Client
mu sync.Mutex
c containers.Container
task *task
client *Client
c containers.Container
task *task
}
// ID returns the container's unique id
@ -41,6 +51,10 @@ func (c *container) ID() string {
return c.c.ID
}
func (c *container) Proto() containers.Container {
return c.c
}
// Spec returns the current OCI specification for the container
func (c *container) Spec() (*specs.Spec, error) {
var s specs.Spec
@ -58,7 +72,6 @@ func (c *container) Delete(ctx context.Context) (err error) {
if c.c.RootFS != "" {
err = c.client.SnapshotService().Remove(ctx, c.c.RootFS)
}
if _, cerr := c.client.ContainerService().Delete(ctx, &containers.DeleteContainerRequest{
ID: c.c.ID,
}); err == nil {
@ -67,13 +80,39 @@ func (c *container) Delete(ctx context.Context) (err error) {
return err
}
func (c *container) Task() Task {
return c.task
func (c *container) Task(ctx context.Context, attach IOAttach) (Task, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.task == nil {
t, err := c.loadTask(ctx, attach)
if err != nil {
return nil, err
}
c.task = t.(*task)
}
return c.task, nil
}
// Image returns the image that the container is based on
func (c *container) Image(ctx context.Context) (Image, error) {
if c.c.Image == "" {
return nil, fmt.Errorf("container is not based on an image")
}
i, err := c.client.ImageService().Get(ctx, c.c.Image)
if err != nil {
return nil, err
}
return &image{
client: c.client,
i: i,
}, nil
}
type NewTaskOpts func(context.Context, *Client, *execution.CreateRequest) error
func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...NewTaskOpts) (Task, error) {
c.mu.Lock()
defer c.mu.Unlock()
i, err := ioCreate()
if err != nil {
return nil, err
@ -126,28 +165,33 @@ func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...Ne
return t, nil
}
func (c *container) LoadTask(ctx context.Context, ioAttach IOAttach) (Task, error) {
func (c *container) loadTask(ctx context.Context, ioAttach IOAttach) (Task, error) {
response, err := c.client.TaskService().Info(ctx, &execution.InfoRequest{
ContainerID: c.c.ID,
})
if err != nil {
if grpc.Code(errors.Cause(err)) == codes.NotFound {
return nil, ErrNoRunningTask
}
return nil, err
}
// get the existing fifo paths from the task information stored by the daemon
paths := &FifoSet{
Dir: getFifoDir([]string{
response.Task.Stdin,
response.Task.Stdout,
response.Task.Stderr,
}),
In: response.Task.Stdin,
Out: response.Task.Stdout,
Err: response.Task.Stderr,
Terminal: response.Task.Terminal,
}
i, err := ioAttach(paths)
if err != nil {
return nil, err
var i *IO
if ioAttach != nil {
// get the existing fifo paths from the task information stored by the daemon
paths := &FifoSet{
Dir: getFifoDir([]string{
response.Task.Stdin,
response.Task.Stdout,
response.Task.Stderr,
}),
In: response.Task.Stdin,
Out: response.Task.Stdout,
Err: response.Task.Stderr,
Terminal: response.Task.Terminal,
}
if i, err = ioAttach(paths); err != nil {
return nil, err
}
}
t := &task{
client: c.client,

View File

@ -558,7 +558,7 @@ func TestContainerAttach(t *testing.T) {
t.Error(err)
return
}
if task, err = container.LoadTask(ctx, WithAttach(r, ow, ioutil.Discard)); err != nil {
if task, err = container.Task(ctx, WithAttach(r, ow, ioutil.Discard)); err != nil {
t.Error(err)
return
}

View File

@ -1,3 +1,5 @@
// +build !windows
package containerd
import (
@ -19,20 +21,6 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func WithSpec(spec *specs.Spec) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
data, err := json.Marshal(spec)
if err != nil {
return err
}
c.Spec = &protobuf.Any{
TypeUrl: spec.Version,
Value: data,
}
return nil
}
}
func WithCheckpoint(desc v1.Descriptor, rootfsID string) NewContainerOpts {
// set image and rw, and spec
return func(ctx context.Context, client *Client, c *containers.Container) error {

View File

@ -110,3 +110,7 @@ func (p *process) Resize(ctx context.Context, w, h uint32) error {
})
return err
}
func (p *process) Delete() error {
return p.io.Close()
}

View File

@ -10,7 +10,9 @@ import (
"strconv"
"strings"
"github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/images"
protobuf "github.com/gogo/protobuf/types"
"github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -255,3 +257,17 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
return nil
}
}
func WithSpec(spec *specs.Spec) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
data, err := json.Marshal(spec)
if err != nil {
return err
}
c.Spec = &protobuf.Any{
TypeUrl: spec.Version,
Value: data,
}
return nil
}
}

View File

@ -6,7 +6,9 @@ import (
"fmt"
"runtime"
"github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/images"
protobuf "github.com/gogo/protobuf/types"
"github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@ -76,3 +78,17 @@ func WithTTY(width, height int) SpecOpts {
return nil
}
}
func WithSpec(spec *specs.Spec) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
data, err := json.Marshal(spec)
if err != nil {
return err
}
c.Spec = &protobuf.Any{
TypeUrl: spec.Version,
Value: data,
}
return nil
}
}

View File

@ -52,6 +52,7 @@ type Task interface {
type Process interface {
Pid() uint32
Start(context.Context) error
Delete() error
Kill(context.Context, syscall.Signal) error
Wait(context.Context) (uint32, error)
CloseStdin(context.Context) error