github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/system_df.go (about)

     1  //+build !remoteclient
     2  
     3  package main
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/containers/buildah/pkg/formats"
    14  	"github.com/containers/libpod/cmd/podman/cliconfig"
    15  	"github.com/containers/libpod/cmd/podman/libpodruntime"
    16  	"github.com/containers/libpod/libpod"
    17  	"github.com/containers/libpod/libpod/define"
    18  	"github.com/containers/libpod/libpod/image"
    19  	"github.com/docker/go-units"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus"
    22  	"github.com/spf13/cobra"
    23  )
    24  
    25  var (
    26  	dfSystemCommand     cliconfig.SystemDfValues
    27  	dfSystemDescription = `
    28  	podman system df
    29  
    30  	Show podman disk usage
    31  	`
    32  	_dfSystemCommand = &cobra.Command{
    33  		Use:   "df",
    34  		Args:  noSubArgs,
    35  		Short: "Show podman disk usage",
    36  		Long:  dfSystemDescription,
    37  		RunE: func(cmd *cobra.Command, args []string) error {
    38  			dfSystemCommand.GlobalFlags = MainGlobalOpts
    39  			dfSystemCommand.Remote = remoteclient
    40  			return dfSystemCmd(&dfSystemCommand)
    41  		},
    42  	}
    43  )
    44  
    45  type dfMetaData struct {
    46  	images                   []*image.Image
    47  	containers               []*libpod.Container
    48  	activeContainers         map[string]*libpod.Container
    49  	imagesUsedbyCtrMap       map[string][]*libpod.Container
    50  	imagesUsedbyActiveCtr    map[string][]*libpod.Container
    51  	volumes                  []*libpod.Volume
    52  	volumeUsedByContainerMap map[string][]*libpod.Container
    53  }
    54  
    55  type systemDfDiskUsage struct {
    56  	Type        string
    57  	Total       int
    58  	Active      int
    59  	Size        string
    60  	Reclaimable string
    61  }
    62  
    63  type imageVerboseDiskUsage struct {
    64  	Repository string
    65  	Tag        string
    66  	ImageID    string
    67  	Created    string
    68  	Size       string
    69  	SharedSize string
    70  	UniqueSize string
    71  	Containers int
    72  }
    73  
    74  type containerVerboseDiskUsage struct {
    75  	ContainerID  string
    76  	Image        string
    77  	Command      string
    78  	LocalVolumes int
    79  	Size         string
    80  	Created      string
    81  	Status       string
    82  	Names        string
    83  }
    84  
    85  type volumeVerboseDiskUsage struct {
    86  	VolumeName string
    87  	Links      int
    88  	Size       string
    89  }
    90  
    91  const systemDfDefaultFormat string = "table {{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
    92  const imageVerboseFormat string = "table {{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
    93  const containerVerboseFormat string = "table {{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.Created}}\t{{.Status}}\t{{.Names}}"
    94  const volumeVerboseFormat string = "table {{.VolumeName}}\t{{.Links}}\t{{.Size}}"
    95  
    96  func init() {
    97  	dfSystemCommand.Command = _dfSystemCommand
    98  	dfSystemCommand.SetUsageTemplate(UsageTemplate())
    99  	flags := dfSystemCommand.Flags()
   100  	flags.BoolVarP(&dfSystemCommand.Verbose, "verbose", "v", false, "Show detailed information on space usage")
   101  	flags.StringVar(&dfSystemCommand.Format, "format", "", "Pretty-print images using a Go template")
   102  }
   103  
   104  func dfSystemCmd(c *cliconfig.SystemDfValues) error {
   105  	runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand)
   106  	if err != nil {
   107  		return errors.Wrapf(err, "Could not get runtime")
   108  	}
   109  	defer runtime.DeferredShutdown(false)
   110  
   111  	ctx := getContext()
   112  
   113  	metaData, err := getDfMetaData(ctx, runtime)
   114  	if err != nil {
   115  		return errors.Wrapf(err, "error getting disk usage data")
   116  	}
   117  
   118  	if c.Verbose {
   119  		err := verboseOutput(ctx, metaData)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		return nil
   124  	}
   125  
   126  	systemDfDiskUsages, err := getDiskUsage(ctx, runtime, metaData)
   127  	if err != nil {
   128  		return errors.Wrapf(err, "error getting output of system df")
   129  	}
   130  	format := systemDfDefaultFormat
   131  	if c.Format != "" {
   132  		format = strings.Replace(c.Format, `\t`, "\t", -1)
   133  	}
   134  	return generateSysDfOutput(systemDfDiskUsages, format)
   135  }
   136  
   137  func generateSysDfOutput(systemDfDiskUsages []systemDfDiskUsage, format string) error {
   138  	var systemDfHeader = map[string]string{
   139  		"Type":        "TYPE",
   140  		"Total":       "TOTAL",
   141  		"Active":      "ACTIVE",
   142  		"Size":        "SIZE",
   143  		"Reclaimable": "RECLAIMABLE",
   144  	}
   145  	out := formats.StdoutTemplateArray{Output: systemDfDiskUsageToGeneric(systemDfDiskUsages), Template: format, Fields: systemDfHeader}
   146  	return out.Out()
   147  }
   148  
   149  func getDiskUsage(ctx context.Context, runtime *libpod.Runtime, metaData dfMetaData) ([]systemDfDiskUsage, error) {
   150  	imageDiskUsage, err := getImageDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap, metaData.imagesUsedbyActiveCtr)
   151  	if err != nil {
   152  		return nil, errors.Wrapf(err, "error getting disk usage of images")
   153  	}
   154  	containerDiskUsage, err := getContainerDiskUsage(metaData.containers, metaData.activeContainers)
   155  	if err != nil {
   156  		return nil, errors.Wrapf(err, "error getting disk usage of containers")
   157  	}
   158  	volumeDiskUsage, err := getVolumeDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap)
   159  	if err != nil {
   160  		return nil, errors.Wrapf(err, "error getting disk usage of volumess")
   161  	}
   162  
   163  	systemDfDiskUsages := []systemDfDiskUsage{imageDiskUsage, containerDiskUsage, volumeDiskUsage}
   164  	return systemDfDiskUsages, nil
   165  }
   166  
   167  func getDfMetaData(ctx context.Context, runtime *libpod.Runtime) (dfMetaData, error) {
   168  	var metaData dfMetaData
   169  	images, err := runtime.ImageRuntime().GetImages()
   170  	if err != nil {
   171  		return metaData, errors.Wrapf(err, "unable to get images")
   172  	}
   173  	containers, err := runtime.GetAllContainers()
   174  	if err != nil {
   175  		return metaData, errors.Wrapf(err, "error getting all containers")
   176  	}
   177  	volumes, err := runtime.GetAllVolumes()
   178  	if err != nil {
   179  		return metaData, errors.Wrap(err, "error getting all volumes")
   180  	}
   181  	activeContainers, err := activeContainers(containers)
   182  	if err != nil {
   183  		return metaData, errors.Wrapf(err, "error getting active containers")
   184  	}
   185  	imagesUsedbyCtrMap, imagesUsedbyActiveCtr, err := imagesUsedbyCtr(containers, activeContainers)
   186  	if err != nil {
   187  		return metaData, errors.Wrapf(err, "error getting getting images used by containers")
   188  	}
   189  	metaData = dfMetaData{
   190  		images:                   images,
   191  		containers:               containers,
   192  		activeContainers:         activeContainers,
   193  		imagesUsedbyCtrMap:       imagesUsedbyCtrMap,
   194  		imagesUsedbyActiveCtr:    imagesUsedbyActiveCtr,
   195  		volumes:                  volumes,
   196  		volumeUsedByContainerMap: volumeUsedByContainer(containers),
   197  	}
   198  	return metaData, nil
   199  }
   200  
   201  func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uint64, error) {
   202  	imgUniqueSizeMap := make(map[string]uint64)
   203  	for _, img := range images {
   204  		parentImg := img
   205  		for {
   206  			next, err := parentImg.GetParent(ctx)
   207  			if err != nil {
   208  				return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID())
   209  			}
   210  			if next == nil {
   211  				break
   212  			}
   213  			parentImg = next
   214  		}
   215  		imgSize, err := img.Size(ctx)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		if img.ID() == parentImg.ID() {
   220  			imgUniqueSizeMap[img.ID()] = *imgSize
   221  		} else {
   222  			parentImgSize, err := parentImg.Size(ctx)
   223  			if err != nil {
   224  				return nil, errors.Wrapf(err, "error getting size of parent image %s", parentImg.ID())
   225  			}
   226  			imgUniqueSizeMap[img.ID()] = *imgSize - *parentImgSize
   227  		}
   228  	}
   229  	return imgUniqueSizeMap, nil
   230  }
   231  
   232  func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) {
   233  	var (
   234  		numberOfImages       int
   235  		sumSize              uint64
   236  		numberOfActiveImages int
   237  		unreclaimableSize    uint64
   238  		imageDiskUsage       systemDfDiskUsage
   239  		reclaimableStr       string
   240  	)
   241  
   242  	imgUniqueSizeMap, err := imageUniqueSize(ctx, images)
   243  	if err != nil {
   244  		return imageDiskUsage, errors.Wrapf(err, "error getting unique size of images")
   245  	}
   246  
   247  	for _, img := range images {
   248  
   249  		unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap)
   250  
   251  		isParent, err := img.IsParent(ctx)
   252  		if err != nil {
   253  			return imageDiskUsage, err
   254  		}
   255  		parent, err := img.GetParent(ctx)
   256  		if err != nil {
   257  			return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
   258  		}
   259  		if isParent && parent != nil {
   260  			continue
   261  		}
   262  		numberOfImages++
   263  		if _, isActive := imageUsedbyCintainerMap[img.ID()]; isActive {
   264  			numberOfActiveImages++
   265  		}
   266  
   267  		if !isParent {
   268  			size, err := img.Size(ctx)
   269  			if err != nil {
   270  				return imageDiskUsage, errors.Wrapf(err, "error getting disk usage of image %s", img.ID())
   271  			}
   272  			sumSize += *size
   273  		}
   274  
   275  	}
   276  	sumSizeStr := units.HumanSizeWithPrecision(float64(sumSize), 3)
   277  	reclaimable := sumSize - unreclaimableSize
   278  	if sumSize != 0 {
   279  		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize)
   280  	} else {
   281  		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0)
   282  	}
   283  	imageDiskUsage = systemDfDiskUsage{
   284  		Type:        "Images",
   285  		Total:       numberOfImages,
   286  		Active:      numberOfActiveImages,
   287  		Size:        sumSizeStr,
   288  		Reclaimable: reclaimableStr,
   289  	}
   290  	return imageDiskUsage, nil
   291  }
   292  
   293  func imageUsedSize(img *image.Image, imgUniqueSizeMap map[string]uint64, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) uint64 {
   294  	var usedSize uint64
   295  	imgUnique := imgUniqueSizeMap[img.ID()]
   296  	if _, isCtrActive := imageUsedbyActiveContainerMap[img.ID()]; isCtrActive {
   297  		return imgUnique
   298  	}
   299  	containers := imageUsedbyCintainerMap[img.ID()]
   300  	for _, ctr := range containers {
   301  		if len(ctr.UserVolumes()) > 0 {
   302  			usedSize += imgUnique
   303  			return usedSize
   304  		}
   305  	}
   306  	return usedSize
   307  }
   308  
   309  func imagesUsedbyCtr(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (map[string][]*libpod.Container, map[string][]*libpod.Container, error) {
   310  	imgCtrMap := make(map[string][]*libpod.Container)
   311  	imgActiveCtrMap := make(map[string][]*libpod.Container)
   312  	for _, ctr := range containers {
   313  		imgID, _ := ctr.Image()
   314  		imgCtrMap[imgID] = append(imgCtrMap[imgID], ctr)
   315  		if _, isActive := activeContainers[ctr.ID()]; isActive {
   316  			imgActiveCtrMap[imgID] = append(imgActiveCtrMap[imgID], ctr)
   317  		}
   318  	}
   319  	return imgCtrMap, imgActiveCtrMap, nil
   320  }
   321  
   322  func getContainerDiskUsage(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (systemDfDiskUsage, error) {
   323  	var (
   324  		sumSize           int64
   325  		unreclaimableSize int64
   326  		reclaimableStr    string
   327  	)
   328  	for _, ctr := range containers {
   329  		size, err := ctr.RWSize()
   330  		if err != nil {
   331  			return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of container %s", ctr.ID())
   332  		}
   333  		sumSize += size
   334  	}
   335  	for _, activeCtr := range activeContainers {
   336  		size, err := activeCtr.RWSize()
   337  		if err != nil {
   338  			return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of active container %s", activeCtr.ID())
   339  		}
   340  		unreclaimableSize += size
   341  	}
   342  	if sumSize == 0 {
   343  		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(0, 3), 0)
   344  	} else {
   345  		reclaimable := sumSize - unreclaimableSize
   346  		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize)
   347  	}
   348  	containerDiskUsage := systemDfDiskUsage{
   349  		Type:        "Containers",
   350  		Total:       len(containers),
   351  		Active:      len(activeContainers),
   352  		Size:        units.HumanSizeWithPrecision(float64(sumSize), 3),
   353  		Reclaimable: reclaimableStr,
   354  	}
   355  	return containerDiskUsage, nil
   356  }
   357  
   358  func ctrIsActive(ctr *libpod.Container) (bool, error) {
   359  	state, err := ctr.State()
   360  	if err != nil {
   361  		return false, err
   362  	}
   363  	return state == define.ContainerStatePaused || state == define.ContainerStateRunning, nil
   364  }
   365  
   366  func activeContainers(containers []*libpod.Container) (map[string]*libpod.Container, error) {
   367  	activeContainers := make(map[string]*libpod.Container)
   368  	for _, aCtr := range containers {
   369  		isActive, err := ctrIsActive(aCtr)
   370  		if err != nil {
   371  			return nil, err
   372  		}
   373  		if isActive {
   374  			activeContainers[aCtr.ID()] = aCtr
   375  		}
   376  	}
   377  	return activeContainers, nil
   378  }
   379  
   380  func getVolumeDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) {
   381  	var (
   382  		sumSize           int64
   383  		unreclaimableSize int64
   384  		reclaimableStr    string
   385  	)
   386  	for _, volume := range volumes {
   387  		size, err := volumeSize(volume)
   388  		if err != nil {
   389  			return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of volime %s", volume.Name())
   390  		}
   391  		sumSize += size
   392  		if _, exist := volumeUsedByContainerMap[volume.Name()]; exist {
   393  			unreclaimableSize += size
   394  		}
   395  	}
   396  	reclaimable := sumSize - unreclaimableSize
   397  	if sumSize != 0 {
   398  		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize)
   399  	} else {
   400  		reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0)
   401  	}
   402  	volumesDiskUsage := systemDfDiskUsage{
   403  		Type:        "Local Volumes",
   404  		Total:       len(volumes),
   405  		Active:      len(volumeUsedByContainerMap),
   406  		Size:        units.HumanSizeWithPrecision(float64(sumSize), 3),
   407  		Reclaimable: reclaimableStr,
   408  	}
   409  	return volumesDiskUsage, nil
   410  }
   411  
   412  func volumeUsedByContainer(containers []*libpod.Container) map[string][]*libpod.Container {
   413  	volumeUsedByContainerMap := make(map[string][]*libpod.Container)
   414  	for _, ctr := range containers {
   415  
   416  		ctrVolumes := ctr.UserVolumes()
   417  		for _, ctrVolume := range ctrVolumes {
   418  			volumeUsedByContainerMap[ctrVolume] = append(volumeUsedByContainerMap[ctrVolume], ctr)
   419  		}
   420  	}
   421  	return volumeUsedByContainerMap
   422  }
   423  
   424  func volumeSize(volume *libpod.Volume) (int64, error) {
   425  	var size int64
   426  	err := filepath.Walk(volume.MountPoint(), func(path string, info os.FileInfo, err error) error {
   427  		if err == nil && !info.IsDir() {
   428  			size += info.Size()
   429  		}
   430  		return err
   431  	})
   432  	return size, err
   433  }
   434  
   435  func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, imagesUsedbyCtr map[string][]*libpod.Container) ([]imageVerboseDiskUsage, error) {
   436  	var imagesVerboseDiskUsage []imageVerboseDiskUsage
   437  	imgUniqueSizeMap, err := imageUniqueSize(ctx, images)
   438  	if err != nil {
   439  		return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images")
   440  	}
   441  	for _, img := range images {
   442  		isParent, err := img.IsParent(ctx)
   443  		if err != nil {
   444  			return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID())
   445  		}
   446  		parent, err := img.GetParent(ctx)
   447  		if err != nil {
   448  			return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID())
   449  		}
   450  		if isParent && parent != nil {
   451  			continue
   452  		}
   453  		size, err := img.Size(ctx)
   454  		if err != nil {
   455  			return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting size of image %s", img.ID())
   456  		}
   457  		numberOfContainers := 0
   458  		if ctrs, exist := imagesUsedbyCtr[img.ID()]; exist {
   459  			numberOfContainers = len(ctrs)
   460  		}
   461  		var repo string
   462  		var tag string
   463  		var repotags []string
   464  		if len(img.Names()) != 0 {
   465  			repotags = []string{img.Names()[0]}
   466  		}
   467  		repopairs, err := image.ReposToMap(repotags)
   468  		if err != nil {
   469  			logrus.Errorf("error finding tag/digest for %s", img.ID())
   470  		}
   471  		for reponame, tags := range repopairs {
   472  			for _, tagname := range tags {
   473  				repo = reponame
   474  				tag = tagname
   475  			}
   476  		}
   477  
   478  		imageVerbosedf := imageVerboseDiskUsage{
   479  			Repository: repo,
   480  			Tag:        tag,
   481  			ImageID:    shortID(img.ID()),
   482  			Created:    fmt.Sprintf("%s ago", units.HumanDuration(time.Since((img.Created().Local())))),
   483  			Size:       units.HumanSizeWithPrecision(float64(*size), 3),
   484  			SharedSize: units.HumanSizeWithPrecision(float64(*size-imgUniqueSizeMap[img.ID()]), 3),
   485  			UniqueSize: units.HumanSizeWithPrecision(float64(imgUniqueSizeMap[img.ID()]), 3),
   486  			Containers: numberOfContainers,
   487  		}
   488  		imagesVerboseDiskUsage = append(imagesVerboseDiskUsage, imageVerbosedf)
   489  	}
   490  	return imagesVerboseDiskUsage, nil
   491  }
   492  
   493  func getContainerVerboseDiskUsage(containers []*libpod.Container) (containersVerboseDiskUsage []containerVerboseDiskUsage, err error) {
   494  	for _, ctr := range containers {
   495  		imgID, _ := ctr.Image()
   496  		size, err := ctr.RWSize()
   497  		if err != nil {
   498  			return containersVerboseDiskUsage, errors.Wrapf(err, "error getting size of container %s", ctr.ID())
   499  		}
   500  		state, err := ctr.State()
   501  		if err != nil {
   502  			return containersVerboseDiskUsage, errors.Wrapf(err, "error getting the state of container %s", ctr.ID())
   503  		}
   504  
   505  		ctrVerboseData := containerVerboseDiskUsage{
   506  			ContainerID:  shortID(ctr.ID()),
   507  			Image:        shortImageID(imgID),
   508  			Command:      strings.Join(ctr.Command(), " "),
   509  			LocalVolumes: len(ctr.UserVolumes()),
   510  			Size:         units.HumanSizeWithPrecision(float64(size), 3),
   511  			Created:      fmt.Sprintf("%s ago", units.HumanDuration(time.Since(ctr.CreatedTime().Local()))),
   512  			Status:       state.String(),
   513  			Names:        ctr.Name(),
   514  		}
   515  		containersVerboseDiskUsage = append(containersVerboseDiskUsage, ctrVerboseData)
   516  
   517  	}
   518  	return containersVerboseDiskUsage, nil
   519  }
   520  
   521  func getVolumeVerboseDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (volumesVerboseDiskUsage []volumeVerboseDiskUsage, err error) {
   522  	for _, vol := range volumes {
   523  		volSize, err := volumeSize(vol)
   524  		if err != nil {
   525  			return volumesVerboseDiskUsage, errors.Wrapf(err, "error getting size of volume %s", vol.Name())
   526  		}
   527  		links := 0
   528  		if linkCtr, exist := volumeUsedByContainerMap[vol.Name()]; exist {
   529  			links = len(linkCtr)
   530  		}
   531  		volumeVerboseData := volumeVerboseDiskUsage{
   532  			VolumeName: vol.Name(),
   533  			Links:      links,
   534  			Size:       units.HumanSizeWithPrecision(float64(volSize), 3),
   535  		}
   536  		volumesVerboseDiskUsage = append(volumesVerboseDiskUsage, volumeVerboseData)
   537  	}
   538  	return volumesVerboseDiskUsage, nil
   539  }
   540  
   541  func imagesVerboseOutput(ctx context.Context, metaData dfMetaData) error {
   542  	var imageVerboseHeader = map[string]string{
   543  		"Repository": "REPOSITORY",
   544  		"Tag":        "TAG",
   545  		"ImageID":    "IMAGE ID",
   546  		"Created":    "CREATED",
   547  		"Size":       "SIZE",
   548  		"SharedSize": "SHARED SIZE",
   549  		"UniqueSize": "UNIQUE SIZE",
   550  		"Containers": "CONTAINERS",
   551  	}
   552  	imagesVerboseDiskUsage, err := getImageVerboseDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap)
   553  	if err != nil {
   554  		return errors.Wrapf(err, "error getting verbose output of images")
   555  	}
   556  	if _, err := os.Stderr.WriteString("Images space usage:\n\n"); err != nil {
   557  		return err
   558  	}
   559  	out := formats.StdoutTemplateArray{Output: systemDfImageVerboseDiskUsageToGeneric(imagesVerboseDiskUsage), Template: imageVerboseFormat, Fields: imageVerboseHeader}
   560  	return out.Out()
   561  }
   562  
   563  func containersVerboseOutput(ctx context.Context, metaData dfMetaData) error {
   564  	var containerVerboseHeader = map[string]string{
   565  		"ContainerID":  "CONTAINER ID ",
   566  		"Image":        "IMAGE",
   567  		"Command":      "COMMAND",
   568  		"LocalVolumes": "LOCAL VOLUMES",
   569  		"Size":         "SIZE",
   570  		"Created":      "CREATED",
   571  		"Status":       "STATUS",
   572  		"Names":        "NAMES",
   573  	}
   574  	containersVerboseDiskUsage, err := getContainerVerboseDiskUsage(metaData.containers)
   575  	if err != nil {
   576  		return errors.Wrapf(err, "error getting verbose output of containers")
   577  	}
   578  	if _, err := os.Stderr.WriteString("\nContainers space usage:\n\n"); err != nil {
   579  		return err
   580  	}
   581  	out := formats.StdoutTemplateArray{Output: systemDfContainerVerboseDiskUsageToGeneric(containersVerboseDiskUsage), Template: containerVerboseFormat, Fields: containerVerboseHeader}
   582  	return out.Out()
   583  
   584  }
   585  
   586  func volumesVerboseOutput(ctx context.Context, metaData dfMetaData) error {
   587  	var volumeVerboseHeader = map[string]string{
   588  		"VolumeName": "VOLUME NAME",
   589  		"Links":      "LINKS",
   590  		"Size":       "SIZE",
   591  	}
   592  	volumesVerboseDiskUsage, err := getVolumeVerboseDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap)
   593  	if err != nil {
   594  		return errors.Wrapf(err, "error getting verbose output of volumes")
   595  	}
   596  	if _, err := os.Stderr.WriteString("\nLocal Volumes space usage:\n\n"); err != nil {
   597  		return err
   598  	}
   599  	out := formats.StdoutTemplateArray{Output: systemDfVolumeVerboseDiskUsageToGeneric(volumesVerboseDiskUsage), Template: volumeVerboseFormat, Fields: volumeVerboseHeader}
   600  	return out.Out()
   601  }
   602  
   603  func verboseOutput(ctx context.Context, metaData dfMetaData) error {
   604  	if err := imagesVerboseOutput(ctx, metaData); err != nil {
   605  		return err
   606  	}
   607  	if err := containersVerboseOutput(ctx, metaData); err != nil {
   608  		return err
   609  	}
   610  	if err := volumesVerboseOutput(ctx, metaData); err != nil {
   611  		return err
   612  	}
   613  	return nil
   614  }
   615  
   616  func systemDfDiskUsageToGeneric(diskUsages []systemDfDiskUsage) (out []interface{}) {
   617  	for _, usage := range diskUsages {
   618  		out = append(out, interface{}(usage))
   619  	}
   620  	return out
   621  }
   622  
   623  func systemDfImageVerboseDiskUsageToGeneric(diskUsages []imageVerboseDiskUsage) (out []interface{}) {
   624  	for _, usage := range diskUsages {
   625  		out = append(out, interface{}(usage))
   626  	}
   627  	return out
   628  }
   629  
   630  func systemDfContainerVerboseDiskUsageToGeneric(diskUsages []containerVerboseDiskUsage) (out []interface{}) {
   631  	for _, usage := range diskUsages {
   632  		out = append(out, interface{}(usage))
   633  	}
   634  	return out
   635  }
   636  
   637  func systemDfVolumeVerboseDiskUsageToGeneric(diskUsages []volumeVerboseDiskUsage) (out []interface{}) {
   638  	for _, usage := range diskUsages {
   639  		out = append(out, interface{}(usage))
   640  	}
   641  	return out
   642  }
   643  
   644  func shortImageID(id string) string {
   645  	const imageIDTruncLength int = 4
   646  	if len(id) > imageIDTruncLength {
   647  		return id[:imageIDTruncLength]
   648  	}
   649  	return id
   650  }