From 2c9ce2e693035b921755d8189da6c7c02a886c57 Mon Sep 17 00:00:00 2001 From: Jess Valarezo Date: Thu, 25 Jan 2018 17:16:13 -0800 Subject: [PATCH] ctr: add container create, config flag for spec Signed-off-by: Jess Valarezo --- cmd/ctr/commands/containers/containers.go | 35 ++++++- cmd/ctr/commands/run/run.go | 110 +++++++++++++--------- cmd/ctr/commands/run/run_unix.go | 35 ++++--- cmd/ctr/commands/run/run_windows.go | 18 +++- 4 files changed, 137 insertions(+), 61 deletions(-) diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go index d0195d4d3..a92d5afeb 100644 --- a/cmd/ctr/commands/containers/containers.go +++ b/cmd/ctr/commands/containers/containers.go @@ -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] [=, ...]", + ArgsUsage: "[flags] CONTAINER [=, ...]", 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 { diff --git a/cmd/ctr/commands/run/run.go b/cmd/ctr/commands/run/run.go index c71d0c58f..425845594 100644 --- a/cmd/ctr/commands/run/run.go +++ b/cmd/ctr/commands/run/run.go @@ -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 } diff --git a/cmd/ctr/commands/run/run_unix.go b/cmd/ctr/commands/run/run_unix.go index eb8b4a05b..ef255ea1d 100644 --- a/cmd/ctr/commands/run/run_unix.go +++ b/cmd/ctr/commands/run/run_unix.go @@ -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...) } diff --git a/cmd/ctr/commands/run/run_windows.go b/cmd/ctr/commands/run/run_windows.go index 82fca433a..648a29819 100644 --- a/cmd/ctr/commands/run/run_windows.go +++ b/cmd/ctr/commands/run/run_windows.go @@ -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...) }