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"
|
imagesapi "github.com/containerd/containerd/api/services/images"
|
||||||
namespacesapi "github.com/containerd/containerd/api/services/namespaces"
|
namespacesapi "github.com/containerd/containerd/api/services/namespaces"
|
||||||
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
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/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
@ -28,6 +29,7 @@ import (
|
|||||||
imagesservice "github.com/containerd/containerd/services/images"
|
imagesservice "github.com/containerd/containerd/services/images"
|
||||||
snapshotservice "github.com/containerd/containerd/services/snapshot"
|
snapshotservice "github.com/containerd/containerd/services/snapshot"
|
||||||
"github.com/containerd/containerd/snapshot"
|
"github.com/containerd/containerd/snapshot"
|
||||||
|
pempty "github.com/golang/protobuf/ptypes/empty"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
ocispec "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 {
|
func (c *Client) HealthService() grpc_health_v1.HealthClient {
|
||||||
return grpc_health_v1.NewHealthClient(c.conn)
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
gocontext "context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/containerd/containerd"
|
||||||
"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/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -25,169 +12,42 @@ var checkpointCommand = cli.Command{
|
|||||||
Name: "checkpoint",
|
Name: "checkpoint",
|
||||||
Usage: "checkpoint a container",
|
Usage: "checkpoint a container",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "id of the container",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "exit",
|
Name: "exit",
|
||||||
Usage: "stop the container after the checkpoint",
|
Usage: "stop the container after the checkpoint",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "binds",
|
|
||||||
Usage: "checkpoint bind mounts with the checkpoint",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
id = context.String("id")
|
|
||||||
ctx, cancel = appContext(context)
|
ctx, cancel = appContext(context)
|
||||||
|
id = context.Args().First()
|
||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("container id must be provided")
|
return errors.New("container id must be provided")
|
||||||
}
|
}
|
||||||
|
client, err := newClient(context)
|
||||||
tasks, err := getTasksService(context)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
content, err := getContentStore(context)
|
container, err := client.LoadContainer(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
imageStore, err := getImageStore(context)
|
task, err := container.Task(ctx, nil)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed resolving image store")
|
|
||||||
}
|
|
||||||
var spec specs.Spec
|
|
||||||
info, err := tasks.Info(ctx, &execution.InfoRequest{
|
|
||||||
ContainerID: id,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(info.Task.Spec.Value, &spec); err != nil {
|
var opts []containerd.CheckpointOpts
|
||||||
return err
|
if context.Bool("exit") {
|
||||||
|
opts = append(opts, containerd.WithExit)
|
||||||
}
|
}
|
||||||
stopped := context.Bool("exit")
|
checkpoint, err := task.Checkpoint(ctx, opts...)
|
||||||
// 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"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
image, err := imageStore.Get(ctx, spec.Annotations["image"])
|
fmt.Println(checkpoint.Digest.String())
|
||||||
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())
|
|
||||||
return nil
|
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
|
package main
|
||||||
|
|
||||||
import (
|
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"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,49 +13,17 @@ var deleteCommand = cli.Command{
|
|||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
ctx, cancel := appContext(context)
|
ctx, cancel := appContext(context)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
client, err := newClient(context)
|
||||||
containers, err := getContainersService(context)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tasks, err := getTasksService(context)
|
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err := container.Task(ctx, nil); err == nil {
|
||||||
snapshotter, err := getSnapshotter(context)
|
return fmt.Errorf("cannot delete a container with a running task")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
id := context.Args().First()
|
return container.Delete(ctx)
|
||||||
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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"errors"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
"github.com/containerd/containerd"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,10 +13,6 @@ var execCommand = cli.Command{
|
|||||||
Name: "exec",
|
Name: "exec",
|
||||||
Usage: "execute additional processes in an existing container",
|
Usage: "execute additional processes in an existing container",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "id of the container",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "cwd",
|
Name: "cwd",
|
||||||
Usage: "working directory of the new process",
|
Usage: "working directory of the new process",
|
||||||
@ -29,61 +24,75 @@ var execCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
id = context.String("id")
|
|
||||||
ctx, cancel = appContext(context)
|
ctx, cancel = appContext(context)
|
||||||
|
id = context.Args().First()
|
||||||
|
args = context.Args().Tail()
|
||||||
|
tty = context.Bool("tty")
|
||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("container id must be provided")
|
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)
|
pspec := &spec.Process
|
||||||
if err != nil {
|
pspec.Terminal = tty
|
||||||
return err
|
pspec.Args = args
|
||||||
}
|
|
||||||
events, err := tasks.Events(ctx, &execution.EventsRequest{})
|
io := containerd.Stdio
|
||||||
if err != nil {
|
if tty {
|
||||||
return err
|
io = containerd.StdioTerminal
|
||||||
}
|
}
|
||||||
tmpDir, err := getTempDir(id)
|
process, err := task.Exec(ctx, pspec, io)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
request, err := newExecRequest(context, tmpDir, id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
var con console.Console
|
||||||
if request.Terminal {
|
if tty {
|
||||||
con = console.Current()
|
con = console.Current()
|
||||||
defer con.Reset()
|
defer con.Reset()
|
||||||
if err := con.SetRaw(); err != nil {
|
if err := con.SetRaw(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fwg, err := prepareStdio(request.Stdin, request.Stdout, request.Stderr, request.Terminal)
|
if err := process.Start(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
response, err := tasks.Exec(ctx, request)
|
if tty {
|
||||||
if err != nil {
|
if err := handleConsoleResize(ctx, process, con); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if request.Terminal {
|
|
||||||
if err := handleConsoleResize(ctx, tasks, id, response.Pid, con); err != nil {
|
|
||||||
logrus.WithError(err).Error("console resize")
|
logrus.WithError(err).Error("console resize")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
sigc := forwardAllSignals(ctx, process)
|
||||||
|
defer stopCatch(sigc)
|
||||||
}
|
}
|
||||||
|
status := <-statusC
|
||||||
// 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
|
|
||||||
}
|
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return cli.NewExitError("", int(status))
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
gocontext "context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
containersapi "github.com/containerd/containerd/api/services/containers"
|
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -15,55 +12,29 @@ import (
|
|||||||
var infoCommand = cli.Command{
|
var infoCommand = cli.Command{
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Usage: "get info about a container",
|
Usage: "get info about a container",
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "id of the container",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
id = context.String("id")
|
|
||||||
ctx, cancel = appContext(context)
|
ctx, cancel = appContext(context)
|
||||||
|
id = context.Args().First()
|
||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("container id must be provided")
|
return errors.New("container id must be provided")
|
||||||
}
|
}
|
||||||
|
client, err := newClient(context)
|
||||||
containers, err := getContainersService(context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tasks, err := getTasksService(context)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
containerResponse, err := containers.Get(ctx, &containersapi.GetContainerRequest{ID: id})
|
container, err := client.LoadContainer(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
cjson, err := json.MarshalIndent(container, "", " ")
|
||||||
// TODO(stevvooe): Just dumping the container and the task, for now. We
|
|
||||||
// should split this into two separate commands.
|
|
||||||
cjson, err := json.MarshalIndent(containerResponse, "", " ")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(cjson))
|
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
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -11,8 +10,9 @@ var killCommand = cli.Command{
|
|||||||
Usage: "signal a container (default: SIGTERM)",
|
Usage: "signal a container (default: SIGTERM)",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "id",
|
Name: "signal, s",
|
||||||
Usage: "id of the container",
|
Value: "SIGTERM",
|
||||||
|
Usage: "signal to send to the container",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "pid",
|
Name: "pid",
|
||||||
@ -26,53 +26,36 @@ var killCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
id = context.String("id")
|
id = context.Args().First()
|
||||||
ctx, cancel = appContext(context)
|
ctx, cancel = appContext(context)
|
||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("container id must be provided")
|
return errors.New("container id must be provided")
|
||||||
}
|
}
|
||||||
|
signal, err := parseSignal(context.String("signal"))
|
||||||
sigstr := context.Args().First()
|
|
||||||
if sigstr == "" {
|
|
||||||
sigstr = "SIGTERM"
|
|
||||||
}
|
|
||||||
|
|
||||||
signal, err := parseSignal(sigstr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
pid := context.Int("pid")
|
pid = context.Int("pid")
|
||||||
all := context.Bool("all")
|
all = context.Bool("all")
|
||||||
|
)
|
||||||
if pid > 0 && all {
|
if pid > 0 && all {
|
||||||
return errors.New("enter a pid or all; not both")
|
return errors.New("enter a pid or all; not both")
|
||||||
}
|
}
|
||||||
|
client, err := newClient(context)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = tasks.Kill(ctx, killRequest)
|
container, err := client.LoadContainer(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gocontext "context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
containersapi "github.com/containerd/containerd/api/services/containers"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
tasktypes "github.com/containerd/containerd/api/types/task"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,69 +30,59 @@ var listCommand = cli.Command{
|
|||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
tasks, err := getTasksService(context)
|
client, err := newClient(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
containers, err := client.Containers(ctx)
|
||||||
containers, err := getContainersService(context)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := containers.List(gocontext.Background(), &containersapi.ListContainersRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if quiet {
|
if quiet {
|
||||||
for _, c := range response.Containers {
|
for _, c := range containers {
|
||||||
fmt.Println(c.ID)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
// Join with tasks to get status.
|
status string
|
||||||
tasksByContainerID := map[string]*tasktypes.Task{}
|
pid uint32
|
||||||
for _, task := range tasksResponse.Tasks {
|
)
|
||||||
task.Descriptor()
|
task, err := c.Task(ctx, nil)
|
||||||
tasksByContainerID[task.ContainerID] = task
|
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
|
||||||
}
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n",
|
||||||
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
|
c.ID(),
|
||||||
fmt.Fprintln(w, "ID\tIMAGE\tPID\tSTATUS")
|
image.Name(),
|
||||||
for _, c := range response.Containers {
|
pid,
|
||||||
var (
|
status,
|
||||||
status string
|
); err != nil {
|
||||||
pid uint32
|
return err
|
||||||
)
|
}
|
||||||
task, ok := tasksByContainerID[c.ID]
|
if err := w.Flush(); err != nil {
|
||||||
if ok {
|
return err
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,7 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var extraCmds = []cli.Command{}
|
||||||
extraCmds = []cli.Command{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cli.VersionPrinter = func(c *cli.Context) {
|
cli.VersionPrinter = func(c *cli.Context) {
|
||||||
@ -30,7 +28,7 @@ func main() {
|
|||||||
/ /__/ /_/ /
|
/ /__/ /_/ /
|
||||||
\___/\__/_/
|
\___/\__/_/
|
||||||
|
|
||||||
containerd client
|
containerd CLI
|
||||||
`
|
`
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
@ -53,10 +51,9 @@ containerd client
|
|||||||
EnvVar: "CONTAINERD_NAMESPACE",
|
EnvVar: "CONTAINERD_NAMESPACE",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = append([]cli.Command{
|
||||||
checkpointCommand,
|
checkpointCommand,
|
||||||
runCommand,
|
runCommand,
|
||||||
eventsCommand,
|
|
||||||
deleteCommand,
|
deleteCommand,
|
||||||
namespacesCommand,
|
namespacesCommand,
|
||||||
listCommand,
|
listCommand,
|
||||||
@ -69,8 +66,7 @@ containerd client
|
|||||||
snapshotCommand,
|
snapshotCommand,
|
||||||
versionCommand,
|
versionCommand,
|
||||||
psCommand,
|
psCommand,
|
||||||
}
|
}, extraCmds...)
|
||||||
app.Commands = append(app.Commands, extraCmds...)
|
|
||||||
app.Before = func(context *cli.Context) error {
|
app.Before = func(context *cli.Context) error {
|
||||||
if context.GlobalBool("debug") {
|
if context.GlobalBool("debug") {
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pauseCommand = cli.Command{
|
var pauseCommand = cli.Command{
|
||||||
Name: "pause",
|
Name: "pause",
|
||||||
@ -15,17 +10,18 @@ var pauseCommand = cli.Command{
|
|||||||
ctx, cancel := appContext(context)
|
ctx, cancel := appContext(context)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
tasks, err := getTasksService(context)
|
client, err := newClient(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
id := context.Args().First()
|
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||||
if id == "" {
|
if err != nil {
|
||||||
return errors.New("container id must be provided")
|
return err
|
||||||
}
|
}
|
||||||
_, err = tasks.Pause(ctx, &execution.PauseRequest{
|
task, err := container.Task(ctx, nil)
|
||||||
ContainerID: id,
|
if err != nil {
|
||||||
})
|
return err
|
||||||
return err
|
}
|
||||||
|
return task.Pause(ctx)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -13,15 +12,9 @@ import (
|
|||||||
var psCommand = cli.Command{
|
var psCommand = cli.Command{
|
||||||
Name: "ps",
|
Name: "ps",
|
||||||
Usage: "list processes for container",
|
Usage: "list processes for container",
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "id of the container",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
id = context.String("id")
|
id = context.Args().First()
|
||||||
ctx, cancel = appContext(context)
|
ctx, cancel = appContext(context)
|
||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -29,34 +22,33 @@ var psCommand = cli.Command{
|
|||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("container id must be provided")
|
return errors.New("container id must be provided")
|
||||||
}
|
}
|
||||||
|
client, err := newClient(context)
|
||||||
pr := &execution.ProcessesRequest{
|
if err != nil {
|
||||||
ContainerID: id,
|
return err
|
||||||
}
|
}
|
||||||
|
container, err := client.LoadContainer(ctx, id)
|
||||||
tasks, err := getTasksService(context)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
|
||||||
fmt.Fprintln(w, "PID")
|
fmt.Fprintln(w, "PID")
|
||||||
for _, ps := range resp.Processes {
|
for _, ps := range processes {
|
||||||
if _, err := fmt.Fprintf(w, "%d\n",
|
if _, err := fmt.Fprintf(w, "%d\n", ps); err != nil {
|
||||||
ps.Pid,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := w.Flush(); err != nil {
|
if err := w.Flush(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var resumeCommand = cli.Command{
|
var resumeCommand = cli.Command{
|
||||||
Name: "resume",
|
Name: "resume",
|
||||||
@ -15,17 +10,18 @@ var resumeCommand = cli.Command{
|
|||||||
ctx, cancel := appContext(context)
|
ctx, cancel := appContext(context)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
tasks, err := getTasksService(context)
|
client, err := newClient(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
id := context.Args().First()
|
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||||
if id == "" {
|
if err != nil {
|
||||||
return errors.New("container id must be provided")
|
return err
|
||||||
}
|
}
|
||||||
_, err = tasks.Resume(ctx, &execution.ResumeRequest{
|
task, err := container.Task(ctx, nil)
|
||||||
ContainerID: id,
|
if err != nil {
|
||||||
})
|
return err
|
||||||
return err
|
}
|
||||||
|
return task.Resume(ctx)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
294
cmd/ctr/run.go
294
cmd/ctr/run.go
@ -1,52 +1,63 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
gocontext "context"
|
||||||
"fmt"
|
"syscall"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
containersapi "github.com/containerd/containerd/api/services/containers"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
|
||||||
"github.com/containerd/containerd/images"
|
|
||||||
"github.com/containerd/containerd/mount"
|
|
||||||
"github.com/containerd/containerd/snapshot"
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"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{
|
var runCommand = cli.Command{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "run a container",
|
Usage: "run a container",
|
||||||
ArgsUsage: "IMAGE [COMMAND] [ARG...]",
|
ArgsUsage: "IMAGE ID [COMMAND] [ARG...]",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "id of the container",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "tty,t",
|
Name: "tty,t",
|
||||||
Usage: "allocate a TTY for the container",
|
Usage: "allocate a TTY for the container",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
Name: "rootfs",
|
|
||||||
Usage: "path to rootfs",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "runtime",
|
Name: "runtime",
|
||||||
Usage: "runtime name (linux, windows, vmware-linux)",
|
Usage: "runtime name (linux, windows, vmware-linux)",
|
||||||
Value: "linux",
|
Value: "linux",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
Name: "runtime-config",
|
|
||||||
Usage: "set the OCI config file for the container",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "readonly",
|
Name: "readonly",
|
||||||
Usage: "set the containers filesystem as readonly",
|
Usage: "set the containers filesystem as readonly",
|
||||||
@ -74,237 +85,72 @@ var runCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
mounts []mount.Mount
|
checkpointIndex digest.Digest
|
||||||
imageConfig ocispec.Image
|
|
||||||
|
|
||||||
ctx, cancel = appContext(context)
|
ctx, cancel = appContext(context)
|
||||||
id = context.String("id")
|
id = context.Args().Get(1)
|
||||||
|
tty = context.Bool("tty")
|
||||||
)
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.New("container id must be provided")
|
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 raw := context.String("checkpoint"); raw != "" {
|
||||||
if checkpointIndex, err = digest.Parse(raw); err != nil {
|
if checkpointIndex, err = digest.Parse(raw); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var spec []byte
|
client, err := newClient(context)
|
||||||
if checkpointIndex != "" {
|
if err != nil {
|
||||||
var index ocispec.Index
|
return err
|
||||||
r, err := content.Reader(ctx, checkpointIndex)
|
}
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
logrus.WithError(err).Error("wait process")
|
||||||
}
|
}
|
||||||
err = json.NewDecoder(r).Decode(&index)
|
statusC <- status
|
||||||
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
|
|
||||||
}
|
|
||||||
var con console.Console
|
var con console.Console
|
||||||
if create.Terminal {
|
if tty {
|
||||||
con = console.Current()
|
con = console.Current()
|
||||||
defer con.Reset()
|
defer con.Reset()
|
||||||
if err := con.SetRaw(); err != nil {
|
if err := con.SetRaw(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fwg, err := prepareStdio(create.Stdin, create.Stdout, create.Stderr, create.Terminal)
|
if err := task.Start(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
response, err := tasks.Create(ctx, create)
|
if tty {
|
||||||
if err != nil {
|
if err := handleConsoleResize(ctx, task, con); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
pid := response.Pid
|
|
||||||
if create.Terminal {
|
|
||||||
if err := handleConsoleResize(ctx, tasks, id, pid, con); err != nil {
|
|
||||||
logrus.WithError(err).Error("console resize")
|
logrus.WithError(err).Error("console resize")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sigc := forwardAllSignals(tasks, id)
|
sigc := forwardAllSignals(ctx, task)
|
||||||
defer stopCatch(sigc)
|
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)
|
status := <-statusC
|
||||||
if err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
return err
|
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 {
|
if status != 0 {
|
||||||
return cli.NewExitError("", int(status))
|
return cli.NewExitError("", int(status))
|
||||||
}
|
}
|
||||||
|
@ -3,343 +3,29 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
gocontext "context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
containersapi "github.com/containerd/containerd/api/services/containers"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/containerd/containerd/api/types/descriptor"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"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"
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func handleConsoleResize(ctx gocontext.Context, task resizer, con console.Console) error {
|
||||||
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 {
|
|
||||||
// do an initial resize of the console
|
// do an initial resize of the console
|
||||||
size, err := con.Size()
|
size, err := con.Size()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := service.Pty(ctx, &execution.PtyRequest{
|
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
|
||||||
ContainerID: id,
|
logrus.WithError(err).Error("resize pty")
|
||||||
Pid: pid,
|
|
||||||
Width: uint32(size.Width),
|
|
||||||
Height: uint32(size.Height),
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
s := make(chan os.Signal, 16)
|
s := make(chan os.Signal, 16)
|
||||||
signal.Notify(s, unix.SIGWINCH)
|
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")
|
logrus.WithError(err).Error("get pty size")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err := service.Pty(ctx, &execution.PtyRequest{
|
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
|
||||||
ContainerID: id,
|
|
||||||
Pid: pid,
|
|
||||||
Width: uint32(size.Width),
|
|
||||||
Height: uint32(size.Height),
|
|
||||||
}); err != nil {
|
|
||||||
logrus.WithError(err).Error("resize pty")
|
logrus.WithError(err).Error("resize pty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
gocontext "context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
|
"github.com/containerd/containerd"
|
||||||
containersapi "github.com/containerd/containerd/api/services/containers"
|
containersapi "github.com/containerd/containerd/api/services/containers"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
@ -17,6 +18,7 @@ import (
|
|||||||
"github.com/containerd/containerd/windows"
|
"github.com/containerd/containerd/windows"
|
||||||
"github.com/containerd/containerd/windows/hcs"
|
"github.com/containerd/containerd/windows/hcs"
|
||||||
protobuf "github.com/gogo/protobuf/types"
|
protobuf "github.com/gogo/protobuf/types"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
@ -169,7 +171,7 @@ func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *o
|
|||||||
return create, nil
|
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
|
// do an initial resize of the console
|
||||||
size, err := con.Size()
|
size, err := con.Size()
|
||||||
if err != nil {
|
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 size.Width != prevSize.Width || size.Height != prevSize.Height {
|
||||||
if _, err := service.Pty(ctx, &execution.PtyRequest{
|
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
|
||||||
ContainerID: id,
|
logrus.WithError(err).Error("resize pty")
|
||||||
Pid: pid,
|
|
||||||
Width: uint32(size.Width),
|
|
||||||
Height: uint32(size.Height),
|
|
||||||
}); err != nil {
|
|
||||||
log.G(ctx).WithError(err).Error("resize pty")
|
|
||||||
}
|
}
|
||||||
prevSize = size
|
prevSize = size
|
||||||
}
|
}
|
||||||
@ -201,3 +198,68 @@ func handleConsoleResize(ctx context.Context, service execution.TasksClient, id
|
|||||||
}()
|
}()
|
||||||
return nil
|
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"
|
"syscall"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containerd/containerd"
|
||||||
containersapi "github.com/containerd/containerd/api/services/containers"
|
containersapi "github.com/containerd/containerd/api/services/containers"
|
||||||
contentapi "github.com/containerd/containerd/api/services/content"
|
contentapi "github.com/containerd/containerd/api/services/content"
|
||||||
diffapi "github.com/containerd/containerd/api/services/diff"
|
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
|
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) {
|
func getContainersService(context *cli.Context) (containersapi.ContainersClient, error) {
|
||||||
conn, err := getGRPCConnection(context)
|
conn, err := getGRPCConnection(context)
|
||||||
if err != nil {
|
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)
|
sigc := make(chan os.Signal, 128)
|
||||||
signal.Notify(sigc)
|
signal.Notify(sigc)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for s := range sigc {
|
for s := range sigc {
|
||||||
logrus.Debug("Forwarding signal ", s)
|
logrus.Debug("forwarding signal ", s)
|
||||||
killRequest := &execution.KillRequest{
|
if err := task.Kill(ctx, s.(syscall.Signal)); err != nil {
|
||||||
ContainerID: id,
|
logrus.WithError(err).Errorf("forward signal %s", s)
|
||||||
Signal: uint32(s.(syscall.Signal)),
|
|
||||||
PidOrAll: &execution.KillRequest_All{
|
|
||||||
All: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err := containers.Kill(gocontext.Background(), killRequest)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatalln(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containerd/containerd/version"
|
"github.com/containerd/containerd/version"
|
||||||
empty "github.com/golang/protobuf/ptypes/empty"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,14 +20,15 @@ var versionCommand = cli.Command{
|
|||||||
fmt.Printf(" Version: %s\n", version.Version)
|
fmt.Printf(" Version: %s\n", version.Version)
|
||||||
fmt.Printf(" Revision: %s\n", version.Revision)
|
fmt.Printf(" Revision: %s\n", version.Revision)
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
vs, err := getVersionService(context)
|
client, err := newClient(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v, err := vs.Version(gocontext.Background(), &empty.Empty{})
|
v, err := client.Version(gocontext.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Server:")
|
fmt.Println("Server:")
|
||||||
fmt.Printf(" Version: %s\n", v.Version)
|
fmt.Printf(" Version: %s\n", v.Version)
|
||||||
fmt.Printf(" Revision: %s\n", v.Revision)
|
fmt.Printf(" Revision: %s\n", v.Revision)
|
||||||
|
92
container.go
92
container.go
@ -3,21 +3,30 @@ package containerd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"path/filepath"
|
"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/containers"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
"github.com/containerd/containerd/api/types/mount"
|
"github.com/containerd/containerd/api/types/mount"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNoRunningTask = errors.New("no running task")
|
||||||
|
|
||||||
type Container interface {
|
type Container interface {
|
||||||
ID() string
|
ID() string
|
||||||
|
Proto() containers.Container
|
||||||
Delete(context.Context) error
|
Delete(context.Context) error
|
||||||
NewTask(context.Context, IOCreation, ...NewTaskOpts) (Task, error)
|
NewTask(context.Context, IOCreation, ...NewTaskOpts) (Task, error)
|
||||||
Spec() (*specs.Spec, error)
|
Spec() (*specs.Spec, error)
|
||||||
Task() Task
|
Task(context.Context, IOAttach) (Task, error)
|
||||||
LoadTask(context.Context, IOAttach) (Task, error)
|
Image(context.Context) (Image, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerFromProto(client *Client, c containers.Container) *container {
|
func containerFromProto(client *Client, c containers.Container) *container {
|
||||||
@ -30,10 +39,11 @@ func containerFromProto(client *Client, c containers.Container) *container {
|
|||||||
var _ = (Container)(&container{})
|
var _ = (Container)(&container{})
|
||||||
|
|
||||||
type container struct {
|
type container struct {
|
||||||
client *Client
|
mu sync.Mutex
|
||||||
|
|
||||||
c containers.Container
|
client *Client
|
||||||
task *task
|
c containers.Container
|
||||||
|
task *task
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the container's unique id
|
// ID returns the container's unique id
|
||||||
@ -41,6 +51,10 @@ func (c *container) ID() string {
|
|||||||
return c.c.ID
|
return c.c.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *container) Proto() containers.Container {
|
||||||
|
return c.c
|
||||||
|
}
|
||||||
|
|
||||||
// Spec returns the current OCI specification for the container
|
// Spec returns the current OCI specification for the container
|
||||||
func (c *container) Spec() (*specs.Spec, error) {
|
func (c *container) Spec() (*specs.Spec, error) {
|
||||||
var s specs.Spec
|
var s specs.Spec
|
||||||
@ -58,7 +72,6 @@ func (c *container) Delete(ctx context.Context) (err error) {
|
|||||||
if c.c.RootFS != "" {
|
if c.c.RootFS != "" {
|
||||||
err = c.client.SnapshotService().Remove(ctx, c.c.RootFS)
|
err = c.client.SnapshotService().Remove(ctx, c.c.RootFS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, cerr := c.client.ContainerService().Delete(ctx, &containers.DeleteContainerRequest{
|
if _, cerr := c.client.ContainerService().Delete(ctx, &containers.DeleteContainerRequest{
|
||||||
ID: c.c.ID,
|
ID: c.c.ID,
|
||||||
}); err == nil {
|
}); err == nil {
|
||||||
@ -67,13 +80,39 @@ func (c *container) Delete(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *container) Task() Task {
|
func (c *container) Task(ctx context.Context, attach IOAttach) (Task, error) {
|
||||||
return c.task
|
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
|
type NewTaskOpts func(context.Context, *Client, *execution.CreateRequest) error
|
||||||
|
|
||||||
func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...NewTaskOpts) (Task, error) {
|
func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...NewTaskOpts) (Task, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
i, err := ioCreate()
|
i, err := ioCreate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -126,28 +165,33 @@ func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...Ne
|
|||||||
return t, nil
|
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{
|
response, err := c.client.TaskService().Info(ctx, &execution.InfoRequest{
|
||||||
ContainerID: c.c.ID,
|
ContainerID: c.c.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if grpc.Code(errors.Cause(err)) == codes.NotFound {
|
||||||
|
return nil, ErrNoRunningTask
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// get the existing fifo paths from the task information stored by the daemon
|
var i *IO
|
||||||
paths := &FifoSet{
|
if ioAttach != nil {
|
||||||
Dir: getFifoDir([]string{
|
// get the existing fifo paths from the task information stored by the daemon
|
||||||
response.Task.Stdin,
|
paths := &FifoSet{
|
||||||
response.Task.Stdout,
|
Dir: getFifoDir([]string{
|
||||||
response.Task.Stderr,
|
response.Task.Stdin,
|
||||||
}),
|
response.Task.Stdout,
|
||||||
In: response.Task.Stdin,
|
response.Task.Stderr,
|
||||||
Out: response.Task.Stdout,
|
}),
|
||||||
Err: response.Task.Stderr,
|
In: response.Task.Stdin,
|
||||||
Terminal: response.Task.Terminal,
|
Out: response.Task.Stdout,
|
||||||
}
|
Err: response.Task.Stderr,
|
||||||
i, err := ioAttach(paths)
|
Terminal: response.Task.Terminal,
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
if i, err = ioAttach(paths); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t := &task{
|
t := &task{
|
||||||
client: c.client,
|
client: c.client,
|
||||||
|
@ -558,7 +558,7 @@ func TestContainerAttach(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
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)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -19,20 +21,6 @@ import (
|
|||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
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 {
|
func WithCheckpoint(desc v1.Descriptor, rootfsID string) NewContainerOpts {
|
||||||
// set image and rw, and spec
|
// set image and rw, and spec
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
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
|
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"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/containers"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
|
protobuf "github.com/gogo/protobuf/types"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
@ -255,3 +257,17 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
return nil
|
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"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/services/containers"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
|
protobuf "github.com/gogo/protobuf/types"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
@ -76,3 +78,17 @@ func WithTTY(width, height int) SpecOpts {
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
task.go
1
task.go
@ -52,6 +52,7 @@ type Task interface {
|
|||||||
type Process interface {
|
type Process interface {
|
||||||
Pid() uint32
|
Pid() uint32
|
||||||
Start(context.Context) error
|
Start(context.Context) error
|
||||||
|
Delete() error
|
||||||
Kill(context.Context, syscall.Signal) error
|
Kill(context.Context, syscall.Signal) error
|
||||||
Wait(context.Context) (uint32, error)
|
Wait(context.Context) (uint32, error)
|
||||||
CloseStdin(context.Context) error
|
CloseStdin(context.Context) error
|
||||||
|
Loading…
Reference in New Issue
Block a user