And system prune feature for v2.

Adds podman system prune for v2.
Refactoring for code reuse from pods containers images and volume prune.
Adds and enables testcases to support the added feature.

Signed-off-by: Sujil02 <sushah@redhat.com>
This commit is contained in:
Sujil02
2020-04-29 22:41:56 -04:00
parent 2f3762eb91
commit b94862171b
15 changed files with 381 additions and 51 deletions

View File

@ -43,7 +43,6 @@ func init() {
func prune(cmd *cobra.Command, args []string) error { func prune(cmd *cobra.Command, args []string) error {
var ( var (
errs utils.OutputErrors
pruneOptions = entities.ContainerPruneOptions{} pruneOptions = entities.ContainerPruneOptions{}
) )
if len(args) > 0 { if len(args) > 0 {
@ -76,11 +75,5 @@ func prune(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
for k := range responses.ID { return utils.PrintContainerPruneResults(responses)
fmt.Println(k)
}
for _, v := range responses.Err {
errs = append(errs, v)
}
return errs.PrintErrors()
} }

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -71,17 +72,5 @@ Are you sure you want to continue? [y/N] `)
return err return err
} }
for _, i := range results.Report.Id { return utils.PrintImagePruneResults(results)
fmt.Println(i)
}
for _, e := range results.Report.Err {
fmt.Fprint(os.Stderr, e.Error()+"\n")
}
if results.Size > 0 {
fmt.Fprintf(os.Stdout, "Size: %d\n", results.Size)
}
return nil
} }

View File

@ -41,9 +41,6 @@ func init() {
} }
func prune(cmd *cobra.Command, args []string) error { func prune(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if len(args) > 0 { if len(args) > 0 {
return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) return errors.Errorf("`%s` takes no arguments", cmd.CommandPath())
} }
@ -60,16 +57,8 @@ func prune(cmd *cobra.Command, args []string) error {
} }
} }
responses, err := registry.ContainerEngine().PodPrune(context.Background(), pruneOptions) responses, err := registry.ContainerEngine().PodPrune(context.Background(), pruneOptions)
if err != nil { if err != nil {
return err return err
} }
for _, r := range responses { return utils.PrintPodPruneResults(responses)
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
} }

103
cmd/podman/system/prune.go Normal file
View File

@ -0,0 +1,103 @@
package system
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
pruneOptions = entities.SystemPruneOptions{}
pruneDescription = fmt.Sprintf(`
podman system prune
Remove unused data
`)
pruneCommand = &cobra.Command{
Use: "prune [flags]",
Short: "Remove unused data",
Args: validate.NoArgs,
Long: pruneDescription,
RunE: prune,
Example: `podman system prune`,
}
force bool
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: pruneCommand,
Parent: systemCmd,
})
flags := pruneCommand.Flags()
flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false")
flags.BoolVarP(&pruneOptions.All, "all", "a", false, "Remove all unused data")
flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes")
}
func prune(cmd *cobra.Command, args []string) error {
// Prompt for confirmation if --force is not set
if !force {
reader := bufio.NewReader(os.Stdin)
volumeString := ""
if pruneOptions.Volume {
volumeString = `
- all volumes not used by at least one container`
}
fmt.Printf(`
WARNING! This will remove:
- all stopped containers%s
- all stopped pods
- all dangling images
- all build cache
Are you sure you want to continue? [y/N] `, volumeString)
answer, err := reader.ReadString('\n')
if err != nil {
return errors.Wrapf(err, "error reading input")
}
if strings.ToLower(answer)[0] != 'y' {
return nil
}
}
// TODO: support for filters in system prune
response, err := registry.ContainerEngine().SystemPrune(context.Background(), pruneOptions)
if err != nil {
return err
}
// Print pod prune results
fmt.Println("Deleted Pods")
err = utils.PrintPodPruneResults(response.PodPruneReport)
if err != nil {
return err
}
// Print container prune results
fmt.Println("Deleted Containers")
err = utils.PrintContainerPruneResults(response.ContainerPruneReport)
if err != nil {
return err
}
// Print Volume prune results
if pruneOptions.Volume {
fmt.Println("Deleted Volumes")
err = utils.PrintVolumePruneResults(response.VolumePruneReport)
if err != nil {
return err
}
}
// Print Images prune results
fmt.Println("Deleted Images")
return utils.PrintImagePruneResults(response.ImagePruneReport)
}

View File

@ -1,6 +1,11 @@
package utils package utils
import "os" import (
"fmt"
"os"
"github.com/containers/libpod/pkg/domain/entities"
)
// IsDir returns true if the specified path refers to a directory. // IsDir returns true if the specified path refers to a directory.
func IsDir(path string) bool { func IsDir(path string) bool {
@ -20,3 +25,51 @@ func FileExists(path string) bool {
} }
return !file.IsDir() return !file.IsDir()
} }
func PrintPodPruneResults(podPruneReports []*entities.PodPruneReport) error {
var errs OutputErrors
for _, r := range podPruneReports {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}
func PrintContainerPruneResults(containerPruneReport *entities.ContainerPruneReport) error {
var errs OutputErrors
for k := range containerPruneReport.ID {
fmt.Println(k)
}
for _, v := range containerPruneReport.Err {
errs = append(errs, v)
}
return errs.PrintErrors()
}
func PrintVolumePruneResults(volumePruneReport []*entities.VolumePruneReport) error {
var errs OutputErrors
for _, r := range volumePruneReport {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}
func PrintImagePruneResults(imagePruneReport *entities.ImagePruneReport) error {
for _, i := range imagePruneReport.Report.Id {
fmt.Println(i)
}
for _, e := range imagePruneReport.Report.Err {
fmt.Fprint(os.Stderr, e.Error()+"\n")
}
if imagePruneReport.Size > 0 {
fmt.Fprintf(os.Stdout, "Size: %d\n", imagePruneReport.Size)
}
return nil
}

View File

@ -44,9 +44,6 @@ func init() {
} }
func prune(cmd *cobra.Command, args []string) error { func prune(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
// Prompt for confirmation if --force is not set // Prompt for confirmation if --force is not set
if !pruneOptions.Force { if !pruneOptions.Force {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
@ -64,12 +61,5 @@ func prune(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
for _, r := range responses { return utils.PrintVolumePruneResults(responses)
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
} }

View File

@ -42,6 +42,7 @@ type ContainerEngine interface {
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error Events(ctx context.Context, opts EventsOptions) error
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error) Info(ctx context.Context) (*define.Info, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)

View File

@ -184,6 +184,10 @@ func (ic *ContainerEngine) ContainerPrune(ctx context.Context, options entities.
filterFuncs = append(filterFuncs, generatedFunc) filterFuncs = append(filterFuncs, generatedFunc)
} }
} }
return ic.pruneContainersHelper(ctx, filterFuncs)
}
func (ic *ContainerEngine) pruneContainersHelper(ctx context.Context, filterFuncs []libpod.ContainerFilter) (*entities.ContainerPruneReport, error) {
prunedContainers, pruneErrors, err := ic.Libpod.PruneContainers(filterFuncs) prunedContainers, pruneErrors, err := ic.Libpod.PruneContainers(filterFuncs)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -36,7 +36,11 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
} }
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) return ir.pruneImagesHelper(ctx, opts.All, opts.Filter)
}
func (ir *ImageEngine) pruneImagesHelper(ctx context.Context, all bool, filters []string) (*entities.ImagePruneReport, error) {
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, all, filters)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -243,6 +243,10 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
} }
func (ic *ContainerEngine) PodPrune(ctx context.Context, options entities.PodPruneOptions) ([]*entities.PodPruneReport, error) { func (ic *ContainerEngine) PodPrune(ctx context.Context, options entities.PodPruneOptions) ([]*entities.PodPruneReport, error) {
return ic.prunePodHelper(ctx)
}
func (ic *ContainerEngine) prunePodHelper(ctx context.Context) ([]*entities.PodPruneReport, error) {
var ( var (
reports []*entities.PodPruneReport reports []*entities.PodPruneReport
) )

View File

@ -175,3 +175,41 @@ func setUMask() { // nolint:deadcode,unused
func checkInput() error { // nolint:deadcode,unused func checkInput() error { // nolint:deadcode,unused
return nil return nil
} }
// SystemPrune removes unsed data from the system. Pruning pods, containers, volumes and images.
func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
var systemPruneReport = new(entities.SystemPruneReport)
podPruneReport, err := ic.prunePodHelper(ctx)
if err != nil {
return nil, err
}
systemPruneReport.PodPruneReport = podPruneReport
containerPruneReport, err := ic.pruneContainersHelper(ctx, nil)
if err != nil {
return nil, err
}
systemPruneReport.ContainerPruneReport = containerPruneReport
results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, nil)
if err != nil {
return nil, err
}
report := entities.ImagePruneReport{
Report: entities.Report{
Id: results,
Err: nil,
},
}
systemPruneReport.ImagePruneReport = &report
if options.Volume {
volumePruneReport, err := ic.pruneVolumesHelper(ctx)
if err != nil {
return nil, err
}
systemPruneReport.VolumePruneReport = volumePruneReport
}
return systemPruneReport, nil
}

View File

@ -1,5 +1,3 @@
// +build ABISupport
package abi package abi
import ( import (
@ -113,6 +111,10 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
} }
func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
return ic.pruneVolumesHelper(ctx)
}
func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context) ([]*entities.VolumePruneReport, error) {
var ( var (
reports []*entities.VolumePruneReport reports []*entities.VolumePruneReport
) )

View File

@ -3,6 +3,7 @@ package tunnel
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/bindings/system" "github.com/containers/libpod/pkg/bindings/system"
@ -21,3 +22,9 @@ func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceO
func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error { func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error {
panic(errors.New("rootless engine mode is not supported when tunneling")) panic(errors.New("rootless engine mode is not supported when tunneling"))
} }
// SystemPrune prunes unused data from the system.
func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
fmt.Println("in tunnel")
return system.Prune(ic.ClientCxt, &options.All, &options.Volume)
}

View File

@ -148,7 +148,6 @@ var _ = Describe("Podman prune", func() {
It("podman system image prune unused images", func() { It("podman system image prune unused images", func() {
SkipIfRemote() SkipIfRemote()
Skip(v2fail)
podmanTest.RestoreAllArtifacts() podmanTest.RestoreAllArtifacts()
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
prune := podmanTest.PodmanNoCache([]string{"system", "prune", "-a", "--force"}) prune := podmanTest.PodmanNoCache([]string{"system", "prune", "-a", "--force"})
@ -162,7 +161,6 @@ var _ = Describe("Podman prune", func() {
}) })
It("podman system prune pods", func() { It("podman system prune pods", func() {
Skip(v2fail)
session := podmanTest.Podman([]string{"pod", "create"}) session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
@ -193,4 +191,160 @@ var _ = Describe("Podman prune", func() {
Expect(pods.ExitCode()).To(Equal(0)) Expect(pods.ExitCode()).To(Equal(0))
Expect(len(pods.OutputToStringArray())).To(Equal(2)) Expect(len(pods.OutputToStringArray())).To(Equal(2))
}) })
It("podman system prune - pod,container stopped", func() {
session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Start and stop a pod to get it in exited state.
session = podmanTest.Podman([]string{"pod", "start", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "stop", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Create a container. This container should be pruned.
create := podmanTest.Podman([]string{"create", "--name", "test", BB})
create.WaitWithDefaultTimeout()
Expect(create.ExitCode()).To(Equal(0))
prune := podmanTest.Podman([]string{"system", "prune", "-f"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(Equal(0))
pods := podmanTest.Podman([]string{"pod", "ps"})
pods.WaitWithDefaultTimeout()
Expect(pods.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfPods()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
})
It("podman system prune with running, exited pod and volume prune set true", func() {
// Start and stop a pod to get it in exited state.
session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "start", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "stop", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Start a pod and leave it running
session = podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "start", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Number of pod should be 2. One exited one running.
Expect(podmanTest.NumberOfPods()).To(Equal(2))
// Create a container. This container should be pruned.
_, ec, _ := podmanTest.RunLsContainer("test1")
Expect(ec).To(Equal(0))
// Number of containers should be three now.
// Two as pods infra container and one newly created.
Expect(podmanTest.NumberOfContainers()).To(Equal(3))
// image list current count should not be pruned if all flag isnt enabled
session = podmanTest.Podman([]string{"images"})
session.WaitWithDefaultTimeout()
numberOfImages := len(session.OutputToStringArray())
// Adding unused volume should be pruned
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(3))
session = podmanTest.Podman([]string{"system", "prune", "--force", "--volumes"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Volumes should be pruned.
session = podmanTest.Podman([]string{"volume", "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(0))
// One Pod should not be pruned as it was running
Expect(podmanTest.NumberOfPods()).To(Equal(1))
// Running pods infra container should not be pruned.
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
// Image should not be pruned and number should be same.
images := podmanTest.Podman([]string{"images"})
images.WaitWithDefaultTimeout()
Expect(len(images.OutputToStringArray())).To(Equal(numberOfImages))
})
It("podman system prune - with dangling images true", func() {
session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Start and stop a pod to get it in exited state.
session = podmanTest.Podman([]string{"pod", "start", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "stop", "-l"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// Create a container. This container should be pruned.
create := podmanTest.Podman([]string{"create", "--name", "test", BB})
create.WaitWithDefaultTimeout()
Expect(create.ExitCode()).To(Equal(0))
// Adding images should be pruned
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
// Adding unused volume should not be pruned as volumes not set
session = podmanTest.Podman([]string{"volume", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
prune := podmanTest.Podman([]string{"system", "prune", "-f", "-a"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(Equal(0))
pods := podmanTest.Podman([]string{"pod", "ps"})
pods.WaitWithDefaultTimeout()
Expect(pods.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfPods()).To(Equal(0))
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
// Volumes should not be pruned
session = podmanTest.Podman([]string{"volume", "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(2))
images := podmanTest.PodmanNoCache([]string{"images", "-aq"})
images.WaitWithDefaultTimeout()
// all images are unused, so they all should be deleted!
Expect(len(images.OutputToStringArray())).To(Equal(0))
})
}) })

View File

@ -65,7 +65,6 @@ var _ = Describe("Podman volume prune", func() {
}) })
It("podman system prune --volume", func() { It("podman system prune --volume", func() {
Skip(v2fail)
session := podmanTest.Podman([]string{"volume", "create"}) session := podmanTest.Podman([]string{"volume", "create"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))