From b90f7f90952f16e0c1b05e8f750b56bb43711b5e Mon Sep 17 00:00:00 2001
From: Baron Lenardson <lenardson.baron@gmail.com>
Date: Tue, 22 Dec 2020 20:02:08 -0600
Subject: Rework pruning to report reclaimed space

This change adds code to report the reclaimed space after a prune.
Reclaimed space from volumes, images, and containers is recorded
during the prune call in a PruneReport struct. These structs are
collected into a slice during a system prune and processed afterwards
to calculate the total reclaimed space.

Closes #8658

Signed-off-by: Baron Lenardson <lenardson.baron@gmail.com>
---
 libpod/image/prune.go    | 23 ++++++++++++++++++-----
 libpod/runtime_ctr.go    | 27 ++++++++++++++++-----------
 libpod/runtime_volume.go | 25 ++++++++++++++++++-------
 libpod/volume.go         | 14 ++++++++++++++
 4 files changed, 66 insertions(+), 23 deletions(-)

(limited to 'libpod')

diff --git a/libpod/image/prune.go b/libpod/image/prune.go
index b38265a7e..3c06a89c2 100644
--- a/libpod/image/prune.go
+++ b/libpod/image/prune.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/containers/podman/v2/libpod/events"
+	"github.com/containers/podman/v2/pkg/domain/entities/reports"
 	"github.com/containers/podman/v2/pkg/timetype"
 	"github.com/containers/storage"
 	"github.com/pkg/errors"
@@ -110,7 +111,8 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I
 
 // PruneImages prunes dangling and optionally all unused images from the local
 // image store
-func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
+func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]*reports.PruneReport, error) {
+	preports := make([]*reports.PruneReport, 0)
 	filterFuncs := make([]ImageFilter, 0, len(filter))
 	for _, f := range filter {
 		filterSplit := strings.SplitN(f, "=", 2)
@@ -125,7 +127,6 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
 		filterFuncs = append(filterFuncs, generatedFunc)
 	}
 
-	pruned := []string{}
 	prev := 0
 	for {
 		toPrune, err := ir.GetPruneImages(ctx, all, filterFuncs)
@@ -143,6 +144,13 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
 			if err != nil {
 				return nil, err
 			}
+			nameOrID := img.ID()
+			s, err := img.Size(ctx)
+			imgSize := *s
+			if err != nil {
+				logrus.Warnf("Failed to collect image size for: %s, %s", nameOrID, err)
+				imgSize = 0
+			}
 			if err := img.Remove(ctx, false); err != nil {
 				if errors.Cause(err) == storage.ErrImageUsedByContainer {
 					logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage (e.g., Buildah, CRI-O, etc.) maybe associated with this image.\nUsing the rmi command with the --force option will remove the container and image, but may cause failures for other dependent systems.", img.ID(), err)
@@ -151,13 +159,18 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
 				return nil, errors.Wrap(err, "failed to prune image")
 			}
 			defer img.newImageEvent(events.Prune)
-			nameOrID := img.ID()
+
 			if len(repotags) > 0 {
 				nameOrID = repotags[0]
 			}
-			pruned = append(pruned, nameOrID)
+
+			preports = append(preports, &reports.PruneReport{
+				Id:   nameOrID,
+				Err:  nil,
+				Size: uint64(imgSize),
+			})
 		}
 
 	}
-	return pruned, nil
+	return preports, nil
 }
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 14b537ca2..f22e48746 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -14,6 +14,7 @@ import (
 	"github.com/containers/podman/v2/libpod/events"
 	"github.com/containers/podman/v2/libpod/shutdown"
 	"github.com/containers/podman/v2/pkg/cgroups"
+	"github.com/containers/podman/v2/pkg/domain/entities/reports"
 	"github.com/containers/podman/v2/pkg/rootless"
 	"github.com/containers/storage"
 	"github.com/containers/storage/pkg/stringid"
@@ -884,9 +885,8 @@ func (r *Runtime) GetExecSessionContainer(id string) (*Container, error) {
 
 // PruneContainers removes stopped and exited containers from localstorage.  A set of optional filters
 // can be provided to be more granular.
-func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int64, map[string]error, error) {
-	pruneErrors := make(map[string]error)
-	prunedContainers := make(map[string]int64)
+func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) ([]*reports.PruneReport, error) {
+	preports := make([]*reports.PruneReport, 0)
 	// We add getting the exited and stopped containers via a filter
 	containerStateFilter := func(c *Container) bool {
 		if c.PodID() != "" {
@@ -906,23 +906,28 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int
 	filterFuncs = append(filterFuncs, containerStateFilter)
 	delContainers, err := r.GetContainers(filterFuncs...)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	for _, c := range delContainers {
-		ctr := c
-		size, err := ctr.RWSize()
+		report := new(reports.PruneReport)
+		report.Id = c.ID()
+		report.Err = nil
+		report.Size = 0
+		size, err := c.RWSize()
 		if err != nil {
-			pruneErrors[ctr.ID()] = err
+			report.Err = err
+			preports = append(preports, report)
 			continue
 		}
-		err = r.RemoveContainer(context.Background(), ctr, false, false)
+		err = r.RemoveContainer(context.Background(), c, false, false)
 		if err != nil {
-			pruneErrors[ctr.ID()] = err
+			report.Err = err
 		} else {
-			prunedContainers[ctr.ID()] = size
+			report.Size = (uint64)(size)
 		}
+		preports = append(preports, report)
 	}
-	return prunedContainers, pruneErrors, nil
+	return preports, nil
 }
 
 // MountStorageContainer mounts the storage container's root filesystem
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 10c32a119..9d985f905 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/containers/podman/v2/libpod/define"
 	"github.com/containers/podman/v2/libpod/events"
+	"github.com/containers/podman/v2/pkg/domain/entities/reports"
 	"github.com/pkg/errors"
 )
 
@@ -133,22 +134,32 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
 }
 
 // PruneVolumes removes unused volumes from the system
-func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) (map[string]error, error) {
-	reports := make(map[string]error)
+func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) ([]*reports.PruneReport, error) {
+	preports := make([]*reports.PruneReport, 0)
 	vols, err := r.Volumes(filterFuncs...)
 	if err != nil {
 		return nil, err
 	}
 
 	for _, vol := range vols {
+		report := new(reports.PruneReport)
+		volSize, err := vol.Size()
+		if err != nil {
+			volSize = 0
+		}
+		report.Size = volSize
+		report.Id = vol.Name()
 		if err := r.RemoveVolume(ctx, vol, false); err != nil {
 			if errors.Cause(err) != define.ErrVolumeBeingUsed && errors.Cause(err) != define.ErrVolumeRemoved {
-				reports[vol.Name()] = err
+				report.Err = err
+			} else {
+				// We didn't remove the volume for some reason
+				continue
 			}
-			continue
+		} else {
+			vol.newVolumeEvent(events.Prune)
 		}
-		vol.newVolumeEvent(events.Prune)
-		reports[vol.Name()] = nil
+		preports = append(preports, report)
 	}
-	return reports, nil
+	return preports, nil
 }
diff --git a/libpod/volume.go b/libpod/volume.go
index 0535bf4db..ed08d375f 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -1,6 +1,8 @@
 package libpod
 
 import (
+	"os"
+	"path/filepath"
 	"time"
 
 	"github.com/containers/podman/v2/libpod/define"
@@ -79,6 +81,18 @@ func (v *Volume) Name() string {
 	return v.config.Name
 }
 
+// Returns the size on disk of volume
+func (v *Volume) Size() (uint64, error) {
+	var size uint64
+	err := filepath.Walk(v.config.MountPoint, func(path string, info os.FileInfo, err error) error {
+		if err == nil && !info.IsDir() {
+			size += (uint64)(info.Size())
+		}
+		return err
+	})
+	return size, err
+}
+
 // Driver retrieves the volume's driver.
 func (v *Volume) Driver() string {
 	return v.config.Driver
-- 
cgit v1.2.3-54-g00ecf