From 74d984e0560b2cb421287395b025687e3aabe118 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 5 Feb 2019 10:41:55 -0800 Subject: Add podman system prune and info commands We are missing the equivalence of the docker system commands This patch set adds `podman system prune` and `podman system info` Signed-off-by: Daniel J Walsh --- cmd/podman/commands.go | 4 ++ cmd/podman/commands_remoteclient.go | 4 ++ cmd/podman/containers_prune.go | 37 +++++++------ cmd/podman/images_prune.go | 1 + cmd/podman/main.go | 1 + cmd/podman/run.go | 1 + cmd/podman/system.go | 29 ++++++++++ cmd/podman/system_prune.go | 107 ++++++++++++++++++++++++++++++++++++ cmd/podman/volume_prune.go | 48 ++++++++-------- commands.md | 1 + completions/bash/podman | 50 +++++++++++++++++ docs/links/podman-system-info.1 | 1 + docs/podman-info.1.md | 4 +- docs/podman-system-prune.1.md | 37 +++++++++++++ docs/podman-system.1.md | 20 +++++++ docs/podman.1.md | 1 + libpod/adapter/runtime_remote.go | 25 +++++++++ test/e2e/info_test.go | 6 ++ test/e2e/prune_test.go | 13 +++++ test/e2e/volume_prune_test.go | 30 ++++++++++ transfer.md | 6 +- 21 files changed, 383 insertions(+), 43 deletions(-) create mode 100644 cmd/podman/system.go create mode 100644 cmd/podman/system_prune.go create mode 100644 docs/links/podman-system-info.1 create mode 100644 docs/podman-system-prune.1.md create mode 100644 docs/podman-system.1.md diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index d8fdd556f..2efcab695 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -53,6 +53,10 @@ func getImageSubCommands() []cli.Command { } } +func getSystemSubCommands() []cli.Command { + return []cli.Command{infoCommand} +} + func getContainerSubCommands() []cli.Command { return []cli.Command{ attachCommand, diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index 6701e14a1..0adbd7b4c 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -16,6 +16,10 @@ func getContainerSubCommands() []cli.Command { return []cli.Command{} } +func getSystemSubCommands() []cli.Command { + return []cli.Command{} +} + func getMainAppFlags() []cli.Flag { return []cli.Flag{} } diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index 92604e82f..09141e9a3 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -1,9 +1,11 @@ package main import ( - "github.com/containers/libpod/cmd/podman/libpodruntime" + "context" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -25,20 +27,11 @@ var ( } ) -func pruneContainersCmd(c *cli.Context) error { - var ( - deleteFuncs []shared.ParallelWorkerInput - ) - - ctx := getContext() - runtime, err := libpodruntime.GetRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) +func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force bool) error { + var deleteFuncs []shared.ParallelWorkerInput filter := func(c *libpod.Container) bool { - state, _ := c.State() + state, err := c.State() if state == libpod.ContainerStateStopped || (state == libpod.ContainerStateExited && err == nil && c.PodID() == "") { return true } @@ -54,7 +47,7 @@ func pruneContainersCmd(c *cli.Context) error { for _, container := range delContainers { con := container f := func() error { - return runtime.RemoveContainer(ctx, con, c.Bool("force")) + return runtime.RemoveContainer(ctx, con, force) } deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ @@ -62,13 +55,23 @@ func pruneContainersCmd(c *cli.Context) error { ParallelFunc: f, }) } + // Run the parallel funcs + deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) + return printParallelOutput(deleteErrors, errCount) +} + +func pruneContainersCmd(c *cli.Context) error { + runtime, err := adapter.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + maxWorkers := shared.Parallelize("rm") if c.GlobalIsSet("max-workers") { maxWorkers = c.GlobalInt("max-workers") } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - // Run the parallel funcs - deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - return printParallelOutput(deleteErrors, errCount) + return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force")) } diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index aef387732..baef813e5 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 1ca8882eb..d90df8222 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -97,6 +97,7 @@ func main() { inspectCommand, pullCommand, rmiCommand, + systemCommand, tagCommand, versionCommand, } diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 20cb85347..662aed900 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -131,6 +131,7 @@ func runCmd(c *cli.Context) error { ctrExitCode, err := readExitFile(runtime.GetConfig().TmpDir, ctr.ID()) if err != nil { logrus.Errorf("Cannot get exit code: %v", err) + exitCode = 127 } else { exitCode = ctrExitCode } diff --git a/cmd/podman/system.go b/cmd/podman/system.go new file mode 100644 index 000000000..9596252ad --- /dev/null +++ b/cmd/podman/system.go @@ -0,0 +1,29 @@ +package main + +import ( + "sort" + + "github.com/urfave/cli" +) + +var ( + systemSubCommands = []cli.Command{ + pruneSystemCommand, + } + systemDescription = "Manage podman" + systemCommand = cli.Command{ + Name: "system", + Usage: "Manage podman", + Description: systemDescription, + ArgsUsage: "", + Subcommands: getSystemSubCommandsSorted(), + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +func getSystemSubCommandsSorted() []cli.Command { + systemSubCommands = append(systemSubCommands, getSystemSubCommands()...) + sort.Sort(commandSortedAlpha{systemSubCommands}) + return systemSubCommands +} diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go new file mode 100644 index 000000000..145fb4c94 --- /dev/null +++ b/cmd/podman/system_prune.go @@ -0,0 +1,107 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod/adapter" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + pruneSystemDescription = ` + podman system prune + + Remove unused data +` + pruneSystemFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "remove all unused data", + }, + cli.BoolFlag{ + Name: "force, f", + Usage: "Do not prompt for confirmation", + }, + cli.BoolFlag{ + Name: "volumes", + Usage: "Prune volumes", + }, + } + pruneSystemCommand = cli.Command{ + Name: "prune", + Usage: "Remove unused data", + Description: pruneSystemDescription, + Action: pruneSystemCmd, + OnUsageError: usageErrorHandler, + Flags: pruneSystemFlags, + } +) + +func pruneSystemCmd(c *cli.Context) error { + + // Prompt for confirmation if --force is not set + if !c.Bool("force") { + reader := bufio.NewReader(os.Stdin) + volumeString := "" + if c.Bool("volumes") { + volumeString = ` + - all volumes not used by at least one container` + } + fmt.Printf(` +WARNING! This will remove: + - all stopped containers%s + - all dangling images + - all build cache +Are you sure you want to continue? [y/N] `, volumeString) + ans, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(ans)[0] != 'y' { + return nil + } + } + + runtime, err := adapter.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ctx := getContext() + fmt.Println("Deleted Containers") + lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false) + if c.Bool("volumes") { + fmt.Println("Deleted Volumes") + err := volumePrune(runtime, getContext()) + if err != nil { + if lasterr != nil { + logrus.Errorf("%q", lasterr) + } + lasterr = err + } + } + + // Call prune; if any cids are returned, print them and then + // return err in case an error also came up + pruneCids, err := runtime.PruneImages(c.Bool("all")) + if len(pruneCids) > 0 { + fmt.Println("Deleted Images") + for _, cid := range pruneCids { + fmt.Println(cid) + } + } + if err != nil { + if lasterr != nil { + logrus.Errorf("%q", lasterr) + } + lasterr = err + } + return lasterr +} diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go index 652c50f42..41d95f9c7 100644 --- a/cmd/podman/volume_prune.go +++ b/cmd/podman/volume_prune.go @@ -2,12 +2,13 @@ package main import ( "bufio" + "context" "fmt" "os" "strings" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -37,21 +38,40 @@ var volumePruneCommand = cli.Command{ UseShortOptionHandling: true, } -func volumePruneCmd(c *cli.Context) error { +func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { var lastError error + volumes, err := runtime.GetAllVolumes() + if err != nil { + return err + } + + for _, vol := range volumes { + err = runtime.RemoveVolume(ctx, vol, false, true) + if err == nil { + fmt.Println(vol.Name()) + } else if err != libpod.ErrVolumeBeingUsed { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name()) + } + } + return lastError +} + +func volumePruneCmd(c *cli.Context) error { + if err := validateFlags(c, volumePruneFlags); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctx := getContext() - // Prompt for confirmation if --force is not set if !c.Bool("force") { reader := bufio.NewReader(os.Stdin) @@ -66,21 +86,5 @@ func volumePruneCmd(c *cli.Context) error { } } - volumes, err := runtime.GetAllVolumes() - if err != nil { - return err - } - - for _, vol := range volumes { - err = runtime.RemoveVolume(ctx, vol, false, true) - if err == nil { - fmt.Println(vol.Name()) - } else if err != libpod.ErrVolumeBeingUsed { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name()) - } - } - return lastError + return volumePrune(runtime, getContext()) } diff --git a/commands.md b/commands.md index 00be4f1bd..c7d03d5ad 100644 --- a/commands.md +++ b/commands.md @@ -66,6 +66,7 @@ | [podman-start(1)](/docs/podman-start.1.md) | Starts one or more containers | [podman-stats(1)](/docs/podman-stats.1.md) | Display a live stream of one or more containers' resource usage statistics|[![...](/docs/play.png)](https://asciinema.org/a/vfUPbAA5tsNWhsfB9p25T6xdr)| | [podman-stop(1)](/docs/podman-stop.1.md) | Stops one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/KNRF9xVXeaeNTNjBQVogvZBcp)| +| [podman-system(1)](/docs/podman-system.1.md) | Manage podman || | [podman-tag(1)](/docs/podman-tag.1.md) | Add an additional name to a local image |[![...](/docs/play.png)](https://asciinema.org/a/133803)| | [podman-top(1)](/docs/podman-top.1.md) | Display the running processes of a container |[![...](/docs/play.png)](https://asciinema.org/a/5WCCi1LXwSuRbvaO9cBUYf3fk)| | [podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| diff --git a/completions/bash/podman b/completions/bash/podman index e0de4586c..9df87aef4 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -979,6 +979,51 @@ _podman_container() { esac } +_podman_system_info() { + _podman_info +} + +_podman_system_prune() { + local options_with_args=" + " + + local boolean_options=" + -a + --all + -f + --force + -h + --help + --volumes + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac +} + +_podman_system() { + local boolean_options=" + --help + -h + " + subcommands=" + info + prune + " + __podman_subcommands "$subcommands" && return + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; + esac +} + _podman_commit() { local options_with_args=" --author @@ -2482,6 +2527,11 @@ _podman_container_prune() { -h --help " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac } _podman_container_exists() { diff --git a/docs/links/podman-system-info.1 b/docs/links/podman-system-info.1 new file mode 100644 index 000000000..809a71b8b --- /dev/null +++ b/docs/links/podman-system-info.1 @@ -0,0 +1 @@ +.so man1/podman-info.1 diff --git a/docs/podman-info.1.md b/docs/podman-info.1.md index d3a0658c9..a6579ec1f 100644 --- a/docs/podman-info.1.md +++ b/docs/podman-info.1.md @@ -1,9 +1,9 @@ -% podman-version(1) +% podman-info(1) ## NAME +podman\-system\-info - Display system information podman\-info - Display system information - ## SYNOPSIS **podman info** [*options*] diff --git a/docs/podman-system-prune.1.md b/docs/podman-system-prune.1.md new file mode 100644 index 000000000..1cdafb774 --- /dev/null +++ b/docs/podman-system-prune.1.md @@ -0,0 +1,37 @@ +% podman-system-prune(1) podman + +## NAME +podman\-system\-prune - Remove all unused container, image and volume data + +## SYNOPSIS +**podman system prune** +[**-all**|**--a**] +[**-force**|**--f**] +[**-help**|**--h**] +[**-volumes**|**--v**] + +## DESCRIPTION +**podman system prune** removes all unused containers, (both dangling and unreferenced) from local storage and optionally, volumes. + +With the `all` option, you can delete all unused images. Unused images are dangling images as well as any image that does not have any containers based on it. + +By default, volumes are not removed to prevent important data from being deleted if there is currently no container using the volume. Use the --volumes flag when running the command to prune volumes as well. + +## OPTIONS +**--all, -a** + +Remove all unused images not just dangling ones. + +**--force, -f** + +Do not prompt for confirmation + +**--volumes** + +Prune volumes not used by at least one container + +## SEE ALSO +podman(1), podman-image-prune(1), podman-container-prune(1), podman-volume-prune(1) + +# HISTORY +February 2019, Originally compiled by Dan Walsh (dwalsh at redhat dot com) diff --git a/docs/podman-system.1.md b/docs/podman-system.1.md new file mode 100644 index 000000000..1b4bccdb9 --- /dev/null +++ b/docs/podman-system.1.md @@ -0,0 +1,20 @@ +% podman-system(1) + +## NAME +podman\-system - Manage podman + +## SYNOPSIS +**podman system** *subcommand* + +## DESCRIPTION +The system command allows you to manage the podman systems + +## COMMANDS + +| Command | Man Page | Description | +| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | +| info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | +| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused data | + +## SEE ALSO +podman diff --git a/docs/podman.1.md b/docs/podman.1.md index 6200a07f0..51ef00383 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -162,6 +162,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-start(1)](podman-start.1.md) | Starts one or more containers. | | [podman-stats(1)](podman-stats.1.md) | Display a live stream of one or more container's resource usage statistics. | | [podman-stop(1)](podman-stop.1.md) | Stop one or more running containers. | +| [podman-system(1)](podman-system.1.md) | Manage podman. | | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | | [podman-top(1)](podman-top.1.md) | Display the running processes of a container. | | [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. | diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index 07c786184..c73e98c95 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -407,3 +407,28 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha } return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) } + +// GetAllVolumes retrieves all the volumes +func (r *LocalRuntime) GetAllVolumes() ([]*libpod.Volume, error) { + return nil, libpod.ErrNotImplemented +} + +// RemoveVolume removes a volumes +func (r *LocalRuntime) RemoveVolume(ctx context.Context, v *libpod.Volume, force, prune bool) error { + return libpod.ErrNotImplemented +} + +// GetContainers retrieves all containers from the state +// Filters can be provided which will determine what containers are included in +// the output. Multiple filters are handled by ANDing their output, so only +// containers matching all filters are returned +func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libpod.Container, error) { + return nil, libpod.ErrNotImplemented +} + +// RemoveContainer removes the given container +// If force is specified, the container will be stopped first +// Otherwise, RemoveContainer will return an error if the container is running +func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force bool) error { + return libpod.ErrNotImplemented +} diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index 637c04e0a..a50c27dda 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -39,4 +39,10 @@ var _ = Describe("Podman Info", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman system info json output", func() { + session := podmanTest.Podman([]string{"system", "info", "--format=json"}) + session.Wait() + Expect(session.ExitCode()).To(Equal(0)) + + }) }) diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 81fb82b20..74cdc126f 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -88,4 +88,17 @@ var _ = Describe("Podman rm", func() { Expect(len(images.OutputToStringArray())).To(Equal(0)) }) + It("podman system image prune unused images", func() { + SkipIfRemote() + podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") + prune := podmanTest.Podman([]string{"system", "prune", "-a", "--force"}) + prune.WaitWithDefaultTimeout() + Expect(prune.ExitCode()).To(Equal(0)) + + images := podmanTest.Podman([]string{"images", "-a"}) + images.WaitWithDefaultTimeout() + // all images are unused, so they all should be deleted! + Expect(len(images.OutputToStringArray())).To(Equal(0)) + }) + }) diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index 008acc2a2..802f3fc4a 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -63,4 +63,34 @@ var _ = Describe("Podman volume prune", func() { podmanTest.Cleanup() }) + + It("podman system prune --volume", func() { + session := podmanTest.Podman([]string{"volume", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "myvol:/myvol", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(4)) + + session = podmanTest.Podman([]string{"system", "prune", "--force", "--volumes"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(2)) + + podmanTest.Cleanup() + }) }) diff --git a/transfer.md b/transfer.md index c0d309575..af7904e5f 100644 --- a/transfer.md +++ b/transfer.md @@ -76,7 +76,10 @@ There are other equivalents for these tools | `docker volume ls` | [`podman volume ls`](./docs/podman-volume-ls.1.md) | | `docker volume prune` | [`podman volume prune`](./docs/podman-volume-prune.1.md) | | `docker volume rm` | [`podman volume rm`](./docs/podman-volume-rm.1.md) | -| `docker wait` | [`podman wait`](./docs/podman-wait.1.md) | +| `docker system` | [`podman system`](./docs/podman-system.1.md) | +| `docker system prune` | [`podman system prune`](./docs/podman-system-prune.1.md) | +| `docker system info` | [`podman system info`](./docs/podman-system-info.1.md) | +| `docker wait` | [`podman wait`](./docs/podman-wait.1.md) | **** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/podman-cp.1.md) for more information. @@ -95,7 +98,6 @@ Those Docker commands currently do not have equivalents in `podman`: | `docker service` || | `docker stack` || | `docker swarm` | podman does not support swarm. We support Kubernetes for orchestration using [CRI-O](https://github.com/kubernetes-sigs/cri-o).| -| `docker system` || | `docker volume` | podman currently supports file volumes. Future enhancement planned to support Docker Volumes Plugins ## Missing commands in Docker -- cgit v1.2.3-54-g00ecf