From 2011782d9d52958546e481f84892d00a548b9e12 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 29 Oct 2018 12:06:48 -0500 Subject: Make restart parallel and add --all When attempting to restart many containers, we can benefit from making the restarts parallel. For convenience, two new options are added: --all attempts to restart all containers --run-only when used with --all will attempt to restart only running containers Signed-off-by: baude --- cmd/podman/shared/parallel.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'cmd/podman/shared') diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index 03eba2f0b..dbf43a982 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -72,20 +72,22 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map func Parallelize(job string) int { numCpus := runtime.NumCPU() switch job { - case "stop": - if numCpus <= 2 { - return 4 - } else { - return numCpus * 3 - } + case "ps": + return 8 + case "restart": + return numCpus * 2 case "rm": if numCpus <= 3 { return numCpus * 3 } else { return numCpus * 4 } - case "ps": - return 8 + case "stop": + if numCpus <= 2 { + return 4 + } else { + return numCpus * 3 + } } return 3 } -- cgit v1.2.3-54-g00ecf From b559c19c2fa739cf1c8ede50eab8f5acf74f6bf3 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 29 Oct 2018 13:53:39 -0500 Subject: Make kill, pause, and unpause parallel. Operations like kill, pause, and unpause -- which can operation on one or more containers -- can greatly benefit from parallizing its main job (eq kill). In the case of pauseand unpause, an --all option as was added. pause --all will pause all **running** containers. And unpause --all will unpause all **paused** containers. Signed-off-by: baude --- cmd/podman/kill.go | 51 +++++++++++++++++++++++------- cmd/podman/pause.go | 72 +++++++++++++++++++++++++++++++++--------- cmd/podman/shared/parallel.go | 15 +++++++++ cmd/podman/stop.go | 4 ++- cmd/podman/unpause.go | 73 ++++++++++++++++++++++++++++++++++--------- completions/bash/podman | 9 ++++++ docs/podman-kill.1.md | 2 +- docs/podman-pause.1.md | 19 ++++++++++- docs/podman-unpause.1.md | 20 +++++++++++- test/e2e/pause_test.go | 62 ++++++++++++++++++++++++++++++++++++ 10 files changed, 282 insertions(+), 45 deletions(-) (limited to 'cmd/podman/shared') diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 7ca5bd7c5..27882aeee 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -1,15 +1,16 @@ package main import ( - "os" + "fmt" "syscall" - "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -41,6 +42,12 @@ var ( // killCmd kills one or more containers with a signal func killCmd(c *cli.Context) error { + var ( + lastError error + killFuncs []shared.ParallelWorkerInput + killSignal uint = uint(syscall.SIGTERM) + ) + if err := checkAllAndLatest(c); err != nil { return err } @@ -56,7 +63,6 @@ func killCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var killSignal uint = uint(syscall.SIGTERM) if c.String("signal") != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err @@ -67,17 +73,40 @@ func killCmd(c *cli.Context) error { killSignal = uint(sysSignal) } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") - + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + return err + } for _, ctr := range containers { - if err := ctr.Kill(killSignal); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + con := ctr + f := func() error { + return con.Kill(killSignal) + } + + killFuncs = append(killFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("kill") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + killErrors := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) + + for cid, result := range killErrors { + if result != nil { + if len(killErrors) > 1 { + fmt.Println(result.Error()) } - lastError = errors.Wrapf(err, "unable to find container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + lastError = result + continue } + fmt.Println(cid) } + return lastError } diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 203fa6070..1e1585216 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -5,11 +5,20 @@ import ( "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( + pauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "pause all running containers", + }, + } pauseDescription = ` podman pause @@ -19,6 +28,7 @@ var ( Name: "pause", Usage: "Pauses all the processes in one or more containers", Description: pauseDescription, + Flags: pauseFlags, Action: pauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +36,11 @@ var ( ) func pauseCmd(c *cli.Context) error { + var ( + lastError error + pauseContainers []*libpod.Container + pauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("pause is not supported for rootless containers") } @@ -37,28 +52,55 @@ func pauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + pauseContainers = append(pauseContainers, containers...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + pauseContainers = append(pauseContainers, ctr) + } + } + + // Now assemble the slice of pauseFuncs + for _, ctr := range pauseContainers { + con := ctr + + f := func() error { + return con.Pause() } - if err = ctr.Pause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + pauseFuncs = append(pauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("pause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + pauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) + + for cid, result := range pauseErrors { + if result != nil { + if len(pauseErrors) > 1 { + fmt.Println(result.Error()) } - lastError = errors.Wrapf(err, "failed to pause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + lastError = result + continue } + fmt.Println(cid) } return lastError } diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index dbf43a982..633781a45 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -72,6 +72,16 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map func Parallelize(job string) int { numCpus := runtime.NumCPU() switch job { + case "kill": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "pause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 case "ps": return 8 case "restart": @@ -88,6 +98,11 @@ func Parallelize(job string) int { } else { return numCpus * 3 } + case "unpause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 } return 3 } diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index afeb49f76..cb36fd5cd 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -89,7 +89,9 @@ func stopCmd(c *cli.Context) error { for cid, result := range stopErrors { if result != nil && result != libpod.ErrCtrStopped { - fmt.Println(result.Error()) + if len(stopErrors) > 1 { + fmt.Println(result.Error()) + } lastError = result continue } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index a792aaf6d..648fc9d3d 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -5,11 +5,20 @@ import ( "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( + unpauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "unpause all paused containers", + }, + } unpauseDescription = ` podman unpause @@ -19,6 +28,7 @@ var ( Name: "unpause", Usage: "Unpause the processes in one or more containers", Description: unpauseDescription, + Flags: unpauseFlags, Action: unpauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +36,11 @@ var ( ) func unpauseCmd(c *cli.Context) error { + var ( + lastError error + unpauseContainers []*libpod.Container + unpauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("unpause is not supported for rootless containers") } @@ -37,28 +52,56 @@ func unpauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + cs, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStatePaused, "paused") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + unpauseContainers = append(unpauseContainers, cs...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + unpauseContainers = append(unpauseContainers, ctr) } - if err = ctr.Unpause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + } + + // Assemble the unpause funcs + for _, ctr := range unpauseContainers { + con := ctr + f := func() error { + return con.Unpause() + } + + unpauseFuncs = append(unpauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("unpause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + unpauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) + + for cid, result := range unpauseErrors { + if result != nil && result != libpod.ErrCtrStopped { + if len(unpauseErrors) > 1 { + fmt.Println(result.Error()) } - lastError = errors.Wrapf(err, "failed to unpause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + lastError = result + continue } + fmt.Println(cid) } + return lastError } diff --git a/completions/bash/podman b/completions/bash/podman index ed4e080c9..c029f893a 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1,5 +1,6 @@ : ${PROG:=$(basename ${BASH_SOURCE})} + __podman_previous_extglob_setting=$(shopt -p extglob) shopt -s extglob @@ -1934,6 +1935,10 @@ _podman_save() { } _podman_pause() { + local boolean_options=" + -a + --all + " local options_with_args=" --help -h " @@ -2035,6 +2040,10 @@ _podman_stop() { } _podman_unpause() { + local boolean_options=" + -a + --all + " local options_with_args=" --help -h " diff --git a/docs/podman-kill.1.md b/docs/podman-kill.1.md index 14066d151..85f68a73d 100644 --- a/docs/podman-kill.1.md +++ b/docs/podman-kill.1.md @@ -4,7 +4,7 @@ podman\-kill - Kills one or more containers with a signal ## SYNOPSIS -**podman kill** [*options*] *container* ... +**podman kill** [*options*] [*container* ...] ## DESCRIPTION The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal. diff --git a/docs/podman-pause.1.md b/docs/podman-pause.1.md index b4930de8d..f19fa5d6a 100644 --- a/docs/podman-pause.1.md +++ b/docs/podman-pause.1.md @@ -4,16 +4,33 @@ podman\-pause - Pause one or more containers ## SYNOPSIS -**podman pause** [*options*] *container* ... +**podman pause** [*options*] [*container*...] ## DESCRIPTION Pauses all the processes in one or more containers. You may use container IDs or names as input. +## OPTIONS + +**--all, -a** + +Pause all running containers. + ## EXAMPLE +Pause a container named 'mywebserver' +``` podman pause mywebserver +``` +Pause a container by partial container ID. +``` podman pause 860a4b23 +``` + +Pause all **running** containers. +``` +podman stop -a +``` ## SEE ALSO podman(1), podman-unpause(1) diff --git a/docs/podman-unpause.1.md b/docs/podman-unpause.1.md index 9404e7648..acfab0930 100644 --- a/docs/podman-unpause.1.md +++ b/docs/podman-unpause.1.md @@ -4,16 +4,34 @@ podman\-unpause - Unpause one or more containers ## SYNOPSIS -**podman unpause** [*options*] *container* ... +**podman unpause** [*options*] [*container*...] ## DESCRIPTION Unpauses the processes in one or more containers. You may use container IDs or names as input. +## OPTIONS + +**--all, -a** + +Unpause all paused containers. + ## EXAMPLE +Unpause a container called 'mywebserver' +``` podman unpause mywebserver +``` +Unpause a container by a partial container ID. + +``` podman unpause 860a4b23 +``` + +Unpause all **paused** containers. +``` +podman unpause -a +``` ## SEE ALSO podman(1), podman-pause(1) diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index c34964f59..24876b6d6 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -213,4 +213,66 @@ var _ = Describe("Podman pause", func() { result.WaitWithDefaultTimeout() }) + It("Pause all containers (no containers exist)", func() { + result := podmanTest.Podman([]string{"pause", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + }) + + It("Unpause all containers (no paused containers exist)", func() { + result := podmanTest.Podman([]string{"unpause", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("Pause a bunch of running containers", func() { + podmanTest.RestoreArtifact(nginx) + for i := 0; i < 3; i++ { + name := fmt.Sprintf("test%d", i) + run := podmanTest.Podman([]string{"run", "-dt", "--name", name, nginx}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + } + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(3)) + + pause := podmanTest.Podman([]string{"pause", "--all"}) + pause.WaitWithDefaultTimeout() + Expect(pause.ExitCode()).To(Equal(0)) + + running = podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(0)) + }) + + It("Unpause a bunch of running containers", func() { + podmanTest.RestoreArtifact(nginx) + for i := 0; i < 3; i++ { + name := fmt.Sprintf("test%d", i) + run := podmanTest.Podman([]string{"run", "-dt", "--name", name, nginx}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + } + pause := podmanTest.Podman([]string{"pause", "--all"}) + pause.WaitWithDefaultTimeout() + Expect(pause.ExitCode()).To(Equal(0)) + + unpause := podmanTest.Podman([]string{"unpause", "--all"}) + unpause.WaitWithDefaultTimeout() + Expect(unpause.ExitCode()).To(Equal(0)) + + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(3)) + }) + }) -- cgit v1.2.3-54-g00ecf From 2dd9cae37cb076418393ba61c0fb7b8cf97148f3 Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 7 Nov 2018 13:20:43 -0600 Subject: rm -f now removes a paused container We now can remove a paused container by sending it a kill signal while it is paused. We then unpause the container and it is immediately killed. Also, reworked how the parallelWorker results are handled to provide a more consistent approach to how each subcommand implements it. It also fixes a bug where if one container errors, the error message is duplicated when printed out. Signed-off-by: baude --- cmd/podman/kill.go | 23 +++++++---------------- cmd/podman/pause.go | 17 ++--------------- cmd/podman/restart.go | 16 ++-------------- cmd/podman/rm.go | 26 +++++++++++--------------- cmd/podman/shared/parallel.go | 10 +++++++--- cmd/podman/stop.go | 23 +++++++++-------------- cmd/podman/unpause.go | 18 ++---------------- cmd/podman/utils.go | 17 +++++++++++++++++ docs/podman-rm.1.md | 19 ++++++++++++++++--- libpod/runtime_ctr.go | 14 +++++++++++++- test/e2e/pause_test.go | 19 ++++++++++--------- 11 files changed, 96 insertions(+), 106 deletions(-) (limited to 'cmd/podman/shared') diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 27882aeee..cfe4b4218 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -43,7 +43,6 @@ var ( // killCmd kills one or more containers with a signal func killCmd(c *cli.Context) error { var ( - lastError error killFuncs []shared.ParallelWorkerInput killSignal uint = uint(syscall.SIGTERM) ) @@ -75,8 +74,12 @@ func killCmd(c *cli.Context) error { containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - return err + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) } + for _, ctr := range containers { con := ctr f := func() error { @@ -95,18 +98,6 @@ func killCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - killErrors := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) - - for cid, result := range killErrors { - if result != nil { - if len(killErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - - return lastError + killErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) + return printParallelOutput(killErrors, errCount) } diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 1e1585216..fcb2f3cb8 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -37,7 +36,6 @@ var ( func pauseCmd(c *cli.Context) error { var ( - lastError error pauseContainers []*libpod.Container pauseFuncs []shared.ParallelWorkerInput ) @@ -90,17 +88,6 @@ func pauseCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - pauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) - - for cid, result := range pauseErrors { - if result != nil { - if len(pauseErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + pauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) + return printParallelOutput(pauseErrors, errCount) } diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 2e264db79..630493ef4 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -46,7 +44,6 @@ func restartCmd(c *cli.Context) error { var ( restartFuncs []shared.ParallelWorkerInput containers []*libpod.Container - lastError error restartContainers []*libpod.Container ) @@ -124,15 +121,6 @@ func restartCmd(c *cli.Context) error { logrus.Debugf("Setting maximum workers to %d", maxWorkers) - restartErrors := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) - - for cid, result := range restartErrors { - if result != nil { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) + return printParallelOutput(restartErrors, errCount) } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 0fb5345ee..7c0569b78 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -46,9 +45,7 @@ Running containers will not be removed without the -f option. // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cli.Context) error { var ( - delContainers []*libpod.Container - lastError error - deleteFuncs []shared.ParallelWorkerInput + deleteFuncs []shared.ParallelWorkerInput ) ctx := getContext() @@ -65,7 +62,13 @@ func rmCmd(c *cli.Context) error { return err } - delContainers, lastError = getAllOrLatestContainers(c, runtime, -1, "all") + delContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(delContainers) == 0 { + return err + } + fmt.Println(err.Error()) + } for _, container := range delContainers { con := container @@ -84,14 +87,7 @@ func rmCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - deleteErrors := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - for cid, result := range deleteErrors { - if result != nil { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + // Run the parallel funcs + deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) + return printParallelOutput(deleteErrors, errCount) } diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index 633781a45..e6ce50f95 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -30,9 +30,10 @@ func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results // ParallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker // int determines how many workers/threads should be premade. -func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map[string]error { +func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) (map[string]error, int) { var ( - wg sync.WaitGroup + wg sync.WaitGroup + errorCount int ) resultChan := make(chan containerError, len(functions)) @@ -62,9 +63,12 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map close(resultChan) for ctrError := range resultChan { results[ctrError.ContainerID] = ctrError.Err + if ctrError.Err != nil { + errorCount += 1 + } } - return results + return results, errorCount } // Parallelize provides the maximum number of parallel workers (int) as calculated by a basic diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index cb36fd5cd..04022839a 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -59,7 +59,13 @@ func stopCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) + } var stopFuncs []shared.ParallelWorkerInput for _, ctr := range containers { @@ -85,17 +91,6 @@ func stopCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - stopErrors := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - - for cid, result := range stopErrors { - if result != nil && result != libpod.ErrCtrStopped { - if len(stopErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + stopErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) + return printParallelOutput(stopErrors, errCount) } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index 648fc9d3d..d77e056f8 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -37,7 +36,6 @@ var ( func unpauseCmd(c *cli.Context) error { var ( - lastError error unpauseContainers []*libpod.Container unpauseFuncs []shared.ParallelWorkerInput ) @@ -90,18 +88,6 @@ func unpauseCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - unpauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) - - for cid, result := range unpauseErrors { - if result != nil && result != libpod.ErrCtrStopped { - if len(unpauseErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - - return lastError + unpauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) + return printParallelOutput(unpauseErrors, errCount) } diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index afeccb668..5735156c2 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -207,3 +207,20 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error } return pods, lastError } + +//printParallelOutput takes the map of parallel worker results and outputs them +// to stdout +func printParallelOutput(m map[string]error, errCount int) error { + var lastError error + for cid, result := range m { + if result != nil { + if errCount > 1 { + fmt.Println(result.Error()) + } + lastError = result + continue + } + fmt.Println(cid) + } + return lastError +} diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 7474a0d1f..56664a8c1 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -13,7 +13,7 @@ podman\-rm - Remove one or more containers **--force, f** -Force the removal of a running container +Force the removal of a running and paused containers **--all, a** @@ -29,16 +29,29 @@ to run containers such as CRI-O, the last started container could be from either Remove the volumes associated with the container. (Not yet implemented) ## EXAMPLE - +Remove a container by its name *mywebserver* +``` podman rm mywebserver - +``` +Remove several containers by name and container id. +``` podman rm mywebserver myflaskserver 860a4b23 +``` +Forcibly remove a container by container ID. +``` podman rm -f 860a4b23 +``` +Remove all containers regardless of its run state. +``` podman rm -f -a +``` +Forcibly remove the latest container created. +``` podman rm -f --latest +``` ## SEE ALSO podman(1), podman-rmi(1) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 09dc7c48b..09d0ec042 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -246,7 +246,19 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) } if c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) + if !force { + return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) + } + if err := c.runtime.ociRuntime.killContainer(c, 9); err != nil { + return err + } + if err := c.unpause(); err != nil { + return err + } + // Need to update container state to make sure we know it's stopped + if err := c.waitForExitFileAndSync(); err != nil { + return err + } } // Check that the container's in a good state to be removed diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index 1a2eb1a09..e80915670 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -91,7 +91,7 @@ var _ = Describe("Podman pause", func() { }) - It("podman remove a paused container by id", func() { + It("podman remove a paused container by id without force", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -111,25 +111,26 @@ var _ = Describe("Podman pause", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) - result = podmanTest.Podman([]string{"rm", "--force", cid}) - result.WaitWithDefaultTimeout() + }) - Expect(result.ExitCode()).To(Equal(125)) - Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) - Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) + It("podman remove a paused container by id with force", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() - result = podmanTest.Podman([]string{"unpause", cid}) + result := podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) result = podmanTest.Podman([]string{"rm", "--force", cid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) - }) It("podman stop a paused container by id", func() { -- cgit v1.2.3-54-g00ecf From 780b790415f94ea41ef70bd97828ecb92a4f06d1 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Fri, 16 Nov 2018 16:05:07 +0100 Subject: runlabel: use shlex for splitting commands Use github.com/google/shlex for splitting commands instead of splitting at whitespaces. This way, we avoid accidentally splitting single string arguments into mutliple ones. Signed-off-by: Valentin Rothberg --- cmd/podman/shared/funcs.go | 8 +- cmd/podman/shared/funcs_test.go | 9 +- vendor.conf | 1 + vendor/github.com/google/shlex/COPYING | 202 ++++++++++++++++ vendor/github.com/google/shlex/README | 2 + vendor/github.com/google/shlex/shlex.go | 416 ++++++++++++++++++++++++++++++++ 6 files changed, 633 insertions(+), 5 deletions(-) create mode 100644 vendor/github.com/google/shlex/COPYING create mode 100644 vendor/github.com/google/shlex/README create mode 100644 vendor/github.com/google/shlex/shlex.go (limited to 'cmd/podman/shared') diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index a92e0d547..8520c0616 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/google/shlex" ) func substituteCommand(cmd string) (string, error) { @@ -42,7 +44,11 @@ func GenerateCommand(command, imageName, name string) ([]string, error) { if name == "" { name = imageName } - cmd := strings.Split(command, " ") + + cmd, err := shlex.Split(command) + if err != nil { + return nil, err + } prog, err := substituteCommand(cmd[0]) if err != nil { diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go index 596df84e8..7506b9d9c 100644 --- a/cmd/podman/shared/funcs_test.go +++ b/cmd/podman/shared/funcs_test.go @@ -18,10 +18,11 @@ var ( ) func TestGenerateCommand(t *testing.T) { - inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" + inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo \"hello world\"" + correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo hello world" newCommand, err := GenerateCommand(inputCommand, "foo", "bar") assert.Nil(t, err) + assert.Equal(t, "hello world", newCommand[11]) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } @@ -108,8 +109,8 @@ func TestGenerateCommandNoSetName(t *testing.T) { } func TestGenerateCommandNoName(t *testing.T) { - inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" + inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" + correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" newCommand, err := GenerateCommand(inputCommand, "foo", "") assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) diff --git a/vendor.conf b/vendor.conf index a16975bf9..c8e968648 100644 --- a/vendor.conf +++ b/vendor.conf @@ -99,3 +99,4 @@ github.com/openshift/imagebuilder master github.com/ulikunitz/xz v0.5.4 github.com/mailru/easyjson 03f2033d19d5860aef995fe360ac7d395cd8ce65 github.com/coreos/go-iptables 25d087f3cffd9aedc0c2b7eff25f23cbf3c20fe1 +github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe diff --git a/vendor/github.com/google/shlex/COPYING b/vendor/github.com/google/shlex/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/google/shlex/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/google/shlex/README b/vendor/github.com/google/shlex/README new file mode 100644 index 000000000..c86bcc066 --- /dev/null +++ b/vendor/github.com/google/shlex/README @@ -0,0 +1,2 @@ +go-shlex is a simple lexer for go that supports shell-style quoting, +commenting, and escaping. diff --git a/vendor/github.com/google/shlex/shlex.go b/vendor/github.com/google/shlex/shlex.go new file mode 100644 index 000000000..d98308bce --- /dev/null +++ b/vendor/github.com/google/shlex/shlex.go @@ -0,0 +1,416 @@ +/* +Copyright 2012 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package shlex implements a simple lexer which splits input in to tokens using +shell-style rules for quoting and commenting. + +The basic use case uses the default ASCII lexer to split a string into sub-strings: + + shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"} + +To process a stream of strings: + + l := NewLexer(os.Stdin) + for ; token, err := l.Next(); err != nil { + // process token + } + +To access the raw token stream (which includes tokens for comments): + + t := NewTokenizer(os.Stdin) + for ; token, err := t.Next(); err != nil { + // process token + } + +*/ +package shlex + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +// TokenType is a top-level token classification: A word, space, comment, unknown. +type TokenType int + +// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape. +type runeTokenClass int + +// the internal state used by the lexer state machine +type lexerState int + +// Token is a (type, value) pair representing a lexographical token. +type Token struct { + tokenType TokenType + value string +} + +// Equal reports whether tokens a, and b, are equal. +// Two tokens are equal if both their types and values are equal. A nil token can +// never be equal to another token. +func (a *Token) Equal(b *Token) bool { + if a == nil || b == nil { + return false + } + if a.tokenType != b.tokenType { + return false + } + return a.value == b.value +} + +// Named classes of UTF-8 runes +const ( + spaceRunes = " \t\r\n" + escapingQuoteRunes = `"` + nonEscapingQuoteRunes = "'" + escapeRunes = `\` + commentRunes = "#" +) + +// Classes of rune token +const ( + unknownRuneClass runeTokenClass = iota + spaceRuneClass + escapingQuoteRuneClass + nonEscapingQuoteRuneClass + escapeRuneClass + commentRuneClass + eofRuneClass +) + +// Classes of lexographic token +const ( + UnknownToken TokenType = iota + WordToken + SpaceToken + CommentToken +) + +// Lexer state machine states +const ( + startState lexerState = iota // no runes have been seen + inWordState // processing regular runes in a word + escapingState // we have just consumed an escape rune; the next rune is literal + escapingQuotedState // we have just consumed an escape rune within a quoted string + quotingEscapingState // we are within a quoted string that supports escaping ("...") + quotingState // we are within a string that does not support escaping ('...') + commentState // we are within a comment (everything following an unquoted or unescaped # +) + +// tokenClassifier is used for classifying rune characters. +type tokenClassifier map[rune]runeTokenClass + +func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) { + for _, runeChar := range runes { + typeMap[runeChar] = tokenType + } +} + +// newDefaultClassifier creates a new classifier for ASCII characters. +func newDefaultClassifier() tokenClassifier { + t := tokenClassifier{} + t.addRuneClass(spaceRunes, spaceRuneClass) + t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass) + t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass) + t.addRuneClass(escapeRunes, escapeRuneClass) + t.addRuneClass(commentRunes, commentRuneClass) + return t +} + +// ClassifyRune classifiees a rune +func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass { + return t[runeVal] +} + +// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped. +type Lexer Tokenizer + +// NewLexer creates a new lexer from an input stream. +func NewLexer(r io.Reader) *Lexer { + + return (*Lexer)(NewTokenizer(r)) +} + +// Next returns the next word, or an error. If there are no more words, +// the error will be io.EOF. +func (l *Lexer) Next() (string, error) { + for { + token, err := (*Tokenizer)(l).Next() + if err != nil { + return "", err + } + switch token.tokenType { + case WordToken: + return token.value, nil + case CommentToken: + // skip comments + default: + return "", fmt.Errorf("Unknown token type: %v", token.tokenType) + } + } +} + +// Tokenizer turns an input stream into a sequence of typed tokens +type Tokenizer struct { + input bufio.Reader + classifier tokenClassifier +} + +// NewTokenizer creates a new tokenizer from an input stream. +func NewTokenizer(r io.Reader) *Tokenizer { + input := bufio.NewReader(r) + classifier := newDefaultClassifier() + return &Tokenizer{ + input: *input, + classifier: classifier} +} + +// scanStream scans the stream for the next token using the internal state machine. +// It will panic if it encounters a rune which it does not know how to handle. +func (t *Tokenizer) scanStream() (*Token, error) { + state := startState + var tokenType TokenType + var value []rune + var nextRune rune + var nextRuneType runeTokenClass + var err error + + for { + nextRune, _, err = t.input.ReadRune() + nextRuneType = t.classifier.ClassifyRune(nextRune) + + if err == io.EOF { + nextRuneType = eofRuneClass + err = nil + } else if err != nil { + return nil, err + } + + switch state { + case startState: // no runes read yet + { + switch nextRuneType { + case eofRuneClass: + { + return nil, io.EOF + } + case spaceRuneClass: + { + } + case escapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingState + } + case escapeRuneClass: + { + tokenType = WordToken + state = escapingState + } + case commentRuneClass: + { + tokenType = CommentToken + state = commentState + } + default: + { + tokenType = WordToken + value = append(value, nextRune) + state = inWordState + } + } + } + case inWordState: // in a regular word + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + state = quotingState + } + case escapeRuneClass: + { + state = escapingState + } + default: + { + value = append(value, nextRune) + } + } + } + case escapingState: // the rune after an escape character + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = inWordState + value = append(value, nextRune) + } + } + } + case escapingQuotedState: // the next rune after an escape character, in double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = quotingEscapingState + value = append(value, nextRune) + } + } + } + case quotingEscapingState: // in escaping double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = inWordState + } + case escapeRuneClass: + { + state = escapingQuotedState + } + default: + { + value = append(value, nextRune) + } + } + } + case quotingState: // in non-escaping single quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case nonEscapingQuoteRuneClass: + { + state = inWordState + } + default: + { + value = append(value, nextRune) + } + } + } + case commentState: // in a comment + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + if nextRune == '\n' { + state = startState + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } else { + value = append(value, nextRune) + } + } + default: + { + value = append(value, nextRune) + } + } + } + default: + { + return nil, fmt.Errorf("Unexpected state: %v", state) + } + } + } +} + +// Next returns the next token in the stream. +func (t *Tokenizer) Next() (*Token, error) { + return t.scanStream() +} + +// Split partitions a string into a slice of strings. +func Split(s string) ([]string, error) { + l := NewLexer(strings.NewReader(s)) + subStrings := make([]string, 0) + for { + word, err := l.Next() + if err != nil { + if err == io.EOF { + return subStrings, nil + } + return subStrings, err + } + subStrings = append(subStrings, word) + } +} -- cgit v1.2.3-54-g00ecf From 5c02dda869390725a799339b094f548d327c9122 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 3 Dec 2018 09:15:29 -0600 Subject: Adding more varlink endpoints * runlabel * checkpoint * restore * container|image exists * mount * unmount Signed-off-by: baude --- API.md | 103 ++++++++++++++++++++++++++++++++++- cmd/podman/runlabel.go | 81 ++++----------------------- cmd/podman/shared/container.go | 81 +++++++++++++++++++++++++++ cmd/podman/varlink/io.podman.varlink | 52 ++++++++++++++++++ pkg/varlinkapi/containers.go | 49 +++++++++++++++++ pkg/varlinkapi/images.go | 45 +++++++++++++++ pkg/varlinkapi/mount.go | 49 +++++++++++++++++ 7 files changed, 389 insertions(+), 71 deletions(-) create mode 100644 pkg/varlinkapi/mount.go (limited to 'cmd/podman/shared') diff --git a/API.md b/API.md index 34d401aca..4d48e8245 100755 --- a/API.md +++ b/API.md @@ -9,6 +9,14 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) +[func ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) string](#ContainerCheckpoint) + +[func ContainerExists(name: string) int](#ContainerExists) + +[func ContainerRestore(name: string, keep: bool, tcpEstablished: bool) string](#ContainerRestore) + +[func ContainerRunlabel(runlabel: Runlabel) ](#ContainerRunlabel) + [func CreateContainer(create: Create) string](#CreateContainer) [func CreateImage() NotImplemented](#CreateImage) @@ -43,6 +51,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func HistoryImage(name: string) ImageHistory](#HistoryImage) +[func ImageExists(name: string) int](#ImageExists) + [func ImportImage(source: string, reference: string, message: string, changes: []string) string](#ImportImage) [func InspectContainer(name: string) string](#InspectContainer) @@ -57,6 +67,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListContainerChanges(name: string) ContainerChanges](#ListContainerChanges) +[func ListContainerMounts() []string](#ListContainerMounts) + +[func ListContainerPorts(name: string) NotImplemented](#ListContainerPorts) + [func ListContainerProcesses(name: string, opts: []string) []string](#ListContainerProcesses) [func ListContainers() ListContainerData](#ListContainers) @@ -65,6 +79,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListPods() ListPodData](#ListPods) +[func MountContainer(name: string) string](#MountContainer) + [func PauseContainer(name: string) string](#PauseContainer) [func PausePod(name: string) string](#PausePod) @@ -103,6 +119,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func TopPod() NotImplemented](#TopPod) +[func UnmountContainer(name: string, force: bool) ](#UnmountContainer) + [func UnpauseContainer(name: string) string](#UnpauseContainer) [func UnpausePod(name: string) string](#UnpausePod) @@ -165,6 +183,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type PodmanInfo](#PodmanInfo) +[type Runlabel](#Runlabel) + [type Sockets](#Sockets) [type StringResponse](#StringResponse) @@ -211,6 +231,31 @@ attributes: _CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, ONBUILD, STOPSIGNAL, USER, VOL container while it is being committed, pass a _true_ bool for the pause argument. If the container cannot be found by the ID or name provided, a (ContainerNotFound)[#ContainerNotFound] error will be returned; otherwise, the resulting image's ID will be returned as a string. +### func ContainerCheckpoint +
+ +method ContainerCheckpoint(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), leaveRunning: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +ID. On successful checkpoint, the id of the checkpointed container is returned. +### func ContainerExists +
+ +method ContainerExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)
+ContainerExists takes a full or partial container ID or name and returns an int as to +whether the container exists in local storage. A result of 0 means the container does +exists; whereas a result of 1 means it could not be found. +### func ContainerRestore +
+ +method ContainerRestore(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+ContainerRestore restores a container that has been checkpointed. The container to be restored can +be identified by its name or full/partial container ID. A successful restore will result in the return +of the container's ID. +### func ContainerRunlabel +
+ +method ContainerRunlabel(runlabel: [Runlabel](#Runlabel))
+ContainerRunlabel runs executes a command as described by a given container image label. ### func CreateContainer
@@ -403,6 +448,13 @@ method HistoryImage(name: [string](https://godoc.org/builtin#string)) [ImageHist HistoryImage takes the name or ID of an image and returns information about its history and layers. The returned history is in the form of an array of ImageHistory structures. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error is returned. +### func ImageExists +
+ +method ImageExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)
+ImageExists talks a full or partial image ID or name and returns an int as to whether +the image exists in local storage. An int result of 0 means the image does exist in +local storage; whereas 1 indicates the image does not exists in local storage. ### func ImportImage
@@ -453,6 +505,17 @@ See also [StopPod](StopPod). method ListContainerChanges(name: [string](https://godoc.org/builtin#string)) [ContainerChanges](#ContainerChanges)
ListContainerChanges takes a name or ID of a container and returns changes between the container and its base image. It returns a struct of changed, deleted, and added path names. +### func ListContainerMounts +
+ +method ListContainerMounts() [[]string](#[]string)
+ListContainerMounts gathers all the mounted container mount points and returns them as an array +of strings +### func ListContainerPorts +
+ +method ListContainerPorts(name: [string](https://godoc.org/builtin#string)) [NotImplemented](#NotImplemented)
+This function is not implemented yet. ### func ListContainerProcesses
@@ -491,6 +554,12 @@ an image currently in storage. See also [InspectImage](InspectImage). method ListPods() [ListPodData](#ListPodData)
ListPods returns a list of pods in no particular order. They are returned as an array of ListPodData structs. See also [GetPod](#GetPod). +### func MountContainer +
+ +method MountContainer(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)
+MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +mount is returned as a string. ### func PauseContainer
@@ -696,6 +765,11 @@ be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, method TopPod() [NotImplemented](#NotImplemented)
This method has not been implemented yet. +### func UnmountContainer +
+ +method UnmountContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool))
+UnmountContainer umounts a container by its name or full/partial container ID. ### func UnpauseContainer
@@ -1293,6 +1367,33 @@ insecure_registries [[]string](#[]string) store [InfoStore](#InfoStore) podman [InfoPodmanBinary](#InfoPodmanBinary) +### type Runlabel + +Runlabel describes the required input for container runlabel + +image [string](https://godoc.org/builtin#string) + +authfile [string](https://godoc.org/builtin#string) + +certDir [string](https://godoc.org/builtin#string) + +creds [string](https://godoc.org/builtin#string) + +display [bool](https://godoc.org/builtin#bool) + +name [string](https://godoc.org/builtin#string) + +pull [bool](https://godoc.org/builtin#bool) + +signaturePolicyPath [string](https://godoc.org/builtin#string) + +tlsVerify [bool](https://godoc.org/builtin#bool) + +label [string](https://godoc.org/builtin#string) + +extraArgs [[]string](#[]string) + +opts [map[string]](#map[string]) ### type Sockets Sockets describes sockets location for a container @@ -1336,7 +1437,7 @@ ImageNotFound means the image could not be found by the provided name or ID in l NoContainerRunning means none of the containers requested are running in a command that requires a running container. ### type NoContainersInPod -NoContainersInPod means a pod has no containers on which to perform operation. It contains +NoContainersInPod means a pod has no containers on which to perform the operation. It contains the pod ID. ### type PodContainerError diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index e1dee1fb2..b0d87d0d9 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -6,11 +6,9 @@ import ( "os" "strings" - "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -94,7 +92,7 @@ func runlabelCmd(c *cli.Context) error { imageName string stdErr, stdOut io.Writer stdIn io.Reader - newImage *image.Image + extraArgs []string ) // Evil images could trick into recursively executing the runlabel @@ -124,6 +122,9 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("the display and quiet flags cannot be used together.") } + if len(args) > 2 { + extraArgs = args[2:] + } pull := c.Bool("pull") label := args[0] @@ -151,75 +152,24 @@ func runlabelCmd(c *cli.Context) error { stdIn = nil } - if pull { - var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) - if err != nil { - return err - } - registryCreds = creds - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), - DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), - } - authfile := getAuthFile(c.String("authfile")) - - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false) - } else { - newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) - } - if err != nil { - return errors.Wrapf(err, "unable to find image") - } - - if len(newImage.Names()) < 1 { - imageName = newImage.ID() - } else { - imageName = newImage.Names()[0] + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: c.String("cert-dir"), + DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), } - runLabel, err := newImage.GetLabel(ctx, label) + authfile := getAuthFile(c.String("authfile")) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut) if err != nil { return err } - - // If no label to execute, we return if runLabel == "" { return nil } - // The user provided extra arguments that need to be tacked onto the label's command - if len(args) > 2 { - runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " ")) - } - - cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name")) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) if err != nil { - return errors.Wrapf(err, "unable to generate command") - } - env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts) - env = append(env, "PODMAN_RUNLABEL_NESTED=1") - - envmap := envSliceToMap(env) - - envmapper := func(k string) string { - switch k { - case "OPT1": - return envmap["OPT1"] - case "OPT2": - return envmap["OPT2"] - case "OPT3": - return envmap["OPT3"] - } - return "" + return err } - - newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd = strings.Split(newS, " ") - if !c.Bool("quiet") { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) if c.Bool("display") { @@ -228,12 +178,3 @@ func runlabelCmd(c *cli.Context) error { } return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } - -func envSliceToMap(env []string) map[string]string { - m := make(map[string]string) - for _, i := range env { - split := strings.Split(i, "=") - m[split[0]] = strings.Join(split[1:], " ") - } - return m -} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 4404268d4..d0e892961 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -1,10 +1,15 @@ package shared import ( + "context" "encoding/json" "fmt" + "github.com/containers/image/types" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "io" "os" "path/filepath" "regexp" @@ -589,3 +594,79 @@ func portsToString(ports []ocicni.PortMapping) string { } return strings.Join(portDisplay, ", ") } + +// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the +// contruction of the runlabel output and environment variables +func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { + var ( + newImage *image.Image + err error + imageName string + ) + if pull { + var registryCreds *types.DockerAuthConfig + if inputCreds != "" { + creds, err := util.ParseRegistryCreds(inputCreds) + if err != nil { + return "", "", err + } + registryCreds = creds + } + dockerRegistryOptions.DockerRegistryCreds = registryCreds + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, false) + } else { + newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) + } + if err != nil { + return "", "", errors.Wrapf(err, "unable to find image") + } + + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } + + runLabel, err := newImage.GetLabel(ctx, label) + return runLabel, imageName, err +} + +// GenerateRunlabelCommand generates the command that will eventually be execucted by podman +func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) { + // The user provided extra arguments that need to be tacked onto the label's command + if len(extraArgs) > 0 { + runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) + } + cmd, err := GenerateCommand(runLabel, imageName, name) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to generate command") + } + env := GenerateRunEnvironment(name, imageName, opts) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + + envmap := envSliceToMap(env) + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd = strings.Split(newS, " ") + return cmd, env, nil +} + +func envSliceToMap(env []string) map[string]string { + m := make(map[string]string) + for _, i := range env { + split := strings.Split(i, "=") + m[split[0]] = strings.Join(split[1:], " ") + } + return m +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4a4a1854c..b081b60a3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -371,6 +371,22 @@ type PodContainerErrorData ( reason: string ) +# Runlabel describes the required input for container runlabel +type Runlabel( + image: string, + authfile: string, + certDir: string, + creds: string, + display: bool, + name: string, + pull: bool, + signaturePolicyPath: string, + tlsVerify: bool, + label: string, + extraArgs: []string, + opts: [string]string +) + # Ping provides a response for developers to ensure their varlink setup is working. # #### Example # ~~~ @@ -804,6 +820,42 @@ method TopPod() -> (notimplemented: NotImplemented) # ~~~ method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) +# ImageExists talks a full or partial image ID or name and returns an int as to whether +# the image exists in local storage. An int result of 0 means the image does exist in +# local storage; whereas 1 indicates the image does not exists in local storage. +method ImageExists(name: string) -> (exists: int) + +# ContainerExists takes a full or partial container ID or name and returns an int as to +# whether the container exists in local storage. A result of 0 means the container does +# exists; whereas a result of 1 means it could not be found. +method ContainerExists(name: string) -> (exists: int) + +# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +# ID. On successful checkpoint, the id of the checkpointed container is returned. +method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRestore restores a container that has been checkpointed. The container to be restored can +# be identified by its name or full/partial container ID. A successful restore will result in the return +# of the container's ID. +method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRunlabel runs executes a command as described by a given container image label. +method ContainerRunlabel(runlabel: Runlabel) -> () + +# ListContainerMounts gathers all the mounted container mount points and returns them as an array +# of strings +method ListContainerMounts() -> (mounts: []string) + +# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +# mount is returned as a string. +method MountContainer(name: string) -> (path: string) + +# UnmountContainer umounts a container by its name or full/partial container ID. +method UnmountContainer(name: string, force: bool) -> () + +# This function is not implemented yet. +method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index f517e9b6e..07d981786 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -278,6 +278,18 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim return call.ReplyRestartContainer(ctr.ID()) } +// ContainerExists looks in local storage for the existence of a container +func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.LookupContainer(name) + if errors.Cause(err) == libpod.ErrNoSuchCtr { + return call.ReplyContainerExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerExists(0) +} + // KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1 // for the signal arg. func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error { @@ -413,3 +425,40 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err } return call.ReplyGetAttachSockets(s) } + +// ContainerCheckpoint ... +func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + KeepRunning: leaveRunning, + } + if err := ctr.Checkpoint(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerCheckpoint(ctr.ID()) +} + +// ContainerRestore ... +func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + } + if err := ctr.Restore(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRestore(ctr.ID()) +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 42e285b53..6d3f19422 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -4,7 +4,9 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/containers/libpod/cmd/podman/shared" "io" + "os" "path/filepath" "strings" "time" @@ -19,6 +21,7 @@ import ( "github.com/containers/libpod/libpod/image" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" "github.com/docker/go-units" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" @@ -500,3 +503,45 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { } return call.ReplyPullImage(newImage.ID()) } + +// ImageExists returns bool as to whether the input image exists in local storage +func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if errors.Cause(err) == libpod.ErrNoSuchImage { + return call.ReplyImageExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyImageExists(0) +} + +// ContainerRunlabel ... +func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { + ctx := getContext() + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: input.CertDir, + DockerInsecureSkipTLSVerify: !input.TlsVerify, + } + + stdErr := os.Stderr + stdOut := os.Stdout + stdIn := os.Stdin + + runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, input.Creds, dockerRegistryOptions, input.Authfile, input.SignaturePolicyPath, nil) + if err != nil { + return err + } + if runLabel == "" { + return nil + } + + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs) + if err != nil { + return err + } + if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRunlabel() +} diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go new file mode 100644 index 000000000..84e6b2709 --- /dev/null +++ b/pkg/varlinkapi/mount.go @@ -0,0 +1,49 @@ +package varlinkapi + +import ( + "github.com/containers/libpod/cmd/podman/varlink" +) + +// ListContainerMounts ... +func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { + var mounts []string + allContainers, err := i.Runtime.GetAllContainers() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, container := range allContainers { + mounted, mountPoint, err := container.Mounted() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if mounted { + mounts = append(mounts, mountPoint) + } + } + return call.ReplyListContainerMounts(mounts) +} + +// MountContainer ... +func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + path, err := container.Mount() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyMountContainer(path) +} + +// UnmountContainer ... +func (i *LibpodAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := container.Unmount(force); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyUnmountContainer() +} -- cgit v1.2.3-54-g00ecf