Add ability to prune containers and images

Allow user to prune unused/unnamed images, the layer images from building,
via podman rmi --prune.

Allow user to prune stopped/exiuted containers via podman rm --prune.

This should resolve #1910

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude 2018-12-01 09:49:46 -06:00
parent 75b19ca8ab
commit e037427035
13 changed files with 351 additions and 6 deletions

View file

@ -22,7 +22,7 @@ var (
mountCommand,
pauseCommand,
portCommand,
// pruneCommand,
pruneContainersCommand,
refreshCommand,
restartCommand,
restoreCommand,

View file

@ -0,0 +1,74 @@
package main
import (
"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 (
pruneContainersDescription = `
podman container prune
Removes all exited containers
`
pruneContainersCommand = cli.Command{
Name: "prune",
Usage: "Remove all stopped containers",
Description: pruneContainersDescription,
Action: pruneContainersCmd,
OnUsageError: usageErrorHandler,
}
)
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)
filter := func(c *libpod.Container) bool {
state, _ := c.State()
if state == libpod.ContainerStateStopped || (state == libpod.ContainerStateExited && err == nil && c.PodID() == "") {
return true
}
return false
}
delContainers, err := runtime.GetContainers(filter)
if err != nil {
return err
}
if len(delContainers) < 1 {
return nil
}
for _, container := range delContainers {
con := container
f := func() error {
return runtime.RemoveContainer(ctx, con, c.Bool("force"))
}
deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{
ContainerID: con.ID(),
ParallelFunc: f,
})
}
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)
}

View file

@ -13,7 +13,7 @@ var (
inspectCommand,
loadCommand,
lsImagesCommand,
// pruneCommand,
pruneImagesCommand,
pullCommand,
pushCommand,
rmImageCommand,

View file

@ -0,0 +1,34 @@
package main
import (
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var (
pruneImagesDescription = `
podman image prune
Removes all unnamed images from local storage
`
pruneImagesCommand = cli.Command{
Name: "prune",
Usage: "Remove unused images",
Description: pruneImagesDescription,
Action: pruneImagesCmd,
OnUsageError: usageErrorHandler,
}
)
func pruneImagesCmd(c *cli.Context) error {
runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
defer runtime.Shutdown(false)
return shared.Prune(runtime.ImageRuntime())
}

View file

@ -0,0 +1,24 @@
package shared
import (
"fmt"
"github.com/pkg/errors"
"github.com/containers/libpod/libpod/image"
)
// Prune removes all unnamed and unused images from the local store
func Prune(ir *image.Runtime) error {
pruneImages, err := ir.GetPruneImages()
if err != nil {
return err
}
for _, i := range pruneImages {
if err := i.Remove(true); err != nil {
return errors.Wrapf(err, "failed to remove %s", i.ID())
}
fmt.Println(i.ID())
}
return nil
}

View file

@ -871,6 +871,7 @@ _podman_container() {
create
diff
exec
exists
export
inspect
kill
@ -879,6 +880,7 @@ _podman_container() {
mount
pause
port
prune
refresh
restart
restore
@ -1210,11 +1212,13 @@ _podman_image() {
"
subcommands="
build
exists
history
import
inspect
load
ls
prune
pull
push
rm
@ -2227,6 +2231,26 @@ _podman_container_runlabel() {
esac
}
_podman_images_prune() {
local options_with_args="
"
local boolean_options="
-h
--help
"
}
_podman_container_prune() {
local options_with_args="
"
local boolean_options="
-h
--help
"
}
_podman_container_exists() {
local options_with_args="
"

View file

@ -0,0 +1,31 @@
% PODMAN(1) Podman Man Pages
% Brent Baude
% December 2018
# NAME
podman-container-prune - Remove all stopped containers
# SYNOPSIS
**podman container prune**
[**-h**|**--help**]
# DESCRIPTION
**podman container prune** removes all stopped containers from local storage.
## Examples ##
Remove all stopped containers from local storage
```
$ sudo podman container prune
878392adf2e6c5c9bb1fc19b69d37d2e98c8abf9d539c0bce4b15b46bbcce471
37664467fbe3618bf9479c34393ac29c02696675addf1750f9e346581636cde7
ed0c6468b8e1cb641b4621d1fe30cb477e1fefc5c0bceb66feaf2f7cb50e5962
6ac6c8f0067b7a4682e6b8e18902665b57d1a0e07e885d9abcd382232a543ccd
fff1c5b6c3631746055ec40598ce8ecaa4b82aef122f9e3a85b03b55c0d06c23
602d343cd47e7cb3dfc808282a9900a3e4555747787ec6723bb68cedab8384d5
```
## SEE ALSO
podman(1), podman-ps
# HISTORY
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)

View file

@ -29,6 +29,7 @@ The container command allows you to manage containers
| mount | [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
| pause | [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. |
| port | [podman-port(1)](podman-port.1.md) | List port mappings for the container. |
| prune | [podman-container-prune(1)](podman-container-prune.1.md) | Remove all stopped containers from local storage |
| refresh | [podman-refresh(1)](podman-container-refresh.1.md) | Refresh the state of all containers |
| restart | [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
| restore | [podman-container-restore(1)](podman-container-restore.1.md) | Restores one or more containers from a checkpoint. |

View file

@ -0,0 +1,32 @@
% PODMAN(1) Podman Man Pages
% Brent Baude
% December 2018
# NAME
podman-image-prune - Remove all unused images
# SYNOPSIS
**podman image prune**
[**-h**|**--help**]
# DESCRIPTION
**podman image prune** removes all unused images from local storage. An unused image
is defined as an image that does not have any containers based on it.
## Examples ##
Remove all unused images from local storage
```
$ sudo podman image prune
f3e20dc537fb04cb51672a5cb6fdf2292e61d411315549391a0d1f64e4e3097e
324a7a3b2e0135f4226ffdd473e4099fd9e477a74230cdc35de69e84c0f9d907
6125002719feb1ddf3030acab1df6156da7ce0e78e571e9b6e9c250424d6220c
91e732da5657264c6f4641b8d0c4001c218ae6c1adb9dcef33ad00cafd37d8b6
e4e5109420323221f170627c138817770fb64832da7d8fe2babd863148287fca
77a57fa8285e9656dbb7b23d9efa837a106957409ddd702f995605af27a45ebe
```
## SEE ALSO
podman(1), podman-images
# HISTORY
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)

View file

@ -21,6 +21,7 @@ The image command allows you to manage images
| load | [podman-load(1)](podman-load.1.md) | Load an image from the docker archive. |
| ls | [podman-images(1)](podman-images.1.md) | Prints out information about images. |
| pull | [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. |
| prune| [podman-container-prune(1)](podman-container-prune.1.md) | Removed all unused images from the local store |
| push | [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. |
| rm | [podman-rm(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
| save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. |

View file

@ -19,15 +19,25 @@ Remove all images in the local storage.
This option will cause podman to remove all containers that are using the image before removing the image from the system.
## EXAMPLE
podman rmi imageID
Remove an image by its short ID
```
podman rmi c0ed59d05ff7
```
Remove an image and its associated containers.
```
podman rmi --force imageID
````
podman rmi imageID1 imageID2 imageID3
Remove multiple images by their shortened IDs.
```
podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7
```
Remove all images and containers.
```
podman rmi -a -f
```
## SEE ALSO
podman(1)

26
libpod/image/prune.go Normal file
View file

@ -0,0 +1,26 @@
package image
// GetPruneImages returns a slice of images that have no names/unused
func (ir *Runtime) GetPruneImages() ([]*Image, error) {
var (
unamedImages []*Image
)
allImages, err := ir.GetImages()
if err != nil {
return nil, err
}
for _, i := range allImages {
if len(i.Names()) == 0 {
unamedImages = append(unamedImages, i)
continue
}
containers, err := i.Containers()
if err != nil {
return nil, err
}
if len(containers) < 1 {
unamedImages = append(unamedImages, i)
}
}
return unamedImages, nil
}

88
test/e2e/prune_test.go Normal file
View file

@ -0,0 +1,88 @@
package integration
import (
"fmt"
"os"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var pruneImage = `
FROM alpine:latest
LABEL RUN podman --version
RUN apk update
RUN apk add bash`
var _ = Describe("Podman rm", func() {
var (
tempdir string
err error
podmanTest *PodmanTestIntegration
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.RestoreAllArtifacts()
})
AfterEach(func() {
podmanTest.Cleanup()
f := CurrentGinkgoTestDescription()
timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
GinkgoWriter.Write([]byte(timedResult))
})
It("podman container prune containers", func() {
top := podmanTest.RunTopContainer("")
top.WaitWithDefaultTimeout()
Expect(top.ExitCode()).To(Equal(0))
session := podmanTest.Podman([]string{"run", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
prune := podmanTest.Podman([]string{"container", "prune"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
})
It("podman image prune none images", func() {
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
none := podmanTest.Podman([]string{"images", "-a"})
none.WaitWithDefaultTimeout()
Expect(none.ExitCode()).To(Equal(0))
hasNone, _ := none.GrepString("<none>")
Expect(hasNone).To(BeTrue())
prune := podmanTest.Podman([]string{"image", "prune"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(Equal(0))
after := podmanTest.Podman([]string{"images", "-a"})
after.WaitWithDefaultTimeout()
Expect(none.ExitCode()).To(Equal(0))
hasNoneAfter, _ := after.GrepString("<none>")
Expect(hasNoneAfter).To(BeFalse())
})
It("podman image prune unused images", func() {
prune := podmanTest.Podman([]string{"image", "prune"})
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))
})
})