Port ctr to use client
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
bdf9f5f738
commit
4c1af8fdd8
22
client.go
22
client.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
294
cmd/ctr/run.go
294
cmd/ctr/run.go
@ -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))
|
||||
}
|
||||
|
@ -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,
|
||||
}))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -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)
|
||||
|
92
container.go
92
container.go
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
@ -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()
|
||||
}
|
||||
|
16
spec_unix.go
16
spec_unix.go
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user