ctr: add container create, config flag for spec

Signed-off-by: Jess Valarezo <valarezo.jessica@gmail.com>
This commit is contained in:
Jess Valarezo 2018-01-25 17:16:13 -08:00
parent b268261446
commit 2c9ce2e693
4 changed files with 137 additions and 61 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/cmd/ctr/commands/run"
"github.com/containerd/containerd/log"
"github.com/urfave/cli"
)
@ -20,6 +21,7 @@ var Command = cli.Command{
Usage: "manage containers",
Aliases: []string{"c", "container"},
Subcommands: []cli.Command{
createCommand,
deleteCommand,
infoCommand,
listCommand,
@ -27,6 +29,35 @@ var Command = cli.Command{
},
}
var createCommand = cli.Command{
Name: "create",
Usage: "create container",
ArgsUsage: "[flags] Image|RootFS CONTAINER",
Flags: append(commands.SnapshotterFlags, run.ContainerFlags...),
Action: func(context *cli.Context) error {
var (
id = context.Args().Get(1)
ref = context.Args().First()
)
if ref == "" {
return errors.New("image ref must be provided")
}
if id == "" {
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
_, err = run.NewContainer(ctx, client, context)
if err != nil {
return err
}
return nil
},
}
var listCommand = cli.Command{
Name: "list",
Aliases: []string{"ls"},
@ -146,13 +177,13 @@ func deleteContainer(ctx context.Context, client *containerd.Client, id string,
var setLabelsCommand = cli.Command{
Name: "label",
Usage: "set and clear labels for a container",
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
ArgsUsage: "[flags] CONTAINER [<key>=<value>, ...]",
Description: "set and clear labels for a container",
Flags: []cli.Flag{},
Action: func(context *cli.Context) error {
containerID, labels := commands.ObjectWithLabelArgs(context)
if containerID == "" {
return errors.New("please specify a container")
return errors.New("container id must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {

View File

@ -3,7 +3,9 @@ package run
import (
gocontext "context"
"encoding/csv"
"encoding/json"
"fmt"
"io/ioutil"
"runtime"
"strings"
@ -20,6 +22,62 @@ import (
"github.com/urfave/cli"
)
// ContainerFlags are cli flags specifying container options
var ContainerFlags = []cli.Flag{
cli.StringFlag{
Name: "config,c",
Usage: "path to the runtime-specific spec config file",
},
cli.StringFlag{
Name: "checkpoint",
Usage: "provide the checkpoint digest to restore the container",
},
cli.StringFlag{
Name: "cwd",
Usage: "specify the working directory of the process",
},
cli.StringSliceFlag{
Name: "env",
Usage: "specify additional container environment variables (i.e. FOO=bar)",
},
cli.StringSliceFlag{
Name: "label",
Usage: "specify additional labels (i.e. foo=bar)",
},
cli.StringSliceFlag{
Name: "mount",
Usage: "specify additional container mount (ex: type=bind,src=/tmp,dest=/host,options=rbind:ro)",
},
cli.BoolFlag{
Name: "net-host",
Usage: "enable host networking for the container",
},
cli.BoolFlag{
Name: "read-only",
Usage: "set the containers filesystem as readonly",
},
cli.StringFlag{
Name: "runtime",
Usage: "runtime name (io.containerd.runtime.v1.linux, io.containerd.runtime.v1.windows, io.containerd.runtime.v1.com.vmware.linux)",
Value: fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS),
},
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a TTY for the container",
},
}
func loadSpec(path string, s *specs.Spec) error {
raw, err := ioutil.ReadFile(path)
if err != nil {
return errors.New("cannot load spec config file")
}
if err := json.Unmarshal(raw, s); err != nil {
return errors.New("decoding spec config file failed")
}
return nil
}
func withMounts(context *cli.Context) oci.SpecOpts {
return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *specs.Spec) error {
mounts := make([]specs.Mount, 0)
@ -75,47 +133,10 @@ var Command = cli.Command{
Usage: "run a container",
ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]",
Flags: append([]cli.Flag{
cli.BoolFlag{
Name: "tty,t",
Usage: "allocate a TTY for the container",
},
cli.StringFlag{
Name: "runtime",
Usage: "runtime name (io.containerd.runtime.v1.linux, io.containerd.runtime.v1.windows, io.containerd.runtime.v1.com.vmware.linux)",
Value: fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS),
},
cli.BoolFlag{
Name: "readonly",
Usage: "set the containers filesystem as readonly",
},
cli.BoolFlag{
Name: "net-host",
Usage: "enable host networking for the container",
},
cli.StringSliceFlag{
Name: "mount",
Usage: "specify additional container mount (ex: type=bind,src=/tmp,dst=/host,options=rbind:ro)",
},
cli.StringSliceFlag{
Name: "env",
Usage: "specify additional container environment variables (i.e. FOO=bar)",
},
cli.StringSliceFlag{
Name: "label",
Usage: "specify additional labels (foo=bar)",
},
cli.BoolFlag{
Name: "rm",
Usage: "remove the container after running",
},
cli.StringFlag{
Name: "checkpoint",
Usage: "provide the checkpoint digest to restore the container",
},
cli.StringFlag{
Name: "cwd",
Usage: "specify the working directory of the process",
},
cli.BoolFlag{
Name: "null-io",
Usage: "send all IO to /dev/null",
@ -128,18 +149,18 @@ var Command = cli.Command{
Name: "fifo-dir",
Usage: "directory used for storing IO FIFOs",
},
}, commands.SnapshotterFlags...),
}, append(commands.SnapshotterFlags, ContainerFlags...)...),
Action: func(context *cli.Context) error {
var (
err error
id = context.Args().Get(1)
imageRef = context.Args().First()
tty = context.Bool("tty")
detach = context.Bool("detach")
id = context.Args().Get(1)
ref = context.Args().First()
tty = context.Bool("tty")
detach = context.Bool("detach")
)
if imageRef == "" {
if ref == "" {
return errors.New("image ref must be provided")
}
if id == "" {
@ -150,7 +171,7 @@ var Command = cli.Command{
return err
}
defer cancel()
container, err := newContainer(ctx, client, context)
container, err := NewContainer(ctx, client, context)
if err != nil {
return err
}
@ -197,7 +218,6 @@ var Command = cli.Command{
if err != nil {
return err
}
if _, err := task.Delete(ctx); err != nil {
return err
}

View File

@ -22,7 +22,8 @@ func init() {
})
}
func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
// NewContainer creates a new container
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
var (
ref = context.Args().First()
id = context.Args().Get(1)
@ -40,8 +41,12 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
var (
opts []oci.SpecOpts
cOpts []containerd.NewContainerOpts
spec containerd.NewContainerOpts
)
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
opts = append(opts, withMounts(context))
cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
if context.Bool("rootfs") {
opts = append(opts, oci.WithRootFSPath(ref))
} else {
@ -50,19 +55,17 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
return nil, err
}
opts = append(opts, oci.WithImageConfig(image))
cOpts = append(cOpts, containerd.WithImage(image))
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
// Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
// after creating some mount points on demand.
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
cOpts = append(cOpts,
containerd.WithImage(image),
containerd.WithSnapshotter(context.String("snapshotter")),
// Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
// after creating some mount points on demand.
containerd.WithNewSnapshot(id, image))
}
if context.Bool("readonly") {
opts = append(opts, oci.WithRootFSReadonly())
}
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
opts = append(opts, withMounts(context))
if len(args) > 0 {
opts = append(opts, oci.WithProcessArgs(args...))
}
@ -75,10 +78,20 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
if context.Bool("net-host") {
opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf)
}
if context.IsSet("config") {
var s specs.Spec
if err := loadSpec(context.String("config"), &s); err != nil {
return nil, err
}
spec = containerd.WithSpec(&s, opts...)
} else {
spec = containerd.WithNewSpec(opts...)
}
cOpts = append(cOpts, spec)
// oci.WithImageConfig (WithUsername, WithUserID) depends on rootfs snapshot for resolving /etc/passwd.
// So cOpts needs to have precedence over opts.
// TODO: WithUsername, WithUserID should additionally support non-snapshot rootfs
cOpts = append(cOpts, []containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}...)
return client.NewContainer(ctx, id, cOpts...)
}

View File

@ -29,7 +29,8 @@ func withTTY(terminal bool) oci.SpecOpts {
return oci.WithTTY(int(size.Width), int(size.Height))
}
func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
// NewContainer creates a new container
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
var (
ref = context.Args().First()
id = context.Args().Get(1)
@ -44,25 +45,36 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
var (
opts []oci.SpecOpts
cOpts []containerd.NewContainerOpts
spec containerd.NewContainerOpts
)
opts = append(opts, oci.WithImageConfig(image))
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
opts = append(opts, withMounts(context))
opts = append(opts, withTTY(context.Bool("tty")))
if len(args) > 0 {
opts = append(opts, oci.WithProcessArgs(args...))
}
if cwd := context.String("cwd"); cwd != "" {
opts = append(opts, oci.WithProcessCwd(cwd))
}
opts = append(opts, withTTY(context.Bool("tty")))
if context.IsSet("config") {
var s specs.Spec
if err := loadSpec(context.String("config"), &s); err != nil {
return nil, err
}
spec = containerd.WithSpec(&s, opts...)
} else {
spec = containerd.WithNewSpec(opts...)
}
cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
cOpts = append(cOpts, containerd.WithImage(image))
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
cOpts = append(cOpts, spec)
cOpts = append([]containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}, cOpts...)
return client.NewContainer(ctx, id, cOpts...)
}