mirror of
https://github.com/containers/podman.git
synced 2025-06-20 00:51:16 +08:00
Add volume filters to system prune
This change was missed in pull/8689. Now that volume pruneing supports filters system pruneing can pass its filters down to the volume pruneing. Additionally this change adds tests for the following components * podman system prune subcommand with `--volumes` & `--filter` options * apiv2 api tests for `/system/` and `/libpod/system` endpoints Relates to #8453, #8672 Signed-off-by: Baron Lenardson <lenardson.baron@gmail.com>
This commit is contained in:
@ -4,7 +4,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -12,8 +11,8 @@ import (
|
|||||||
"github.com/containers/podman/v2/cmd/podman/registry"
|
"github.com/containers/podman/v2/cmd/podman/registry"
|
||||||
"github.com/containers/podman/v2/cmd/podman/utils"
|
"github.com/containers/podman/v2/cmd/podman/utils"
|
||||||
"github.com/containers/podman/v2/cmd/podman/validate"
|
"github.com/containers/podman/v2/cmd/podman/validate"
|
||||||
|
lpfilters "github.com/containers/podman/v2/libpod/filters"
|
||||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,6 +54,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func prune(cmd *cobra.Command, args []string) error {
|
func prune(cmd *cobra.Command, args []string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
// Prompt for confirmation if --force is not set
|
// Prompt for confirmation if --force is not set
|
||||||
if !force {
|
if !force {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
@ -79,16 +80,11 @@ Are you sure you want to continue? [y/N] `, volumeString)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneOptions.ContainerPruneOptions = entities.ContainerPruneOptions{}
|
pruneOptions.Filters, err = lpfilters.ParseFilterArgumentsIntoFilters(filters)
|
||||||
for _, f := range filters {
|
if err != nil {
|
||||||
t := strings.SplitN(f, "=", 2)
|
return err
|
||||||
pruneOptions.ContainerPruneOptions.Filters = make(url.Values)
|
|
||||||
if len(t) < 2 {
|
|
||||||
return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
|
|
||||||
}
|
}
|
||||||
pruneOptions.ContainerPruneOptions.Filters.Add(t[0], t[1])
|
|
||||||
}
|
|
||||||
// TODO: support for filters in system prune
|
|
||||||
response, err := registry.ContainerEngine().SystemPrune(context.Background(), pruneOptions)
|
response, err := registry.ContainerEngine().SystemPrune(context.Background(), pruneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
20
libpod/filters/helpers.go
Normal file
20
libpod/filters/helpers.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package lpfilters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseFilterArgumentsIntoFilters(filters []string) (url.Values, error) {
|
||||||
|
parsedFilters := make(url.Values)
|
||||||
|
for _, f := range filters {
|
||||||
|
t := strings.SplitN(f, "=", 2)
|
||||||
|
if len(t) < 2 {
|
||||||
|
return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
|
||||||
|
}
|
||||||
|
parsedFilters.Add(t[0], t[1])
|
||||||
|
}
|
||||||
|
return parsedFilters, nil
|
||||||
|
}
|
@ -158,4 +158,61 @@ var _ = Describe("Podman system", func() {
|
|||||||
// Volume should be pruned now as flag set true
|
// Volume should be pruned now as flag set true
|
||||||
Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(1))
|
Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman system prune running alpine container volume prune --filter", func() {
|
||||||
|
// Start a pod and leave it running
|
||||||
|
_, err := pods.Start(bt.conn, newpod, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Start and stop a container to enter in exited state.
|
||||||
|
var name = "top"
|
||||||
|
_, err = bt.RunTopContainer(&name, bindings.PFalse, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
err = containers.Stop(bt.conn, name, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Start second container and leave in running
|
||||||
|
var name2 = "top2"
|
||||||
|
_, err = bt.RunTopContainer(&name2, bindings.PFalse, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Adding an unused volume should work
|
||||||
|
_, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Adding an unused volume with label should work
|
||||||
|
_, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{Label: map[string]string{
|
||||||
|
"label1": "value1",
|
||||||
|
}}, nil)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
f := make(map[string][]string)
|
||||||
|
f["label"] = []string{"label1=idontmatch"}
|
||||||
|
|
||||||
|
options := new(system.PruneOptions).WithAll(true).WithVolumes(true).WithFilters(f)
|
||||||
|
systemPruneResponse, err := system.Prune(bt.conn, options)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0))
|
||||||
|
// TODO fix system filter handling so all components can handle filters
|
||||||
|
// This check **should** be "Equal(0)" since we are passing label
|
||||||
|
// filters however the Prune function doesn't seem to pass filters
|
||||||
|
// to each component.
|
||||||
|
Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
|
||||||
|
Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
|
||||||
|
To(BeNumerically(">", 0))
|
||||||
|
// Alpine image should not be pruned as used by running container
|
||||||
|
Expect(systemPruneResponse.ImagePruneReport.Report.Id).
|
||||||
|
ToNot(ContainElement("docker.io/library/alpine:latest"))
|
||||||
|
// Volume shouldn't be pruned because the PruneOptions filters doesn't match
|
||||||
|
Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0))
|
||||||
|
|
||||||
|
// Fix filter and re prune
|
||||||
|
f["label"] = []string{"label1=value1"}
|
||||||
|
options = new(system.PruneOptions).WithAll(true).WithVolumes(true).WithFilters(f)
|
||||||
|
systemPruneResponse, err = system.Prune(bt.conn, options)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
// Volume should be pruned because the PruneOptions filters now match
|
||||||
|
Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(1))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@ type ServiceOptions struct {
|
|||||||
type SystemPruneOptions struct {
|
type SystemPruneOptions struct {
|
||||||
All bool
|
All bool
|
||||||
Volume bool
|
Volume bool
|
||||||
ContainerPruneOptions
|
Filters map[string][]string `json:"filters" schema:"filters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemPruneReport provides report after system prune is executed.
|
// SystemPruneReport provides report after system prune is executed.
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -180,7 +181,12 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
|
|||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...)
|
systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...)
|
||||||
containerPruneReport, err := ic.ContainerPrune(ctx, options.ContainerPruneOptions)
|
|
||||||
|
// TODO: Figure out cleaner way to handle all of the different PruneOptions
|
||||||
|
containerPruneOptions := entities.ContainerPruneOptions{}
|
||||||
|
containerPruneOptions.Filters = (url.Values)(options.Filters)
|
||||||
|
|
||||||
|
containerPruneReport, err := ic.ContainerPrune(ctx, containerPruneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -217,7 +223,9 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
|
|||||||
systemPruneReport.ImagePruneReport.Report.Id = append(systemPruneReport.ImagePruneReport.Report.Id, results...)
|
systemPruneReport.ImagePruneReport.Report.Id = append(systemPruneReport.ImagePruneReport.Report.Id, results...)
|
||||||
}
|
}
|
||||||
if options.Volume {
|
if options.Volume {
|
||||||
volumePruneReport, err := ic.pruneVolumesHelper(ctx, nil)
|
volumePruneOptions := entities.VolumePruneOptions{}
|
||||||
|
volumePruneOptions.Filters = (url.Values)(options.Filters)
|
||||||
|
volumePruneReport, err := ic.VolumePrune(ctx, volumePruneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
|
|||||||
|
|
||||||
// SystemPrune prunes unused data from the system.
|
// SystemPrune prunes unused data from the system.
|
||||||
func (ic *ContainerEngine) SystemPrune(ctx context.Context, opts entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
|
func (ic *ContainerEngine) SystemPrune(ctx context.Context, opts entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
|
||||||
options := new(system.PruneOptions).WithAll(opts.All).WithVolumes(opts.Volume).WithFilters(opts.ContainerPruneOptions.Filters)
|
options := new(system.PruneOptions).WithAll(opts.All).WithVolumes(opts.Volume).WithFilters(opts.Filters)
|
||||||
return system.Prune(ic.ClientCxt, options)
|
return system.Prune(ic.ClientCxt, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
67
test/apiv2/45-system.at
Normal file
67
test/apiv2/45-system.at
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- sh -*-
|
||||||
|
#
|
||||||
|
# system related tests
|
||||||
|
#
|
||||||
|
|
||||||
|
## ensure system is clean
|
||||||
|
t POST 'libpod/system/prune?volumes=true&all=true' params='' 200
|
||||||
|
|
||||||
|
## podman system df
|
||||||
|
t GET system/df 200 '{"LayersSize":0,"Images":[],"Containers":[],"Volumes":[],"BuildCache":[],"BuilderSize":0}'
|
||||||
|
t GET libpod/system/df 200 '{"Images":[],"Containers":[],"Volumes":[]}'
|
||||||
|
|
||||||
|
# Create volume. We expect df to report this volume next invocation of system/df
|
||||||
|
t GET libpod/info 200
|
||||||
|
volumepath=$(jq -r ".store.volumePath" <<<"$output")
|
||||||
|
t POST libpod/volumes/create name=foo1 201 \
|
||||||
|
.Name=foo1 \
|
||||||
|
.Driver=local \
|
||||||
|
.Mountpoint=$volumepath/foo1/_data \
|
||||||
|
.CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \
|
||||||
|
.Labels={} \
|
||||||
|
.Options=null
|
||||||
|
|
||||||
|
t GET system/df 200 '.Volumes[0].Name=foo1'
|
||||||
|
|
||||||
|
t GET libpod/system/df 200 '.Volumes[0].VolumeName=foo1'
|
||||||
|
|
||||||
|
# Create two more volumes to test pruneing
|
||||||
|
t POST libpod/volumes/create \
|
||||||
|
'"Name":"foo2","Label":{"testlabel1":""},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \
|
||||||
|
.Name=foo2 \
|
||||||
|
.Driver=local \
|
||||||
|
.Mountpoint=$volumepath/foo2/_data \
|
||||||
|
.CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \
|
||||||
|
.Labels.testlabel1="" \
|
||||||
|
.Options.o=nodev,noexec
|
||||||
|
|
||||||
|
t POST libpod/volumes/create \
|
||||||
|
'"Name":"foo3","Label":{"testlabel1":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \
|
||||||
|
.Name=foo3 \
|
||||||
|
.Driver=local \
|
||||||
|
.Mountpoint=$volumepath/foo3/_data \
|
||||||
|
.CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \
|
||||||
|
.Labels.testlabel1=testonly \
|
||||||
|
.Options.o=nodev,noexec
|
||||||
|
|
||||||
|
t GET system/df 200 '.Volumes | length=3'
|
||||||
|
t GET libpod/system/df 200 '.Volumes | length=3'
|
||||||
|
|
||||||
|
# Prune volumes
|
||||||
|
|
||||||
|
# -G --data-urlencode 'volumes=true&filters={"label":["testlabel1=idontmatch"]}'
|
||||||
|
t POST 'libpod/system/prune?volumes=true&filters=%7B%22label%22:%5B%22testlabel1=idontmatch%22%5D%7D' params='' 200
|
||||||
|
|
||||||
|
# nothing should have been pruned
|
||||||
|
t GET system/df 200 '.Volumes | length=3'
|
||||||
|
t GET libpod/system/df 200 '.Volumes | length=3'
|
||||||
|
|
||||||
|
# -G --data-urlencode 'volumes=true&filters={"label":["testlabel1=testonly"]}'
|
||||||
|
# only foo3 should be pruned because of filter
|
||||||
|
t POST 'libpod/system/prune?volumes=true&filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D' params='' 200 .VolumePruneReport[0].Id=foo3
|
||||||
|
# only foo2 should be pruned because of filter
|
||||||
|
t POST 'libpod/system/prune?volumes=true&filters=%7B%22label%22:%5B%22testlabel1%22%5D%7D' params='' 200 .VolumePruneReport[0].Id=foo2
|
||||||
|
# foo1, the last remaining volume should be pruned without any filters applied
|
||||||
|
t POST 'libpod/system/prune?volumes=true' params='' 200 .VolumePruneReport[0].Id=foo1
|
||||||
|
|
||||||
|
# TODO add other system prune tests for pods / images
|
@ -349,4 +349,64 @@ var _ = Describe("Podman prune", func() {
|
|||||||
// all images are unused, so they all should be deleted!
|
// all images are unused, so they all should be deleted!
|
||||||
Expect(len(images.OutputToStringArray())).To(Equal(len(CACHE_IMAGES)))
|
Expect(len(images.OutputToStringArray())).To(Equal(len(CACHE_IMAGES)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman system prune --volumes --filter", func() {
|
||||||
|
session := podmanTest.Podman([]string{"volume", "create", "--label", "label1=value1", "myvol1"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv1", "myvol2"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv2", "myvol3"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1", "myvol4"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"create", "-v", "myvol5:/myvol5", ALPINE, "ls"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"create", "-v", "myvol6:/myvol6", 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(7))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"system", "prune", "--force", "--volumes", "--filter", "label=label1=value1"})
|
||||||
|
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(6))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"system", "prune", "--force", "--volumes", "--filter", "label=sharedlabel1=slv1"})
|
||||||
|
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(5))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"system", "prune", "--force", "--volumes", "--filter", "label=sharedlabel1"})
|
||||||
|
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(3))
|
||||||
|
|
||||||
|
podmanTest.Cleanup()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user