diff options
author | Matthew Heon <matthew.heon@pm.me> | 2019-04-29 10:37:50 -0400 |
---|---|---|
committer | Matthew Heon <matthew.heon@pm.me> | 2019-05-01 11:12:24 -0400 |
commit | 0b2c9c2acc38f51f871fd5a06aca205127a06d1d (patch) | |
tree | 62a3f1a5e8ad83d9ec7bfe1a066b3d898f69c8c9 | |
parent | ad68036a88e35dc3c7a19962b8e21867b459f8f1 (diff) | |
download | podman-0b2c9c2acc38f51f871fd5a06aca205127a06d1d.tar.gz podman-0b2c9c2acc38f51f871fd5a06aca205127a06d1d.tar.bz2 podman-0b2c9c2acc38f51f871fd5a06aca205127a06d1d.zip |
Add basic structure of podman init command
As part of this, rework the number of workers used by various
Podman tasks to match original behavior - need an explicit
fallthrough in the switch statement for that block to work as
expected.
Also, trivial change to Podman cleanup to work on initialized
containers - we need to reset to a different state after cleaning
up the OCI runtime.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
-rw-r--r-- | cmd/podman/cliconfig/config.go | 6 | ||||
-rw-r--r-- | cmd/podman/container.go | 1 | ||||
-rw-r--r-- | cmd/podman/init.go | 64 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | cmd/podman/shared/workers.go | 5 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 8 | ||||
-rw-r--r-- | commands.md | 1 | ||||
-rw-r--r-- | completions/bash/podman | 25 | ||||
-rw-r--r-- | docs/podman-container.1.md | 1 | ||||
-rw-r--r-- | docs/podman-init.1.md | 41 | ||||
-rw-r--r-- | docs/podman.1.md | 1 | ||||
-rw-r--r-- | libpod/container_api.go | 2 | ||||
-rw-r--r-- | libpod/container_internal.go | 16 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 37 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 25 | ||||
-rw-r--r-- | pkg/varlinkapi/containers.go | 12 | ||||
-rw-r--r-- | test/e2e/init_test.go | 129 |
17 files changed, 369 insertions, 6 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 77156f47a..43ba7ddc9 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -177,6 +177,12 @@ type InfoValues struct { Format string } +type InitValues struct { + PodmanCommand + All bool + Latest bool +} + type InspectValues struct { PodmanCommand TypeObject string diff --git a/cmd/podman/container.go b/cmd/podman/container.go index b3058bf12..4dd9cbed6 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -56,6 +56,7 @@ var ( _diffCommand, _exportCommand, _createCommand, + _initCommand, _killCommand, _listSubCommand, _logsCommand, diff --git a/cmd/podman/init.go b/cmd/podman/init.go new file mode 100644 index 000000000..68c80631d --- /dev/null +++ b/cmd/podman/init.go @@ -0,0 +1,64 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + initCommand cliconfig.InitValues + initDescription = `Initialize one or more containers, creating the OCI spec and mounts for inspection. Container names or IDs can be used.` + + _initCommand = &cobra.Command{ + Use: "init [flags] CONTAINER [CONTAINER...]", + Short: "Initialize one or more containers", + Long: initDescription, + RunE: func(cmd *cobra.Command, args []string) error { + initCommand.InputArgs = args + initCommand.GlobalFlags = MainGlobalOpts + initCommand.Remote = remoteclient + return initCmd(&initCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + return checkAllAndLatest(cmd, args, false) + }, + Example: `podman init --latest + podman init 3c45ef19d893 + podman init test1`, + } +) + +func init() { + initCommand.Command = _initCommand + initCommand.SetHelpTemplate(HelpTemplate()) + initCommand.SetUsageTemplate(UsageTemplate()) + flags := initCommand.Flags() + flags.BoolVarP(&initCommand.All, "all", "a", false, "Initialize all containers") + flags.BoolVarP(&initCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + markFlagHiddenForRemoteClient("latest", flags) +} + +// initCmd initializes a container +func initCmd(c *cliconfig.InitValues) error { + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "initCmd") + defer span.Finish() + } + + ctx := getContext() + + runtime, err := adapter.GetRuntime(ctx, &c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ok, failures, err := runtime.InitContainers(ctx, c) + if err != nil { + return err + } + return printCmdResults(ok, failures) +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index f501ee674..7d2138ba7 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -39,6 +39,7 @@ var mainCommands = []*cobra.Command{ &_imagesCommand, _importCommand, _infoCommand, + _initCommand, &_inspectCommand, _killCommand, _loadCommand, diff --git a/cmd/podman/shared/workers.go b/cmd/podman/shared/workers.go index 112af89cc..b6e3f10e7 100644 --- a/cmd/podman/shared/workers.go +++ b/cmd/podman/shared/workers.go @@ -110,9 +110,14 @@ func (p *Pool) newWorker(slot int) { func DefaultPoolSize(name string) int { numCpus := runtime.NumCPU() switch name { + case "init": + fallthrough case "kill": + fallthrough case "pause": + fallthrough case "rm": + fallthrough case "unpause": if numCpus <= 3 { return numCpus * 3 diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 309f9765a..1b6113b7c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -641,6 +641,14 @@ method StartContainer(name: string) -> (container: string) # ~~~ method StopContainer(name: string, timeout: int) -> (container: string) +# InitContainer initializes the given container. It accepts a container name or +# ID, and will initialize the container matching that ID if possible, and error +# if not. Containers can only be initialized when they are in the Created or +# Exited states. Initialization prepares a container to be started, but does not +# start the container. It is intended to be used to debug a container's state +# prior to starting it. +method InitContainer(name: string) -> (container: string) + # RestartContainer will restart a running container given a container name or ID and timeout value. The timeout # value is the time before a forcible stop is used to stop the container. If the container cannot be found by # name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise, the ID of the diff --git a/commands.md b/commands.md index 1c05640f2..88290dc1d 100644 --- a/commands.md +++ b/commands.md @@ -34,6 +34,7 @@ Command | Descr [podman-images(1)](/docs/podman-images.1.md) | List images in local storage | [![...](/docs/play.png)](https://podman.io/asciinema/podman/images/) | [Here](https://github.com/containers/Demos/blob/master/podman_cli/podman_images.sh) [podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image | [podman-info(1)](/docs/podman-info.1.md) | Display system information | +[podman-init(1)](/docs/podman-init.1.md) | Initialize a container | [podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image | [![...](/docs/play.png)](https://asciinema.org/a/133418) [podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers | [podman-load(1)](/docs/podman-load.1.md) | Load an image from a container image archive | diff --git a/completions/bash/podman b/completions/bash/podman index b5963f8b9..a02a47190 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -780,6 +780,10 @@ _podman_container_export() { _podman_export } +_podman_container_init() { + _podman_init +} + _podman_container_inspect() { _podman_inspect } @@ -2223,6 +2227,27 @@ _podman_ps() { _complete_ "$options_with_args" "$boolean_options" } +_podman_init() { + local boolean_options=" + --all + -a + --help + -h + --latest + -l + " + local options_with_args=" + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_containers_unpauseable + ;; + esac +} + _podman_start() { local options_with_args=" --detach-keys diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md index 1ba957480..564d791fa 100644 --- a/docs/podman-container.1.md +++ b/docs/podman-container.1.md @@ -22,6 +22,7 @@ The container command allows you to manage containers | exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | | exists | [podman-container-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage | | export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | +| init | [podman-init(1)](podman-init.1.md) | Initialize a container | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | | list | [podman-ps(1)](podman-ps.1.md) | List the containers on the system.(alias ls) | diff --git a/docs/podman-init.1.md b/docs/podman-init.1.md new file mode 100644 index 000000000..aff7a833d --- /dev/null +++ b/docs/podman-init.1.md @@ -0,0 +1,41 @@ +% podman-init(1) + +## NAME +podman\-init - Initialize one or more containers + +## SYNOPSIS +**podman init** [*options*] *container* ... + +## DESCRIPTION +Initialize one or more containers. +You may use container IDs or names as input. +Initializing a container performs all tasks necessary for starting the container (mounting filesystems, creating an OCI spec, initializing the container network) but does not start the container. +If a container is not initialized, the `podman start` and `podman run` commands will do so automatically prior to starting it. +This command is intended to be used for inspecting or modifying the container's filesystem or OCI spec prior to starting it. +This can be used to inspect the container before it runs, or debug why a container is failing to run. + +## OPTIONS + +**--all, a** + +Initialize all containers. Containers that have already initialized (including containers that have been started and are running) are ignored. + +**--latest, -l** +Instead of providing the container name or ID, use the last created container. If you use methods other than Podman +to run containers such as CRI-O, the last started container could be from either of those methods. + +The latest option is not supported on the remote client. + +## EXAMPLE + +podman init 35480fc9d568 + +podman init test1 + +podman init --latest + +## SEE ALSO +podman(1), podman-start(1) + +## HISTORY +April 2019, Originally compiled by Matthew Heon <mheon@redhat.com> diff --git a/docs/podman.1.md b/docs/podman.1.md index 9c0ca8a7a..ef12cf1cc 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -147,6 +147,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-images(1)](podman-images.1.md) | List images in local storage. | | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | [podman-info(1)](podman-info.1.md) | Displays Podman related system information. | +| [podman-init(1)](podman-init.1.md) | Initialize a container | | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | | [podman-load(1)](podman-load.1.md) | Load an image from a container image archive into container storage. | diff --git a/libpod/container_api.go b/libpod/container_api.go index 465b23831..5bfd869b3 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -40,7 +40,7 @@ func (c *Container) Init(ctx context.Context) (err error) { if !(c.state.State == ContainerStateConfigured || c.state.State == ContainerStateStopped || c.state.State == ContainerStateExited) { - return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID()) + return errors.Wrapf(ErrCtrStateInvalid, "container %s has already been created in runtime", c.ID()) } // don't recursively start diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 927b71b2b..d0fbc10fe 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -811,8 +811,9 @@ func (c *Container) cleanupRuntime(ctx context.Context) error { span.SetTag("struct", "container") defer span.Finish() - // If the container is not ContainerStateStopped, do nothing - if c.state.State != ContainerStateStopped { + // If the container is not ContainerStateStopped or + // ContainerStateCreated, do nothing. + if c.state.State != ContainerStateStopped && c.state.State != ContainerStateCreated { return nil } @@ -825,9 +826,14 @@ func (c *Container) cleanupRuntime(ctx context.Context) error { return err } - // Our state is now Exited, as we've removed ourself from - // the runtime. - c.state.State = ContainerStateExited + // If we were Stopped, we are now Exited, as we've removed ourself + // from the runtime. + // If we were Created, we are now Configured. + if c.state.State == ContainerStateStopped { + c.state.State = ContainerStateExited + } else if c.state.State == ContainerStateCreated { + c.state.State = ContainerStateConfigured + } if c.valid { if err := c.save(); err != nil { diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 9ec897a60..8be4d9d31 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -133,6 +133,43 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa return pool.Run() } +// InitContainers initializes container(s) based on CLI inputs. +// Returns list of successful id(s), map of failed id(s) to errors, or a general +// error not from the container. +func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) { + maxWorkers := shared.DefaultPoolSize("init") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum init workers to %d", maxWorkers) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return nil, nil, err + } + + pool := shared.NewPool("init", maxWorkers, len(ctrs)) + for _, c := range ctrs { + ctr := c + + pool.Add(shared.Job{ + ctr.ID(), + func() error { + err := ctr.Init(ctx) + if err != nil { + // If we're initializing all containers, ignore invalid state errors + if cli.All && errors.Cause(err) == libpod.ErrCtrStateInvalid { + return nil + } + return err + } + return nil + }, + }) + } + return pool.Run() +} + // RemoveContainers removes container(s) based on CLI inputs. func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) { var ( diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index a3a48a564..268ac09e8 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -234,6 +234,31 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa return ok, failures, nil } +// InitContainers initializes container(s) based on Varlink. +// It returns a list of successful ID(s), a map of failed container ID to error, +// or an error if a more general error occurred. +func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return nil, nil, err + } + + for _, id := range ids { + initialized, err := iopodman.InitContainer().Call(r.Conn, id) + if err != nil { + failures[id] = err + } else { + ok = append(ok, initialized) + } + } + return ok, failures, nil +} + // KillContainers sends signal to container(s) based on varlink. // Returns list of successful id(s), map of failed id(s) + error, or error not from container func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) { diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 872c7bc26..861e3210b 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -365,6 +365,18 @@ func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error return call.ReplyStartContainer(ctr.ID()) } +// InitContainer initializes the container given by Varlink. +func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error { + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name, err.Error()) + } + if err := ctr.Init(getContext()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyInitContainer(ctr.ID()) +} + // StopContainer ... func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeout int64) error { ctr, err := i.Runtime.LookupContainer(name) diff --git a/test/e2e/init_test.go b/test/e2e/init_test.go new file mode 100644 index 000000000..5865930a5 --- /dev/null +++ b/test/e2e/init_test.go @@ -0,0 +1,129 @@ +package integration + +import ( + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman init", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman init bogus container", func() { + session := podmanTest.Podman([]string{"start", "123456"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) + + It("podman init with no arguments", func() { + session := podmanTest.Podman([]string{"start"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) + + It("podman init single container by ID", func() { + session := podmanTest.Podman([]string{"create", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + init := podmanTest.Podman([]string{"init", cid}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + }) + + It("podman init single container by name", func() { + name := "test1" + session := podmanTest.Podman([]string{"create", "--name", name, "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", name}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", name}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + }) + + It("podman init latest container", func() { + session := podmanTest.Podman([]string{"create", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", "--latest"}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", "--latest"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + }) + + It("podman init all three containers, one running", func() { + session := podmanTest.Podman([]string{"create", "--name", "test1", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session2 := podmanTest.Podman([]string{"create", "--name", "test2", "-d", ALPINE, "ls"}) + session2.WaitWithDefaultTimeout() + Expect(session2.ExitCode()).To(Equal(0)) + session3 := podmanTest.Podman([]string{"run", "--name", "test3", "-d", ALPINE, "top"}) + session3.WaitWithDefaultTimeout() + Expect(session3.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", "--all"}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"inspect", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(conData[0].State.Status).To(Equal("created")) + result2 := podmanTest.Podman([]string{"inspect", "test2"}) + result2.WaitWithDefaultTimeout() + Expect(result2.ExitCode()).To(Equal(0)) + conData2 := result2.InspectContainerToJSON() + Expect(conData2[0].State.Status).To(Equal("created")) + result3 := podmanTest.Podman([]string{"inspect", "test3"}) + result3.WaitWithDefaultTimeout() + Expect(result3.ExitCode()).To(Equal(0)) + conData3 := result3.InspectContainerToJSON() + Expect(conData3[0].State.Status).To(Equal("running")) + }) + + It("podman init running container errors", func() { + session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + init := podmanTest.Podman([]string{"init", "--latest"}) + init.WaitWithDefaultTimeout() + Expect(init.ExitCode()).To(Equal(125)) + }) +}) |