github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/formatter/disk_usage.go (about)

     1  package formatter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"text/template"
     9  
    10  	"github.com/distribution/reference"
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/image"
    13  	"github.com/docker/docker/api/types/volume"
    14  	units "github.com/docker/go-units"
    15  )
    16  
    17  const (
    18  	defaultDiskUsageImageTableFormat      = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
    19  	defaultDiskUsageContainerTableFormat  = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
    20  	defaultDiskUsageVolumeTableFormat     = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
    21  	defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
    22  	defaultDiskUsageTableFormat           = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
    23  
    24  	typeHeader        = "TYPE"
    25  	totalHeader       = "TOTAL"
    26  	activeHeader      = "ACTIVE"
    27  	reclaimableHeader = "RECLAIMABLE"
    28  	containersHeader  = "CONTAINERS"
    29  	sharedSizeHeader  = "SHARED SIZE"
    30  	uniqueSizeHeader  = "UNIQUE SIZE"
    31  )
    32  
    33  // DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
    34  type DiskUsageContext struct {
    35  	Context
    36  	Verbose     bool
    37  	LayersSize  int64
    38  	Images      []*image.Summary
    39  	Containers  []*types.Container
    40  	Volumes     []*volume.Volume
    41  	BuildCache  []*types.BuildCache
    42  	BuilderSize int64
    43  }
    44  
    45  func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
    46  	ctx.buffer = bytes.NewBufferString("")
    47  	ctx.header = ""
    48  	ctx.Format = Format(format)
    49  	ctx.preFormat()
    50  
    51  	return ctx.parseFormat()
    52  }
    53  
    54  // NewDiskUsageFormat returns a format for rendering an DiskUsageContext
    55  func NewDiskUsageFormat(source string, verbose bool) Format {
    56  	switch {
    57  	case verbose && source == RawFormatKey:
    58  		format := `{{range .Images}}type: Image
    59  ` + NewImageFormat(source, false, true) + `
    60  {{end -}}
    61  {{range .Containers}}type: Container
    62  ` + NewContainerFormat(source, false, true) + `
    63  {{end -}}
    64  {{range .Volumes}}type: Volume
    65  ` + NewVolumeFormat(source, false) + `
    66  {{end -}}
    67  {{range .BuildCache}}type: Build Cache
    68  ` + NewBuildCacheFormat(source, false) + `
    69  {{end -}}`
    70  		return format
    71  	case !verbose && source == TableFormatKey:
    72  		return Format(defaultDiskUsageTableFormat)
    73  	case !verbose && source == RawFormatKey:
    74  		format := `type: {{.Type}}
    75  total: {{.TotalCount}}
    76  active: {{.Active}}
    77  size: {{.Size}}
    78  reclaimable: {{.Reclaimable}}
    79  `
    80  		return Format(format)
    81  	default:
    82  		return Format(source)
    83  	}
    84  }
    85  
    86  func (ctx *DiskUsageContext) Write() (err error) {
    87  	if ctx.Verbose {
    88  		return ctx.verboseWrite()
    89  	}
    90  	ctx.buffer = bytes.NewBufferString("")
    91  	ctx.preFormat()
    92  
    93  	tmpl, err := ctx.parseFormat()
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
    99  		totalSize: ctx.LayersSize,
   100  		images:    ctx.Images,
   101  	})
   102  	if err != nil {
   103  		return err
   104  	}
   105  	err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
   106  		containers: ctx.Containers,
   107  	})
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
   113  		volumes: ctx.Volumes,
   114  	})
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
   120  		builderSize: ctx.BuilderSize,
   121  		buildCache:  ctx.BuildCache,
   122  	})
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
   128  	diskUsageContainersCtx.Header = SubHeaderContext{
   129  		"Type":        typeHeader,
   130  		"TotalCount":  totalHeader,
   131  		"Active":      activeHeader,
   132  		"Size":        SizeHeader,
   133  		"Reclaimable": reclaimableHeader,
   134  	}
   135  	ctx.postFormat(tmpl, &diskUsageContainersCtx)
   136  
   137  	return err
   138  }
   139  
   140  type diskUsageContext struct {
   141  	Images     []*imageContext
   142  	Containers []*ContainerContext
   143  	Volumes    []*volumeContext
   144  	BuildCache []*buildCacheContext
   145  }
   146  
   147  func (ctx *DiskUsageContext) verboseWrite() error {
   148  	duc := &diskUsageContext{
   149  		Images:     make([]*imageContext, 0, len(ctx.Images)),
   150  		Containers: make([]*ContainerContext, 0, len(ctx.Containers)),
   151  		Volumes:    make([]*volumeContext, 0, len(ctx.Volumes)),
   152  		BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
   153  	}
   154  	trunc := ctx.Format.IsTable()
   155  
   156  	// First images
   157  	for _, i := range ctx.Images {
   158  		repo := "<none>"
   159  		tag := "<none>"
   160  		if len(i.RepoTags) > 0 && !isDangling(*i) {
   161  			// Only show the first tag
   162  			ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
   163  			if err != nil {
   164  				continue
   165  			}
   166  			if nt, ok := ref.(reference.NamedTagged); ok {
   167  				repo = reference.FamiliarName(ref)
   168  				tag = nt.Tag()
   169  			}
   170  		}
   171  
   172  		duc.Images = append(duc.Images, &imageContext{
   173  			repo:  repo,
   174  			tag:   tag,
   175  			trunc: trunc,
   176  			i:     *i,
   177  		})
   178  	}
   179  
   180  	// Now containers
   181  	for _, c := range ctx.Containers {
   182  		// Don't display the virtual size
   183  		c.SizeRootFs = 0
   184  		duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: *c})
   185  	}
   186  
   187  	// And volumes
   188  	for _, v := range ctx.Volumes {
   189  		duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
   190  	}
   191  
   192  	// And build cache
   193  	buildCacheSort(ctx.BuildCache)
   194  	for _, v := range ctx.BuildCache {
   195  		duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
   196  	}
   197  
   198  	if ctx.Format == TableFormatKey {
   199  		return ctx.verboseWriteTable(duc)
   200  	}
   201  
   202  	ctx.preFormat()
   203  	tmpl, err := ctx.parseFormat()
   204  	if err != nil {
   205  		return err
   206  	}
   207  	return tmpl.Execute(ctx.Output, duc)
   208  }
   209  
   210  func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
   211  	tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	ctx.Output.Write([]byte("Images space usage:\n\n"))
   216  	for _, img := range duc.Images {
   217  		if err := ctx.contextFormat(tmpl, img); err != nil {
   218  			return err
   219  		}
   220  	}
   221  	ctx.postFormat(tmpl, newImageContext())
   222  
   223  	tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
   228  	for _, c := range duc.Containers {
   229  		if err := ctx.contextFormat(tmpl, c); err != nil {
   230  			return err
   231  		}
   232  	}
   233  	ctx.postFormat(tmpl, NewContainerContext())
   234  
   235  	tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
   240  	for _, v := range duc.Volumes {
   241  		if err := ctx.contextFormat(tmpl, v); err != nil {
   242  			return err
   243  		}
   244  	}
   245  	ctx.postFormat(tmpl, newVolumeContext())
   246  
   247  	tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat)
   248  	if err != nil {
   249  		return err
   250  	}
   251  	fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
   252  	for _, v := range duc.BuildCache {
   253  		if err := ctx.contextFormat(tmpl, v); err != nil {
   254  			return err
   255  		}
   256  	}
   257  	ctx.postFormat(tmpl, newBuildCacheContext())
   258  
   259  	return nil
   260  }
   261  
   262  type diskUsageImagesContext struct {
   263  	HeaderContext
   264  	totalSize int64
   265  	images    []*image.Summary
   266  }
   267  
   268  func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
   269  	return MarshalJSON(c)
   270  }
   271  
   272  func (c *diskUsageImagesContext) Type() string {
   273  	return "Images"
   274  }
   275  
   276  func (c *diskUsageImagesContext) TotalCount() string {
   277  	return strconv.Itoa(len(c.images))
   278  }
   279  
   280  func (c *diskUsageImagesContext) Active() string {
   281  	used := 0
   282  	for _, i := range c.images {
   283  		if i.Containers > 0 {
   284  			used++
   285  		}
   286  	}
   287  
   288  	return strconv.Itoa(used)
   289  }
   290  
   291  func (c *diskUsageImagesContext) Size() string {
   292  	return units.HumanSize(float64(c.totalSize))
   293  }
   294  
   295  func (c *diskUsageImagesContext) Reclaimable() string {
   296  	var used int64
   297  
   298  	for _, i := range c.images {
   299  		if i.Containers != 0 {
   300  			if i.Size == -1 || i.SharedSize == -1 {
   301  				continue
   302  			}
   303  			used += i.Size - i.SharedSize
   304  		}
   305  	}
   306  
   307  	reclaimable := c.totalSize - used
   308  	if c.totalSize > 0 {
   309  		return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
   310  	}
   311  	return units.HumanSize(float64(reclaimable))
   312  }
   313  
   314  type diskUsageContainersContext struct {
   315  	HeaderContext
   316  	containers []*types.Container
   317  }
   318  
   319  func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
   320  	return MarshalJSON(c)
   321  }
   322  
   323  func (c *diskUsageContainersContext) Type() string {
   324  	return "Containers"
   325  }
   326  
   327  func (c *diskUsageContainersContext) TotalCount() string {
   328  	return strconv.Itoa(len(c.containers))
   329  }
   330  
   331  func (c *diskUsageContainersContext) isActive(container types.Container) bool {
   332  	return strings.Contains(container.State, "running") ||
   333  		strings.Contains(container.State, "paused") ||
   334  		strings.Contains(container.State, "restarting")
   335  }
   336  
   337  func (c *diskUsageContainersContext) Active() string {
   338  	used := 0
   339  	for _, container := range c.containers {
   340  		if c.isActive(*container) {
   341  			used++
   342  		}
   343  	}
   344  
   345  	return strconv.Itoa(used)
   346  }
   347  
   348  func (c *diskUsageContainersContext) Size() string {
   349  	var size int64
   350  
   351  	for _, container := range c.containers {
   352  		size += container.SizeRw
   353  	}
   354  
   355  	return units.HumanSize(float64(size))
   356  }
   357  
   358  func (c *diskUsageContainersContext) Reclaimable() string {
   359  	var reclaimable int64
   360  	var totalSize int64
   361  
   362  	for _, container := range c.containers {
   363  		if !c.isActive(*container) {
   364  			reclaimable += container.SizeRw
   365  		}
   366  		totalSize += container.SizeRw
   367  	}
   368  
   369  	if totalSize > 0 {
   370  		return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
   371  	}
   372  
   373  	return units.HumanSize(float64(reclaimable))
   374  }
   375  
   376  type diskUsageVolumesContext struct {
   377  	HeaderContext
   378  	volumes []*volume.Volume
   379  }
   380  
   381  func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
   382  	return MarshalJSON(c)
   383  }
   384  
   385  func (c *diskUsageVolumesContext) Type() string {
   386  	return "Local Volumes"
   387  }
   388  
   389  func (c *diskUsageVolumesContext) TotalCount() string {
   390  	return strconv.Itoa(len(c.volumes))
   391  }
   392  
   393  func (c *diskUsageVolumesContext) Active() string {
   394  	used := 0
   395  	for _, v := range c.volumes {
   396  		if v.UsageData.RefCount > 0 {
   397  			used++
   398  		}
   399  	}
   400  
   401  	return strconv.Itoa(used)
   402  }
   403  
   404  func (c *diskUsageVolumesContext) Size() string {
   405  	var size int64
   406  
   407  	for _, v := range c.volumes {
   408  		if v.UsageData.Size != -1 {
   409  			size += v.UsageData.Size
   410  		}
   411  	}
   412  
   413  	return units.HumanSize(float64(size))
   414  }
   415  
   416  func (c *diskUsageVolumesContext) Reclaimable() string {
   417  	var reclaimable int64
   418  	var totalSize int64
   419  
   420  	for _, v := range c.volumes {
   421  		if v.UsageData.Size != -1 {
   422  			if v.UsageData.RefCount == 0 {
   423  				reclaimable += v.UsageData.Size
   424  			}
   425  			totalSize += v.UsageData.Size
   426  		}
   427  	}
   428  
   429  	if totalSize > 0 {
   430  		return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
   431  	}
   432  
   433  	return units.HumanSize(float64(reclaimable))
   434  }
   435  
   436  type diskUsageBuilderContext struct {
   437  	HeaderContext
   438  	builderSize int64
   439  	buildCache  []*types.BuildCache
   440  }
   441  
   442  func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
   443  	return MarshalJSON(c)
   444  }
   445  
   446  func (c *diskUsageBuilderContext) Type() string {
   447  	return "Build Cache"
   448  }
   449  
   450  func (c *diskUsageBuilderContext) TotalCount() string {
   451  	return strconv.Itoa(len(c.buildCache))
   452  }
   453  
   454  func (c *diskUsageBuilderContext) Active() string {
   455  	numActive := 0
   456  	for _, bc := range c.buildCache {
   457  		if bc.InUse {
   458  			numActive++
   459  		}
   460  	}
   461  	return strconv.Itoa(numActive)
   462  }
   463  
   464  func (c *diskUsageBuilderContext) Size() string {
   465  	return units.HumanSize(float64(c.builderSize))
   466  }
   467  
   468  func (c *diskUsageBuilderContext) Reclaimable() string {
   469  	var inUseBytes int64
   470  	for _, bc := range c.buildCache {
   471  		if bc.InUse && !bc.Shared {
   472  			inUseBytes += bc.Size
   473  		}
   474  	}
   475  
   476  	return units.HumanSize(float64(c.builderSize - inUseBytes))
   477  }