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 <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2019-02-05 10:41:55 -08:00
parent 650e242aa9
commit 74d984e056
No known key found for this signature in database
GPG Key ID: A2DF901DABE2C028
21 changed files with 389 additions and 49 deletions

View File

@ -53,6 +53,10 @@ func getImageSubCommands() []cli.Command {
} }
} }
func getSystemSubCommands() []cli.Command {
return []cli.Command{infoCommand}
}
func getContainerSubCommands() []cli.Command { func getContainerSubCommands() []cli.Command {
return []cli.Command{ return []cli.Command{
attachCommand, attachCommand,

View File

@ -16,6 +16,10 @@ func getContainerSubCommands() []cli.Command {
return []cli.Command{} return []cli.Command{}
} }
func getSystemSubCommands() []cli.Command {
return []cli.Command{}
}
func getMainAppFlags() []cli.Flag { func getMainAppFlags() []cli.Flag {
return []cli.Flag{} return []cli.Flag{}
} }

View File

@ -1,9 +1,11 @@
package main package main
import ( import (
"github.com/containers/libpod/cmd/podman/libpodruntime" "context"
"github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/adapter"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -25,20 +27,11 @@ var (
} }
) )
func pruneContainersCmd(c *cli.Context) error { func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force bool) error {
var ( var deleteFuncs []shared.ParallelWorkerInput
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)
filter := func(c *libpod.Container) bool { filter := func(c *libpod.Container) bool {
state, _ := c.State() state, err := c.State()
if state == libpod.ContainerStateStopped || (state == libpod.ContainerStateExited && err == nil && c.PodID() == "") { if state == libpod.ContainerStateStopped || (state == libpod.ContainerStateExited && err == nil && c.PodID() == "") {
return true return true
} }
@ -54,7 +47,7 @@ func pruneContainersCmd(c *cli.Context) error {
for _, container := range delContainers { for _, container := range delContainers {
con := container con := container
f := func() error { f := func() error {
return runtime.RemoveContainer(ctx, con, c.Bool("force")) return runtime.RemoveContainer(ctx, con, force)
} }
deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{
@ -62,13 +55,23 @@ func pruneContainersCmd(c *cli.Context) error {
ParallelFunc: f, 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") maxWorkers := shared.Parallelize("rm")
if c.GlobalIsSet("max-workers") { if c.GlobalIsSet("max-workers") {
maxWorkers = c.GlobalInt("max-workers") maxWorkers = c.GlobalInt("max-workers")
} }
logrus.Debugf("Setting maximum workers to %d", maxWorkers) logrus.Debugf("Setting maximum workers to %d", maxWorkers)
// Run the parallel funcs return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force"))
deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs)
return printParallelOutput(deleteErrors, errCount)
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/adapter"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"

View File

@ -97,6 +97,7 @@ func main() {
inspectCommand, inspectCommand,
pullCommand, pullCommand,
rmiCommand, rmiCommand,
systemCommand,
tagCommand, tagCommand,
versionCommand, versionCommand,
} }

View File

@ -131,6 +131,7 @@ func runCmd(c *cli.Context) error {
ctrExitCode, err := readExitFile(runtime.GetConfig().TmpDir, ctr.ID()) ctrExitCode, err := readExitFile(runtime.GetConfig().TmpDir, ctr.ID())
if err != nil { if err != nil {
logrus.Errorf("Cannot get exit code: %v", err) logrus.Errorf("Cannot get exit code: %v", err)
exitCode = 127
} else { } else {
exitCode = ctrExitCode exitCode = ctrExitCode
} }

29
cmd/podman/system.go Normal file
View File

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

107
cmd/podman/system_prune.go Normal file
View File

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

View File

@ -2,12 +2,13 @@ package main
import ( import (
"bufio" "bufio"
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/adapter"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -37,35 +38,9 @@ var volumePruneCommand = cli.Command{
UseShortOptionHandling: true, UseShortOptionHandling: true,
} }
func volumePruneCmd(c *cli.Context) error { func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error {
var lastError error var lastError error
if err := validateFlags(c, volumePruneFlags); err != nil {
return err
}
runtime, err := libpodruntime.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)
fmt.Println("WARNING! This will remove all volumes not used by at least one container.")
fmt.Print("Are you sure you want to continue? [y/N] ")
ans, err := reader.ReadString('\n')
if err != nil {
return errors.Wrapf(err, "error reading input")
}
if strings.ToLower(ans)[0] != 'y' {
return nil
}
}
volumes, err := runtime.GetAllVolumes() volumes, err := runtime.GetAllVolumes()
if err != nil { if err != nil {
return err return err
@ -84,3 +59,32 @@ func volumePruneCmd(c *cli.Context) error {
} }
return lastError return lastError
} }
func volumePruneCmd(c *cli.Context) error {
if err := validateFlags(c, volumePruneFlags); err != nil {
return err
}
runtime, err := adapter.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.Shutdown(false)
// Prompt for confirmation if --force is not set
if !c.Bool("force") {
reader := bufio.NewReader(os.Stdin)
fmt.Println("WARNING! This will remove all volumes not used by at least one container.")
fmt.Print("Are you sure you want to continue? [y/N] ")
ans, err := reader.ReadString('\n')
if err != nil {
return errors.Wrapf(err, "error reading input")
}
if strings.ToLower(ans)[0] != 'y' {
return nil
}
}
return volumePrune(runtime, getContext())
}

View File

@ -66,6 +66,7 @@
| [podman-start(1)](/docs/podman-start.1.md) | Starts one or more containers | [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-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-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-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-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)| | [podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)|

View File

@ -979,6 +979,51 @@ _podman_container() {
esac 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() { _podman_commit() {
local options_with_args=" local options_with_args="
--author --author
@ -2482,6 +2527,11 @@ _podman_container_prune() {
-h -h
--help --help
" "
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
} }
_podman_container_exists() { _podman_container_exists() {

View File

@ -0,0 +1 @@
.so man1/podman-info.1

View File

@ -1,9 +1,9 @@
% podman-version(1) % podman-info(1)
## NAME ## NAME
podman\-system\-info - Display system information
podman\-info - Display system information podman\-info - Display system information
## SYNOPSIS ## SYNOPSIS
**podman info** [*options*] **podman info** [*options*]

View File

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

20
docs/podman-system.1.md Normal file
View File

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

View File

@ -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-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-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-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-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-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. | | [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. |

View File

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

View File

@ -39,4 +39,10 @@ var _ = Describe("Podman Info", func() {
Expect(session.ExitCode()).To(Equal(0)) 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))
})
}) })

View File

@ -88,4 +88,17 @@ var _ = Describe("Podman rm", func() {
Expect(len(images.OutputToStringArray())).To(Equal(0)) 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))
})
}) })

View File

@ -63,4 +63,34 @@ var _ = Describe("Podman volume prune", func() {
podmanTest.Cleanup() 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()
})
}) })

View File

@ -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 ls` | [`podman volume ls`](./docs/podman-volume-ls.1.md) |
| `docker volume prune` | [`podman volume prune`](./docs/podman-volume-prune.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 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. **** 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 service` ||
| `docker stack` || | `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 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 | `docker volume` | podman currently supports file volumes. Future enhancement planned to support Docker Volumes Plugins
## Missing commands in Docker ## Missing commands in Docker