mirror of https://github.com/containers/podman.git
				
				
				
			Merge pull request #2498 from QiWang19/sysdf
podman system df- show podman disk usage
This commit is contained in:
		
						commit
						9e556530d2
					
				|  | @ -572,3 +572,9 @@ type SystemPruneValues struct { | ||||||
| type SystemRenumberValues struct { | type SystemRenumberValues struct { | ||||||
| 	PodmanCommand | 	PodmanCommand | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type SystemDfValues struct { | ||||||
|  | 	PodmanCommand | ||||||
|  | 	Verbose bool | ||||||
|  | 	Format  string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -108,6 +108,7 @@ func getSystemSubCommands() []*cobra.Command { | ||||||
| 	return []*cobra.Command{ | 	return []*cobra.Command{ | ||||||
| 		_pruneSystemCommand, | 		_pruneSystemCommand, | ||||||
| 		_renumberCommand, | 		_renumberCommand, | ||||||
|  | 		_dfSystemCommand, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,639 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/containers/buildah/pkg/formats" | ||||||
|  | 	"github.com/containers/libpod/cmd/podman/cliconfig" | ||||||
|  | 	"github.com/containers/libpod/cmd/podman/libpodruntime" | ||||||
|  | 	"github.com/containers/libpod/libpod" | ||||||
|  | 	"github.com/containers/libpod/libpod/image" | ||||||
|  | 	units "github.com/docker/go-units" | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	dfSystemCommand     cliconfig.SystemDfValues | ||||||
|  | 	dfSystemDescription = ` | ||||||
|  | 	podman system df | ||||||
|  | 
 | ||||||
|  | 	Show podman disk usage | ||||||
|  | 	` | ||||||
|  | 	_dfSystemCommand = &cobra.Command{ | ||||||
|  | 		Use:   "df", | ||||||
|  | 		Short: "Show podman disk usage", | ||||||
|  | 		Long:  dfSystemDescription, | ||||||
|  | 		RunE: func(cmd *cobra.Command, args []string) error { | ||||||
|  | 			dfSystemCommand.GlobalFlags = MainGlobalOpts | ||||||
|  | 			return dfSystemCmd(&dfSystemCommand) | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type dfMetaData struct { | ||||||
|  | 	images                   []*image.Image | ||||||
|  | 	containers               []*libpod.Container | ||||||
|  | 	activeContainers         map[string]*libpod.Container | ||||||
|  | 	imagesUsedbyCtrMap       map[string][]*libpod.Container | ||||||
|  | 	imagesUsedbyActiveCtr    map[string][]*libpod.Container | ||||||
|  | 	volumes                  []*libpod.Volume | ||||||
|  | 	volumeUsedByContainerMap map[string][]*libpod.Container | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type systemDfDiskUsage struct { | ||||||
|  | 	Type        string | ||||||
|  | 	Total       int | ||||||
|  | 	Active      int | ||||||
|  | 	Size        string | ||||||
|  | 	Reclaimable string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type imageVerboseDiskUsage struct { | ||||||
|  | 	Repository string | ||||||
|  | 	Tag        string | ||||||
|  | 	ImageID    string | ||||||
|  | 	Created    string | ||||||
|  | 	Size       string | ||||||
|  | 	SharedSize string | ||||||
|  | 	UniqueSize string | ||||||
|  | 	Containers int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type containerVerboseDiskUsage struct { | ||||||
|  | 	ContainerID  string | ||||||
|  | 	Image        string | ||||||
|  | 	Command      string | ||||||
|  | 	LocalVolumes int | ||||||
|  | 	Size         string | ||||||
|  | 	Created      string | ||||||
|  | 	Status       string | ||||||
|  | 	Names        string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type volumeVerboseDiskUsage struct { | ||||||
|  | 	VolumeName string | ||||||
|  | 	Links      int | ||||||
|  | 	Size       string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const systemDfDefaultFormat string = "table {{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}" | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	dfSystemCommand.Command = _dfSystemCommand | ||||||
|  | 	dfSystemCommand.SetUsageTemplate(UsageTemplate()) | ||||||
|  | 	flags := dfSystemCommand.Flags() | ||||||
|  | 	flags.BoolVarP(&dfSystemCommand.Verbose, "verbose", "v", false, "Show detailed information on space usage") | ||||||
|  | 	flags.StringVar(&dfSystemCommand.Format, "format", "", "Pretty-print images using a Go template") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func dfSystemCmd(c *cliconfig.SystemDfValues) error { | ||||||
|  | 	runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrapf(err, "Could not get runtime") | ||||||
|  | 	} | ||||||
|  | 	defer runtime.Shutdown(false) | ||||||
|  | 
 | ||||||
|  | 	ctx := getContext() | ||||||
|  | 
 | ||||||
|  | 	metaData, err := getDfMetaData(ctx, runtime) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrapf(err, "error getting disk usage data") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if c.Verbose { | ||||||
|  | 		err := verboseOutput(ctx, metaData) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	systemDfDiskUsages, err := getDiskUsage(ctx, runtime, metaData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrapf(err, "error getting output of system df") | ||||||
|  | 	} | ||||||
|  | 	format := systemDfDefaultFormat | ||||||
|  | 	if c.Format != "" { | ||||||
|  | 		format = strings.Replace(c.Format, `\t`, "\t", -1) | ||||||
|  | 	} | ||||||
|  | 	generateSysDfOutput(systemDfDiskUsages, format) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func generateSysDfOutput(systemDfDiskUsages []systemDfDiskUsage, format string) { | ||||||
|  | 	var systemDfHeader = map[string]string{ | ||||||
|  | 		"Type":        "TYPE", | ||||||
|  | 		"Total":       "TOTAL", | ||||||
|  | 		"Active":      "ACTIVE", | ||||||
|  | 		"Size":        "SIZE", | ||||||
|  | 		"Reclaimable": "RECLAIMABLE", | ||||||
|  | 	} | ||||||
|  | 	out := formats.StdoutTemplateArray{Output: systemDfDiskUsageToGeneric(systemDfDiskUsages), Template: format, Fields: systemDfHeader} | ||||||
|  | 	formats.Writer(out).Out() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getDiskUsage(ctx context.Context, runtime *libpod.Runtime, metaData dfMetaData) ([]systemDfDiskUsage, error) { | ||||||
|  | 	imageDiskUsage, err := getImageDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap, metaData.imagesUsedbyActiveCtr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.Wrapf(err, "error getting disk usage of images") | ||||||
|  | 	} | ||||||
|  | 	containerDiskUsage, err := getContainerDiskUsage(metaData.containers, metaData.activeContainers) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.Wrapf(err, "error getting disk usage of containers") | ||||||
|  | 	} | ||||||
|  | 	volumeDiskUsage, err := getVolumeDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.Wrapf(err, "error getting disk usage of volumess") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	systemDfDiskUsages := []systemDfDiskUsage{imageDiskUsage, containerDiskUsage, volumeDiskUsage} | ||||||
|  | 	return systemDfDiskUsages, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getDfMetaData(ctx context.Context, runtime *libpod.Runtime) (dfMetaData, error) { | ||||||
|  | 	var metaData dfMetaData | ||||||
|  | 	images, err := runtime.ImageRuntime().GetImages() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return metaData, errors.Wrapf(err, "unable to get images") | ||||||
|  | 	} | ||||||
|  | 	containers, err := runtime.GetAllContainers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return metaData, errors.Wrapf(err, "error getting all containers") | ||||||
|  | 	} | ||||||
|  | 	volumes, err := runtime.GetAllVolumes() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return metaData, errors.Wrap(err, "error getting all volumes") | ||||||
|  | 	} | ||||||
|  | 	activeContainers, err := activeContainers(containers) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return metaData, errors.Wrapf(err, "error getting active containers") | ||||||
|  | 	} | ||||||
|  | 	imagesUsedbyCtrMap, imagesUsedbyActiveCtr, err := imagesUsedbyCtr(containers, activeContainers) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return metaData, errors.Wrapf(err, "error getting getting images used by containers") | ||||||
|  | 	} | ||||||
|  | 	metaData = dfMetaData{ | ||||||
|  | 		images:                   images, | ||||||
|  | 		containers:               containers, | ||||||
|  | 		activeContainers:         activeContainers, | ||||||
|  | 		imagesUsedbyCtrMap:       imagesUsedbyCtrMap, | ||||||
|  | 		imagesUsedbyActiveCtr:    imagesUsedbyActiveCtr, | ||||||
|  | 		volumes:                  volumes, | ||||||
|  | 		volumeUsedByContainerMap: volumeUsedByContainer(containers), | ||||||
|  | 	} | ||||||
|  | 	return metaData, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uint64, error) { | ||||||
|  | 	imgUniqueSizeMap := make(map[string]uint64) | ||||||
|  | 	for _, img := range images { | ||||||
|  | 		parentImg := img | ||||||
|  | 		for { | ||||||
|  | 			next, err := parentImg.GetParent() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID()) | ||||||
|  | 			} | ||||||
|  | 			if next == nil { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			parentImg = next | ||||||
|  | 		} | ||||||
|  | 		imgSize, err := img.Size(ctx) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if img.ID() == parentImg.ID() { | ||||||
|  | 			imgUniqueSizeMap[img.ID()] = *imgSize | ||||||
|  | 		} else { | ||||||
|  | 			parentImgSize, err := parentImg.Size(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, errors.Wrapf(err, "error getting size of parent image %s", parentImg.ID()) | ||||||
|  | 			} | ||||||
|  | 			imgUniqueSizeMap[img.ID()] = *imgSize - *parentImgSize | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return imgUniqueSizeMap, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) { | ||||||
|  | 	var ( | ||||||
|  | 		numberOfImages       int | ||||||
|  | 		sumSize              uint64 | ||||||
|  | 		numberOfActiveImages int | ||||||
|  | 		unreclaimableSize    uint64 | ||||||
|  | 		imageDiskUsage       systemDfDiskUsage | ||||||
|  | 		reclaimableStr       string | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	imgUniqueSizeMap, err := imageUniqueSize(ctx, images) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return imageDiskUsage, errors.Wrapf(err, "error getting unique size of images") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, img := range images { | ||||||
|  | 
 | ||||||
|  | 		unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap) | ||||||
|  | 
 | ||||||
|  | 		isParent, err := img.IsParent() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return imageDiskUsage, err | ||||||
|  | 		} | ||||||
|  | 		parent, err := img.GetParent() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) | ||||||
|  | 		} | ||||||
|  | 		if isParent && parent != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		numberOfImages++ | ||||||
|  | 		if _, isActive := imageUsedbyCintainerMap[img.ID()]; isActive { | ||||||
|  | 			numberOfActiveImages++ | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !isParent { | ||||||
|  | 			size, err := img.Size(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return imageDiskUsage, errors.Wrapf(err, "error getting disk usage of image %s", img.ID()) | ||||||
|  | 			} | ||||||
|  | 			sumSize += *size | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 	sumSizeStr := units.HumanSizeWithPrecision(float64(sumSize), 3) | ||||||
|  | 	reclaimable := sumSize - unreclaimableSize | ||||||
|  | 	if sumSize != 0 { | ||||||
|  | 		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) | ||||||
|  | 	} else { | ||||||
|  | 		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0) | ||||||
|  | 	} | ||||||
|  | 	imageDiskUsage = systemDfDiskUsage{ | ||||||
|  | 		Type:        "Images", | ||||||
|  | 		Total:       numberOfImages, | ||||||
|  | 		Active:      numberOfActiveImages, | ||||||
|  | 		Size:        sumSizeStr, | ||||||
|  | 		Reclaimable: reclaimableStr, | ||||||
|  | 	} | ||||||
|  | 	return imageDiskUsage, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func imageUsedSize(img *image.Image, imgUniqueSizeMap map[string]uint64, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) uint64 { | ||||||
|  | 	var usedSize uint64 | ||||||
|  | 	imgUnique := imgUniqueSizeMap[img.ID()] | ||||||
|  | 	if _, isCtrActive := imageUsedbyActiveContainerMap[img.ID()]; isCtrActive { | ||||||
|  | 		return imgUnique | ||||||
|  | 	} | ||||||
|  | 	containers := imageUsedbyCintainerMap[img.ID()] | ||||||
|  | 	for _, ctr := range containers { | ||||||
|  | 		if len(ctr.UserVolumes()) > 0 { | ||||||
|  | 			usedSize += imgUnique | ||||||
|  | 			return usedSize | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return usedSize | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func imagesUsedbyCtr(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (map[string][]*libpod.Container, map[string][]*libpod.Container, error) { | ||||||
|  | 	imgCtrMap := make(map[string][]*libpod.Container) | ||||||
|  | 	imgActiveCtrMap := make(map[string][]*libpod.Container) | ||||||
|  | 	for _, ctr := range containers { | ||||||
|  | 		imgID, _ := ctr.Image() | ||||||
|  | 		imgCtrMap[imgID] = append(imgCtrMap[imgID], ctr) | ||||||
|  | 		if _, isActive := activeContainers[ctr.ID()]; isActive { | ||||||
|  | 			imgActiveCtrMap[imgID] = append(imgActiveCtrMap[imgID], ctr) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return imgCtrMap, imgActiveCtrMap, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getContainerDiskUsage(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (systemDfDiskUsage, error) { | ||||||
|  | 	var ( | ||||||
|  | 		sumSize           int64 | ||||||
|  | 		unreclaimableSize int64 | ||||||
|  | 		reclaimableStr    string | ||||||
|  | 	) | ||||||
|  | 	for _, ctr := range containers { | ||||||
|  | 		size, err := ctr.RWSize() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of container %s", ctr.ID()) | ||||||
|  | 		} | ||||||
|  | 		sumSize += size | ||||||
|  | 	} | ||||||
|  | 	for _, activeCtr := range activeContainers { | ||||||
|  | 		size, err := activeCtr.RWSize() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of active container %s", activeCtr.ID()) | ||||||
|  | 		} | ||||||
|  | 		unreclaimableSize += size | ||||||
|  | 	} | ||||||
|  | 	if sumSize == 0 { | ||||||
|  | 		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(0, 3), 0) | ||||||
|  | 	} else { | ||||||
|  | 		reclaimable := sumSize - unreclaimableSize | ||||||
|  | 		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) | ||||||
|  | 	} | ||||||
|  | 	containerDiskUsage := systemDfDiskUsage{ | ||||||
|  | 		Type:        "Containers", | ||||||
|  | 		Total:       len(containers), | ||||||
|  | 		Active:      len(activeContainers), | ||||||
|  | 		Size:        units.HumanSizeWithPrecision(float64(sumSize), 3), | ||||||
|  | 		Reclaimable: reclaimableStr, | ||||||
|  | 	} | ||||||
|  | 	return containerDiskUsage, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ctrIsActive(ctr *libpod.Container) (bool, error) { | ||||||
|  | 	state, err := ctr.State() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	return state == libpod.ContainerStatePaused || state == libpod.ContainerStateRunning, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func activeContainers(containers []*libpod.Container) (map[string]*libpod.Container, error) { | ||||||
|  | 	activeContainers := make(map[string]*libpod.Container) | ||||||
|  | 	for _, aCtr := range containers { | ||||||
|  | 		isActive, err := ctrIsActive(aCtr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if isActive { | ||||||
|  | 			activeContainers[aCtr.ID()] = aCtr | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return activeContainers, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getVolumeDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) { | ||||||
|  | 	var ( | ||||||
|  | 		sumSize           int64 | ||||||
|  | 		unreclaimableSize int64 | ||||||
|  | 		reclaimableStr    string | ||||||
|  | 	) | ||||||
|  | 	for _, volume := range volumes { | ||||||
|  | 		size, err := volumeSize(volume) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of volime %s", volume.Name()) | ||||||
|  | 		} | ||||||
|  | 		sumSize += size | ||||||
|  | 		if _, exist := volumeUsedByContainerMap[volume.Name()]; exist { | ||||||
|  | 			unreclaimableSize += size | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	reclaimable := sumSize - unreclaimableSize | ||||||
|  | 	if sumSize != 0 { | ||||||
|  | 		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) | ||||||
|  | 	} else { | ||||||
|  | 		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0) | ||||||
|  | 	} | ||||||
|  | 	volumesDiskUsage := systemDfDiskUsage{ | ||||||
|  | 		Type:        "Local Volumes", | ||||||
|  | 		Total:       len(volumes), | ||||||
|  | 		Active:      len(volumeUsedByContainerMap), | ||||||
|  | 		Size:        units.HumanSizeWithPrecision(float64(sumSize), 3), | ||||||
|  | 		Reclaimable: reclaimableStr, | ||||||
|  | 	} | ||||||
|  | 	return volumesDiskUsage, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func volumeUsedByContainer(containers []*libpod.Container) map[string][]*libpod.Container { | ||||||
|  | 	volumeUsedByContainerMap := make(map[string][]*libpod.Container) | ||||||
|  | 	for _, ctr := range containers { | ||||||
|  | 
 | ||||||
|  | 		ctrVolumes := ctr.UserVolumes() | ||||||
|  | 		for _, ctrVolume := range ctrVolumes { | ||||||
|  | 			volumeUsedByContainerMap[ctrVolume] = append(volumeUsedByContainerMap[ctrVolume], ctr) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return volumeUsedByContainerMap | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func volumeSize(volume *libpod.Volume) (int64, error) { | ||||||
|  | 	var size int64 | ||||||
|  | 	err := filepath.Walk(volume.MountPoint(), func(path string, info os.FileInfo, err error) error { | ||||||
|  | 		if err == nil && !info.IsDir() { | ||||||
|  | 			size += info.Size() | ||||||
|  | 		} | ||||||
|  | 		return err | ||||||
|  | 	}) | ||||||
|  | 	return size, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, imagesUsedbyCtr map[string][]*libpod.Container) ([]imageVerboseDiskUsage, error) { | ||||||
|  | 	var imagesVerboseDiskUsage []imageVerboseDiskUsage | ||||||
|  | 	imgUniqueSizeMap, err := imageUniqueSize(ctx, images) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images") | ||||||
|  | 	} | ||||||
|  | 	for _, img := range images { | ||||||
|  | 		isParent, err := img.IsParent() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID()) | ||||||
|  | 		} | ||||||
|  | 		parent, err := img.GetParent() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) | ||||||
|  | 		} | ||||||
|  | 		if isParent && parent != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		size, err := img.Size(ctx) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting size of image %s", img.ID()) | ||||||
|  | 		} | ||||||
|  | 		numberOfContainers := 0 | ||||||
|  | 		if ctrs, exist := imagesUsedbyCtr[img.ID()]; exist { | ||||||
|  | 			numberOfContainers = len(ctrs) | ||||||
|  | 		} | ||||||
|  | 		var repo string | ||||||
|  | 		var tag string | ||||||
|  | 		if len(img.Names()) == 0 { | ||||||
|  | 			repo = "<none>" | ||||||
|  | 			tag = "<none>" | ||||||
|  | 		} | ||||||
|  | 		repopairs, err := image.ReposToMap([]string{img.Names()[0]}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			logrus.Errorf("error finding tag/digest for %s", img.ID()) | ||||||
|  | 		} | ||||||
|  | 		for reponame, tags := range repopairs { | ||||||
|  | 			for _, tagname := range tags { | ||||||
|  | 				repo = reponame | ||||||
|  | 				tag = tagname | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		imageVerbosedf := imageVerboseDiskUsage{ | ||||||
|  | 			Repository: repo, | ||||||
|  | 			Tag:        tag, | ||||||
|  | 			ImageID:    shortID(img.ID()), | ||||||
|  | 			Created:    units.HumanDuration(time.Since((img.Created().Local()))) + " ago", | ||||||
|  | 			Size:       units.HumanSizeWithPrecision(float64(*size), 3), | ||||||
|  | 			SharedSize: units.HumanSizeWithPrecision(float64(*size-imgUniqueSizeMap[img.ID()]), 3), | ||||||
|  | 			UniqueSize: units.HumanSizeWithPrecision(float64(imgUniqueSizeMap[img.ID()]), 3), | ||||||
|  | 			Containers: numberOfContainers, | ||||||
|  | 		} | ||||||
|  | 		imagesVerboseDiskUsage = append(imagesVerboseDiskUsage, imageVerbosedf) | ||||||
|  | 	} | ||||||
|  | 	return imagesVerboseDiskUsage, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getContainerVerboseDiskUsage(containers []*libpod.Container) (containersVerboseDiskUsage []containerVerboseDiskUsage, err error) { | ||||||
|  | 	for _, ctr := range containers { | ||||||
|  | 		imgID, _ := ctr.Image() | ||||||
|  | 		size, err := ctr.RWSize() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return containersVerboseDiskUsage, errors.Wrapf(err, "error getting size of container %s", ctr.ID()) | ||||||
|  | 		} | ||||||
|  | 		state, err := ctr.State() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return containersVerboseDiskUsage, errors.Wrapf(err, "error getting the state of container %s", ctr.ID()) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ctrVerboseData := containerVerboseDiskUsage{ | ||||||
|  | 			ContainerID:  shortID(ctr.ID()), | ||||||
|  | 			Image:        shortImageID(imgID), | ||||||
|  | 			Command:      strings.Join(ctr.Command(), " "), | ||||||
|  | 			LocalVolumes: len(ctr.UserVolumes()), | ||||||
|  | 			Size:         units.HumanSizeWithPrecision(float64(size), 3), | ||||||
|  | 			Created:      units.HumanDuration(time.Since(ctr.CreatedTime().Local())) + "ago", | ||||||
|  | 			Status:       state.String(), | ||||||
|  | 			Names:        ctr.Name(), | ||||||
|  | 		} | ||||||
|  | 		containersVerboseDiskUsage = append(containersVerboseDiskUsage, ctrVerboseData) | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 	return containersVerboseDiskUsage, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getVolumeVerboseDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (volumesVerboseDiskUsage []volumeVerboseDiskUsage, err error) { | ||||||
|  | 	for _, vol := range volumes { | ||||||
|  | 		volSize, err := volumeSize(vol) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return volumesVerboseDiskUsage, errors.Wrapf(err, "error getting size of volume %s", vol.Name()) | ||||||
|  | 		} | ||||||
|  | 		links := 0 | ||||||
|  | 		if linkCtr, exist := volumeUsedByContainerMap[vol.Name()]; exist { | ||||||
|  | 			links = len(linkCtr) | ||||||
|  | 		} | ||||||
|  | 		volumeVerboseData := volumeVerboseDiskUsage{ | ||||||
|  | 			VolumeName: vol.Name(), | ||||||
|  | 			Links:      links, | ||||||
|  | 			Size:       units.HumanSizeWithPrecision(float64(volSize), 3), | ||||||
|  | 		} | ||||||
|  | 		volumesVerboseDiskUsage = append(volumesVerboseDiskUsage, volumeVerboseData) | ||||||
|  | 	} | ||||||
|  | 	return volumesVerboseDiskUsage, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func imagesVerboseOutput(ctx context.Context, metaData dfMetaData) error { | ||||||
|  | 	var imageVerboseHeader = map[string]string{ | ||||||
|  | 		"Repository": "REPOSITORY", | ||||||
|  | 		"Tag":        "TAG", | ||||||
|  | 		"ImageID":    "IMAGE ID", | ||||||
|  | 		"Created":    "CREATED", | ||||||
|  | 		"Size":       "SIZE", | ||||||
|  | 		"SharedSize": "SHARED SIZE", | ||||||
|  | 		"UniqueSize": "UNQUE SIZE", | ||||||
|  | 		"Containers": "CONTAINERS", | ||||||
|  | 	} | ||||||
|  | 	imagesVerboseDiskUsage, err := getImageVerboseDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrapf(err, "error getting verbose output of images") | ||||||
|  | 	} | ||||||
|  | 	os.Stderr.WriteString("Images space usage:\n\n") | ||||||
|  | 	out := formats.StdoutTemplateArray{Output: systemDfImageVerboseDiskUsageToGeneric(imagesVerboseDiskUsage), Template: "table {{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}", Fields: imageVerboseHeader} | ||||||
|  | 	formats.Writer(out).Out() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func containersVerboseOutput(ctx context.Context, metaData dfMetaData) error { | ||||||
|  | 	var containerVerboseHeader = map[string]string{ | ||||||
|  | 		"ContainerID":  "CONTAINER ID ", | ||||||
|  | 		"Image":        "IMAGE", | ||||||
|  | 		"Command":      "COMMAND", | ||||||
|  | 		"LocalVolumes": "LOCAL VOLUMES", | ||||||
|  | 		"Size":         "SIZE", | ||||||
|  | 		"Created":      "CREATED", | ||||||
|  | 		"Status":       "STATUS", | ||||||
|  | 		"Names":        "NAMES", | ||||||
|  | 	} | ||||||
|  | 	containersVerboseDiskUsage, err := getContainerVerboseDiskUsage(metaData.containers) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrapf(err, "error getting verbose output of containers") | ||||||
|  | 	} | ||||||
|  | 	os.Stderr.WriteString("\nContainers space usage:\n\n") | ||||||
|  | 	out := formats.StdoutTemplateArray{Output: systemDfContainerVerboseDiskUsageToGeneric(containersVerboseDiskUsage), Template: "table {{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.Created}}\t{{.Status}}\t{{.Names}}", Fields: containerVerboseHeader} | ||||||
|  | 	formats.Writer(out).Out() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func volumesVerboseOutput(ctx context.Context, metaData dfMetaData) error { | ||||||
|  | 	var volumeVerboseHeader = map[string]string{ | ||||||
|  | 		"VolumeName": "VOLUME NAME", | ||||||
|  | 		"Links":      "LINKS", | ||||||
|  | 		"Size":       "SIZE", | ||||||
|  | 	} | ||||||
|  | 	volumesVerboseDiskUsage, err := getVolumeVerboseDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.Wrapf(err, "error getting verbose ouput of volumes") | ||||||
|  | 	} | ||||||
|  | 	os.Stderr.WriteString("\nLocal Volumes space usage:\n\n") | ||||||
|  | 	out := formats.StdoutTemplateArray{Output: systemDfVolumeVerboseDiskUsageToGeneric(volumesVerboseDiskUsage), Template: "table {{.VolumeName}}\t{{.Links}}\t{{.Size}}", Fields: volumeVerboseHeader} | ||||||
|  | 	formats.Writer(out).Out() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func verboseOutput(ctx context.Context, metaData dfMetaData) error { | ||||||
|  | 	if err := imagesVerboseOutput(ctx, metaData); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := containersVerboseOutput(ctx, metaData); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := volumesVerboseOutput(ctx, metaData); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func systemDfDiskUsageToGeneric(diskUsages []systemDfDiskUsage) (out []interface{}) { | ||||||
|  | 	for _, usage := range diskUsages { | ||||||
|  | 		out = append(out, interface{}(usage)) | ||||||
|  | 	} | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func systemDfImageVerboseDiskUsageToGeneric(diskUsages []imageVerboseDiskUsage) (out []interface{}) { | ||||||
|  | 	for _, usage := range diskUsages { | ||||||
|  | 		out = append(out, interface{}(usage)) | ||||||
|  | 	} | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func systemDfContainerVerboseDiskUsageToGeneric(diskUsages []containerVerboseDiskUsage) (out []interface{}) { | ||||||
|  | 	for _, usage := range diskUsages { | ||||||
|  | 		out = append(out, interface{}(usage)) | ||||||
|  | 	} | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func systemDfVolumeVerboseDiskUsageToGeneric(diskUsages []volumeVerboseDiskUsage) (out []interface{}) { | ||||||
|  | 	for _, usage := range diskUsages { | ||||||
|  | 		out = append(out, interface{}(usage)) | ||||||
|  | 	} | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func shortImageID(id string) string { | ||||||
|  | 	const imageIDTruncLength int = 4 | ||||||
|  | 	if len(id) > imageIDTruncLength { | ||||||
|  | 		return id[:imageIDTruncLength] | ||||||
|  | 	} | ||||||
|  | 	return id | ||||||
|  | } | ||||||
|  | @ -999,6 +999,24 @@ _podman_container() { | ||||||
|      esac |      esac | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | _podman_system_df() { | ||||||
|  | 	local options_with_args=" | ||||||
|  | 	--format | ||||||
|  | 	--verbose | ||||||
|  | 	" | ||||||
|  | 	local boolean_options=" | ||||||
|  |      -h | ||||||
|  |      --help | ||||||
|  | 	 --verbose | ||||||
|  | 	 -v | ||||||
|  | 	" | ||||||
|  |     case "$cur" in | ||||||
|  | 	-*) | ||||||
|  | 	    COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) | ||||||
|  | 	    ;; | ||||||
|  |     esac | ||||||
|  | } | ||||||
|  | 
 | ||||||
| _podman_system_info() { | _podman_system_info() { | ||||||
|    _podman_info |    _podman_info | ||||||
| } | } | ||||||
|  | @ -1029,6 +1047,7 @@ _podman_system() { | ||||||
| 	-h | 	-h | ||||||
| 	" | 	" | ||||||
|      subcommands=" |      subcommands=" | ||||||
|  | 	df | ||||||
| 	info | 	info | ||||||
| 	prune | 	prune | ||||||
|      " |      " | ||||||
|  |  | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | % podman-system-df(1) podman | ||||||
|  | 
 | ||||||
|  | ## NAME | ||||||
|  | podman\-system\-df - Show podman disk usage | ||||||
|  | 
 | ||||||
|  | ## SYNOPSIS | ||||||
|  | **podman system df** [*options*] | ||||||
|  | 
 | ||||||
|  | ## DESCRIPTION | ||||||
|  | Show podman disk usage | ||||||
|  | 
 | ||||||
|  | ## OPTIONS | ||||||
|  | **--format**="" | ||||||
|  | 
 | ||||||
|  | Pretty-print images using a Go template | ||||||
|  | 
 | ||||||
|  | **-v, --verbose**[=false] | ||||||
|  | Show detailed information on space usage | ||||||
|  | 
 | ||||||
|  | ## EXAMPLE | ||||||
|  | 
 | ||||||
|  | $ podman system df | ||||||
|  | TYPE            TOTAL   ACTIVE   SIZE    RECLAIMABLE | ||||||
|  | Images          6       2        281MB   168MB (59%) | ||||||
|  | Containers      3       1        0B      0B (0%) | ||||||
|  | Local Volumes   1       1        22B     0B (0%) | ||||||
|  | 
 | ||||||
|  | $ podman system df -v | ||||||
|  | Images space usage: | ||||||
|  | 
 | ||||||
|  | REPOSITORY                 TAG      IMAGE ID       CREATED       SIZE     SHARED SIZE   UNQUE SIZE   CONTAINERS | ||||||
|  | docker.io/library/alpine   latest   5cb3aa00f899   2 weeks ago   5.79MB   0B            5.79MB       5 | ||||||
|  | 
 | ||||||
|  | Containers space usage: | ||||||
|  | 
 | ||||||
|  | CONTAINER ID    IMAGE   COMMAND       LOCAL VOLUMES   SIZE     CREATED            STATUS       NAMES | ||||||
|  | 073f7e62812d    5cb3    sleep 100     1               0B       About an hourago   exited       zen_joliot | ||||||
|  | 3f19f5bba242    5cb3    sleep 100     0               5.52kB   4 hoursago         exited       pedantic_archimedes | ||||||
|  | 8cd89bf645cc    5cb3    ls foodir     0               58B      2 hoursago         configured   agitated_hamilton | ||||||
|  | a1d948a4b61d    5cb3    ls foodir     0               12B      2 hoursago         exited       laughing_wing | ||||||
|  | eafe3e3c5bb3    5cb3    sleep 10000   0               72B      2 hoursago         running      priceless_liskov | ||||||
|  | 
 | ||||||
|  | Local Volumes space usage: | ||||||
|  | 
 | ||||||
|  | VOLUME NAME   LINKS   SIZE | ||||||
|  | data          1       0B | ||||||
|  | 
 | ||||||
|  | $ podman system df --format "{{.Type}}\t{{.Total}}" | ||||||
|  | Images          1 | ||||||
|  | Containers      5 | ||||||
|  | Local Volumes   1 | ||||||
|  | 
 | ||||||
|  | ## SEE ALSO | ||||||
|  | podman-system(1) | ||||||
|  | 
 | ||||||
|  | # HISTORY | ||||||
|  | March 2019, Originally compiled by Qi Wang (qiwan at redhat dot com) | ||||||
|  | @ -13,7 +13,8 @@ The system command allows you to manage the podman systems | ||||||
| 
 | 
 | ||||||
| | Command  | Man Page                                            | Description                                                                  | | | Command  | Man Page                                            | Description                                                                  | | ||||||
| | -------  | --------------------------------------------------- | ---------------------------------------------------------------------------- | | | -------  | --------------------------------------------------- | ---------------------------------------------------------------------------- | | ||||||
| | info     | [podman-info(1)](podman-info.1.md)                  | Displays Podman related system information.                                  | | | df       | [podman-system-df(1)](podman-system-df.1.md)        | Show podman disk usage.                                                      | | ||||||
|  | | info     | [podman-system-info(1)](podman-info.1.md)           | Displays Podman related system information.                                  | | ||||||
| | prune    | [podman-system-prune(1)](podman-system-prune.1.md)  | Remove all unused data                                                       | | | prune    | [podman-system-prune(1)](podman-system-prune.1.md)  | Remove all unused data                                                       | | ||||||
| | renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks.      | | | renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks.      | | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | // +build !remoteclient
 | ||||||
|  | 
 | ||||||
|  | package integration | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	. "github.com/containers/libpod/test/utils" | ||||||
|  | 	. "github.com/onsi/ginkgo" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _ = Describe("podman system df", 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 system df", func() { | ||||||
|  | 		session := podmanTest.Podman([]string{"create", ALPINE}) | ||||||
|  | 		session.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(session.ExitCode()).To(Equal(0)) | ||||||
|  | 
 | ||||||
|  | 		session = podmanTest.Podman([]string{"volume", "create", "data"}) | ||||||
|  | 		session.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(session.ExitCode()).To(Equal(0)) | ||||||
|  | 
 | ||||||
|  | 		session = podmanTest.Podman([]string{"create", "-v", "data:/data", "--name", "container1", "busybox"}) | ||||||
|  | 		session.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(session.ExitCode()).To(Equal(0)) | ||||||
|  | 
 | ||||||
|  | 		session = podmanTest.Podman([]string{"system", "df"}) | ||||||
|  | 		session.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(session.ExitCode()).To(Equal(0)) | ||||||
|  | 		Expect(len(session.OutputToStringArray())).To(Equal(4)) | ||||||
|  | 		images := strings.Fields(session.OutputToStringArray()[1]) | ||||||
|  | 		containers := strings.Fields(session.OutputToStringArray()[2]) | ||||||
|  | 		volumes := strings.Fields(session.OutputToStringArray()[3]) | ||||||
|  | 		Expect(images[1]).To(Equal("2")) | ||||||
|  | 		Expect(containers[1]).To(Equal("2")) | ||||||
|  | 		Expect(volumes[2]).To(Equal("1")) | ||||||
|  | 	}) | ||||||
|  | }) | ||||||
|  | @ -78,6 +78,7 @@ There are other equivalents for these tools | ||||||
| | `docker volume prune`  | [`podman volume prune`](./docs/podman-volume-prune.1.md)    | | | `docker volume prune`  | [`podman volume prune`](./docs/podman-volume-prune.1.md)    | | ||||||
| | `docker volume rm`     | [`podman volume rm`](./docs/podman-volume-rm.1.md)          | | | `docker volume rm`     | [`podman volume rm`](./docs/podman-volume-rm.1.md)          | | ||||||
| | `docker system`        | [`podman system`](./docs/podman-system.1.md)                | | | `docker system`        | [`podman system`](./docs/podman-system.1.md)                | | ||||||
|  | | `docker system df`     | [`podman system df`](./docs/podman-system-df.1.md)          | | ||||||
| | `docker system prune`  | [`podman system prune`](./docs/podman-system-prune.1.md)    | | | `docker system prune`  | [`podman system prune`](./docs/podman-system-prune.1.md)    | | ||||||
| | `docker system info`   | [`podman system info`](./docs/podman-system-info.1.md)      | | | `docker system info`   | [`podman system info`](./docs/podman-system-info.1.md)      | | ||||||
| | `docker wait`          | [`podman wait`](./docs/podman-wait.1.md)		       | | | `docker wait`          | [`podman wait`](./docs/podman-wait.1.md)		       | | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue