Merge pull request #1790 from jessvalarezo/ctr-c-create

ctr: add container create cmd and config flag
This commit is contained in:
Michael Crosby 2018-01-29 17:19:14 -05:00 committed by GitHub
commit 5f89502a24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 137 additions and 61 deletions

View File

@ -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 {

View File

@ -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
} }

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 ( 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),
// Even when "readonly" is set, we don't use KindView snapshot here. (#1495) containerd.WithSnapshotter(context.String("snapshotter")),
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only, // Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
// after creating some mount points on demand. // We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image)) // after creating some mount points on demand.
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...)
} }

View File

@ -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...)
} }