Merge pull request #1790 from jessvalarezo/ctr-c-create
ctr: add container create cmd and config flag
This commit is contained in:
commit
5f89502a24
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands/run"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -20,6 +21,7 @@ var Command = cli.Command{
|
|||||||
Usage: "manage containers",
|
Usage: "manage containers",
|
||||||
Aliases: []string{"c", "container"},
|
Aliases: []string{"c", "container"},
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
|
createCommand,
|
||||||
deleteCommand,
|
deleteCommand,
|
||||||
infoCommand,
|
infoCommand,
|
||||||
listCommand,
|
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{
|
var listCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
@ -146,13 +177,13 @@ func deleteContainer(ctx context.Context, client *containerd.Client, id string,
|
|||||||
var setLabelsCommand = cli.Command{
|
var setLabelsCommand = cli.Command{
|
||||||
Name: "label",
|
Name: "label",
|
||||||
Usage: "set and clear labels for a container",
|
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",
|
Description: "set and clear labels for a container",
|
||||||
Flags: []cli.Flag{},
|
Flags: []cli.Flag{},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
containerID, labels := commands.ObjectWithLabelArgs(context)
|
containerID, labels := commands.ObjectWithLabelArgs(context)
|
||||||
if containerID == "" {
|
if containerID == "" {
|
||||||
return errors.New("please specify a container")
|
return errors.New("container id must be provided")
|
||||||
}
|
}
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,7 +3,9 @@ package run
|
|||||||
import (
|
import (
|
||||||
gocontext "context"
|
gocontext "context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -20,6 +22,62 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"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 {
|
func withMounts(context *cli.Context) oci.SpecOpts {
|
||||||
return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *specs.Spec) error {
|
return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *specs.Spec) error {
|
||||||
mounts := make([]specs.Mount, 0)
|
mounts := make([]specs.Mount, 0)
|
||||||
@ -75,47 +133,10 @@ var Command = cli.Command{
|
|||||||
Usage: "run a container",
|
Usage: "run a container",
|
||||||
ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]",
|
ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]",
|
||||||
Flags: append([]cli.Flag{
|
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{
|
cli.BoolFlag{
|
||||||
Name: "rm",
|
Name: "rm",
|
||||||
Usage: "remove the container after running",
|
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{
|
cli.BoolFlag{
|
||||||
Name: "null-io",
|
Name: "null-io",
|
||||||
Usage: "send all IO to /dev/null",
|
Usage: "send all IO to /dev/null",
|
||||||
@ -128,18 +149,18 @@ var Command = cli.Command{
|
|||||||
Name: "fifo-dir",
|
Name: "fifo-dir",
|
||||||
Usage: "directory used for storing IO FIFOs",
|
Usage: "directory used for storing IO FIFOs",
|
||||||
},
|
},
|
||||||
}, commands.SnapshotterFlags...),
|
}, append(commands.SnapshotterFlags, ContainerFlags...)...),
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
|
|
||||||
id = context.Args().Get(1)
|
id = context.Args().Get(1)
|
||||||
imageRef = context.Args().First()
|
ref = context.Args().First()
|
||||||
tty = context.Bool("tty")
|
tty = context.Bool("tty")
|
||||||
detach = context.Bool("detach")
|
detach = context.Bool("detach")
|
||||||
)
|
)
|
||||||
|
|
||||||
if imageRef == "" {
|
if ref == "" {
|
||||||
return errors.New("image ref must be provided")
|
return errors.New("image ref must be provided")
|
||||||
}
|
}
|
||||||
if id == "" {
|
if id == "" {
|
||||||
@ -150,7 +171,7 @@ var Command = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
container, err := newContainer(ctx, client, context)
|
container, err := NewContainer(ctx, client, context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -197,7 +218,6 @@ var Command = cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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 (
|
var (
|
||||||
ref = context.Args().First()
|
ref = context.Args().First()
|
||||||
id = context.Args().Get(1)
|
id = context.Args().Get(1)
|
||||||
@ -40,8 +41,12 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
var (
|
var (
|
||||||
opts []oci.SpecOpts
|
opts []oci.SpecOpts
|
||||||
cOpts []containerd.NewContainerOpts
|
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.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
|
||||||
|
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
|
||||||
if context.Bool("rootfs") {
|
if context.Bool("rootfs") {
|
||||||
opts = append(opts, oci.WithRootFSPath(ref))
|
opts = append(opts, oci.WithRootFSPath(ref))
|
||||||
} else {
|
} else {
|
||||||
@ -50,19 +55,17 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts = append(opts, oci.WithImageConfig(image))
|
opts = append(opts, oci.WithImageConfig(image))
|
||||||
cOpts = append(cOpts, containerd.WithImage(image))
|
cOpts = append(cOpts,
|
||||||
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
|
containerd.WithImage(image),
|
||||||
|
containerd.WithSnapshotter(context.String("snapshotter")),
|
||||||
// Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
|
// 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,
|
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
|
||||||
// after creating some mount points on demand.
|
// after creating some mount points on demand.
|
||||||
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
|
containerd.WithNewSnapshot(id, image))
|
||||||
}
|
}
|
||||||
if context.Bool("readonly") {
|
if context.Bool("readonly") {
|
||||||
opts = append(opts, oci.WithRootFSReadonly())
|
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 {
|
if len(args) > 0 {
|
||||||
opts = append(opts, oci.WithProcessArgs(args...))
|
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") {
|
if context.Bool("net-host") {
|
||||||
opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf)
|
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.
|
// oci.WithImageConfig (WithUsername, WithUserID) depends on rootfs snapshot for resolving /etc/passwd.
|
||||||
// So cOpts needs to have precedence over opts.
|
// So cOpts needs to have precedence over opts.
|
||||||
// TODO: WithUsername, WithUserID should additionally support non-snapshot rootfs
|
// TODO: WithUsername, WithUserID should additionally support non-snapshot rootfs
|
||||||
cOpts = append(cOpts, []containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}...)
|
|
||||||
return client.NewContainer(ctx, id, cOpts...)
|
return client.NewContainer(ctx, id, cOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ func withTTY(terminal bool) oci.SpecOpts {
|
|||||||
return oci.WithTTY(int(size.Width), int(size.Height))
|
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 (
|
var (
|
||||||
ref = context.Args().First()
|
ref = context.Args().First()
|
||||||
id = context.Args().Get(1)
|
id = context.Args().Get(1)
|
||||||
@ -44,25 +45,36 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
var (
|
var (
|
||||||
opts []oci.SpecOpts
|
opts []oci.SpecOpts
|
||||||
cOpts []containerd.NewContainerOpts
|
cOpts []containerd.NewContainerOpts
|
||||||
|
spec containerd.NewContainerOpts
|
||||||
)
|
)
|
||||||
opts = append(opts, oci.WithImageConfig(image))
|
opts = append(opts, oci.WithImageConfig(image))
|
||||||
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
|
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
|
||||||
opts = append(opts, withMounts(context))
|
opts = append(opts, withMounts(context))
|
||||||
|
opts = append(opts, withTTY(context.Bool("tty")))
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
opts = append(opts, oci.WithProcessArgs(args...))
|
opts = append(opts, oci.WithProcessArgs(args...))
|
||||||
}
|
}
|
||||||
if cwd := context.String("cwd"); cwd != "" {
|
if cwd := context.String("cwd"); cwd != "" {
|
||||||
opts = append(opts, oci.WithProcessCwd(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.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
|
||||||
cOpts = append(cOpts, containerd.WithImage(image))
|
cOpts = append(cOpts, containerd.WithImage(image))
|
||||||
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
|
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
|
||||||
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
|
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
|
||||||
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
|
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...)
|
return client.NewContainer(ctx, id, cOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user