spiegel_podman/cmd/podman/system/df.go
Paul Holzinger 415668c802
system df --verbose don't crash
When a container has no image, i.e. using rootfs like our new infra
containers then the Image function crashed trying to show the first 12
image ID chars. If there is no image simply show nothing there.

Fixes: #26224

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2025-05-28 20:50:17 +02:00

336 lines
8.9 KiB
Go

package system
import (
"errors"
"fmt"
"math"
"os"
"strings"
"time"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v5/cmd/podman/common"
"github.com/containers/podman/v5/cmd/podman/registry"
"github.com/containers/podman/v5/cmd/podman/validate"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/docker/go-units"
"github.com/spf13/cobra"
)
var (
dfSystemDescription = `
podman system df
Show podman disk usage
`
dfSystemCommand = &cobra.Command{
Use: "df [options]",
Args: validate.NoArgs,
Short: "Show podman disk usage",
Long: dfSystemDescription,
RunE: df,
ValidArgsFunction: completion.AutocompleteNone,
}
)
var (
dfOptions entities.SystemDfOptions
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: dfSystemCommand,
Parent: systemCmd,
})
flags := dfSystemCommand.Flags()
flags.BoolVarP(&dfOptions.Verbose, "verbose", "v", false, "Show detailed information on disk usage")
formatFlagName := "format"
flags.StringVar(&dfOptions.Format, formatFlagName, "", "Pretty-print images using a Go template")
_ = dfSystemCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&dfSummary{}))
}
func df(cmd *cobra.Command, args []string) error {
reports, err := registry.ContainerEngine().SystemDf(registry.Context(), dfOptions)
if err != nil {
return err
}
if dfOptions.Format != "" && dfOptions.Verbose {
return errors.New("cannot combine --format and --verbose flags")
}
if dfOptions.Verbose {
return printVerbose(cmd, reports)
}
return printSummary(cmd, reports)
}
func printSummary(cmd *cobra.Command, reports *entities.SystemDfReport) error {
var (
dfSummaries []*dfSummary
active int
used int64
)
visitedImages := make(map[string]bool)
for _, i := range reports.Images {
if _, ok := visitedImages[i.ImageID]; ok {
continue
}
visitedImages[i.ImageID] = true
if i.Containers > 0 {
active++
used += i.UniqueSize
}
}
imageSummary := dfSummary{
Type: "Images",
Total: len(reports.Images),
Active: active,
RawSize: reports.ImagesSize, // The "raw" size is the sum of all layer sizes
RawReclaimable: reports.ImagesSize - used, // We can reclaim the date of "unused" images (i.e., the ones without containers)
}
dfSummaries = append(dfSummaries, &imageSummary)
// Containers
var (
conActive int
conSize, conReclaimable int64
)
for _, c := range reports.Containers {
if c.Status == "running" {
conActive++
} else {
conReclaimable += c.RWSize
}
conSize += c.RWSize
}
containerSummary := dfSummary{
Type: "Containers",
Total: len(reports.Containers),
Active: conActive,
RawSize: conSize,
RawReclaimable: conReclaimable,
}
dfSummaries = append(dfSummaries, &containerSummary)
// Volumes
var (
activeVolumes int
volumesSize, volumesReclaimable int64
)
for _, v := range reports.Volumes {
activeVolumes += v.Links
volumesSize += v.Size
volumesReclaimable += v.ReclaimableSize
}
volumeSummary := dfSummary{
Type: "Local Volumes",
Total: len(reports.Volumes),
Active: activeVolumes,
RawSize: volumesSize,
RawReclaimable: volumesReclaimable,
}
dfSummaries = append(dfSummaries, &volumeSummary)
// need to give un-exported fields
hdrs := report.Headers(dfSummary{}, map[string]string{
"Size": "SIZE",
"Reclaimable": "RECLAIMABLE",
})
rpt := report.New(os.Stdout, cmd.Name())
defer rpt.Flush()
var err error
if cmd.Flags().Changed("format") {
if report.IsJSON(dfOptions.Format) {
return printJSON(dfSummaries)
}
rpt, err = rpt.Parse(report.OriginUser, dfOptions.Format)
} else {
row := "{{range . }}{{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}\n{{end -}}"
rpt, err = rpt.Parse(report.OriginPodman, row)
}
if err != nil {
return err
}
return writeTemplate(rpt, hdrs, dfSummaries)
}
func printJSON(data []*dfSummary) error {
bytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
fmt.Println(string(bytes))
return nil
}
func printVerbose(cmd *cobra.Command, reports *entities.SystemDfReport) error { //nolint:interfacer
rpt := report.New(os.Stdout, cmd.Name())
defer rpt.Flush()
fmt.Fprint(rpt.Writer(), "Images space usage:\n\n")
// convert to dfImage for output
dfImages := make([]*dfImage, 0, len(reports.Images))
for _, d := range reports.Images {
dfImages = append(dfImages, &dfImage{SystemDfImageReport: d})
}
hdrs := report.Headers(entities.SystemDfImageReport{}, map[string]string{
"ImageID": "IMAGE ID",
"SharedSize": "SHARED SIZE",
"UniqueSize": "UNIQUE SIZE",
})
imageRow := "{{range .}}{{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}\n{{end -}}"
rpt, err := rpt.Parse(report.OriginPodman, imageRow)
if err != nil {
return err
}
if err := writeTemplate(rpt, hdrs, dfImages); err != nil {
return err
}
fmt.Fprint(rpt.Writer(), "\nContainers space usage:\n\n")
// convert to dfContainers for output
dfContainers := make([]*dfContainer, 0, len(reports.Containers))
for _, d := range reports.Containers {
dfContainers = append(dfContainers, &dfContainer{SystemDfContainerReport: d})
}
hdrs = report.Headers(entities.SystemDfContainerReport{}, map[string]string{
"ContainerID": "CONTAINER ID",
"LocalVolumes": "LOCAL VOLUMES",
"RWSize": "SIZE",
})
containerRow := "{{range .}}{{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.RWSize}}\t{{.Created}}\t{{.Status}}\t{{.Names}}\n{{end -}}"
rpt, err = rpt.Parse(report.OriginPodman, containerRow)
if err != nil {
return err
}
if err := writeTemplate(rpt, hdrs, dfContainers); err != nil {
return err
}
fmt.Fprint(rpt.Writer(), "\nLocal Volumes space usage:\n\n")
dfVolumes := make([]*dfVolume, 0, len(reports.Volumes))
// convert to dfVolume for output
for _, d := range reports.Volumes {
dfVolumes = append(dfVolumes, &dfVolume{SystemDfVolumeReport: d})
}
hdrs = report.Headers(entities.SystemDfVolumeReport{}, map[string]string{
"VolumeName": "VOLUME NAME",
})
volumeRow := "{{range .}}{{.VolumeName}}\t{{.Links}}\t{{.Size}}\n{{end -}}"
rpt, err = rpt.Parse(report.OriginPodman, volumeRow)
if err != nil {
return err
}
return writeTemplate(rpt, hdrs, dfVolumes)
}
func writeTemplate(rpt *report.Formatter, hdrs []map[string]string, output interface{}) error {
if rpt.RenderHeaders {
if err := rpt.Execute(hdrs); err != nil {
return err
}
}
return rpt.Execute(output)
}
type dfImage struct {
*entities.SystemDfImageReport
}
func (d *dfImage) ImageID() string {
return d.SystemDfImageReport.ImageID[0:12]
}
func (d *dfImage) Created() string {
return units.HumanDuration(time.Since(d.SystemDfImageReport.Created))
}
func (d *dfImage) Size() string {
return units.HumanSize(float64(d.SystemDfImageReport.Size))
}
func (d *dfImage) SharedSize() string {
return units.HumanSize(float64(d.SystemDfImageReport.SharedSize))
}
func (d *dfImage) UniqueSize() string {
return units.HumanSize(float64(d.SystemDfImageReport.UniqueSize))
}
type dfContainer struct {
*entities.SystemDfContainerReport
}
func (d *dfContainer) ContainerID() string {
return d.SystemDfContainerReport.ContainerID[0:12]
}
func (d *dfContainer) Image() string {
if len(d.SystemDfContainerReport.Image) >= 12 {
return d.SystemDfContainerReport.Image[0:12]
}
return ""
}
func (d *dfContainer) Command() string {
return strings.Join(d.SystemDfContainerReport.Command, " ")
}
func (d *dfContainer) RWSize() string {
return units.HumanSize(float64(d.SystemDfContainerReport.RWSize))
}
func (d *dfContainer) Created() string {
return units.HumanDuration(time.Since(d.SystemDfContainerReport.Created))
}
type dfVolume struct {
*entities.SystemDfVolumeReport
}
func (d *dfVolume) Size() string {
return units.HumanSize(float64(d.SystemDfVolumeReport.Size))
}
type dfSummary struct {
Type string
Total int
Active int
RawSize int64
RawReclaimable int64
}
func (d *dfSummary) Size() string {
return units.HumanSize(float64(d.RawSize))
}
func (d *dfSummary) Reclaimable() string {
percent := 0
// make sure to check this to prevent div by zero problems
if d.RawSize > 0 {
percent = int(math.Round(float64(d.RawReclaimable) / float64(d.RawSize) * float64(100)))
}
return fmt.Sprintf("%s (%d%%)", units.HumanSize(float64(d.RawReclaimable)), percent)
}
func (d *dfSummary) MarshalJSON() ([]byte, error) {
// need to create a new type here to prevent infinite recursion in MarshalJSON() call
type rawDf dfSummary
return json.Marshal(struct {
rawDf
// fields for docker compat https://github.com/containers/podman/issues/16902
TotalCount int
Size string
Reclaimable string
}{rawDf(*d), d.Total, d.Size(), d.Reclaimable()})
}