diff --git a/cli/command/formatter/buildcache.go b/cli/command/formatter/buildcache.go new file mode 100644 index 0000000000..9e9d10bd68 --- /dev/null +++ b/cli/command/formatter/buildcache.go @@ -0,0 +1,165 @@ +package formatter + +import ( + "fmt" + "sort" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/go-units" +) + +const ( + defaultBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}\t{{.Description}}" + + cacheIDHeader = "CACHE ID" + parentHeader = "PARENT" + lastUsedSinceHeader = "LAST USED" + usageCountHeader = "USAGE" + inUseHeader = "IN USE" + sharedHeader = "SHARED" +) + +// NewBuildCacheFormat returns a Format for rendering using a Context +func NewBuildCacheFormat(source string, quiet bool) Format { + switch source { + case TableFormatKey: + if quiet { + return defaultQuietFormat + } + return Format(defaultBuildCacheTableFormat) + case RawFormatKey: + if quiet { + return `build_cache_id: {{.ID}}` + } + format := `build_cache_id: {{.ID}} +parent_id: {{.Parent}} +type: {{.Type}} +description: {{.Description}} +created_at: {{.CreatedSince}} +last_used_at: {{.LastUsedSince}} +usage_count: {{.UsageCount}} +in_use: {{.InUse}} +shared: {{.Shared}} +` + return Format(format) + } + return Format(source) +} + +func buildCacheSort(buildCache []*types.BuildCache) { + sort.Slice(buildCache, func(i, j int) bool { + lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt + switch { + case lui == nil && luj == nil: + return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0 + case lui == nil: + return true + case luj == nil: + return false + case lui.Equal(*luj): + return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0 + default: + return lui.Before(*luj) + } + }) +} + +// BuildCacheWrite renders the context for a list of containers +func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error { + render := func(format func(subContext subContext) error) error { + buildCacheSort(buildCaches) + for _, bc := range buildCaches { + err := format(&buildCacheContext{trunc: ctx.Trunc, v: bc}) + if err != nil { + return err + } + } + return nil + } + return ctx.Write(newBuildCacheContext(), render) +} + +type buildCacheHeaderContext map[string]string + +type buildCacheContext struct { + HeaderContext + trunc bool + v *types.BuildCache +} + +func newBuildCacheContext() *buildCacheContext { + buildCacheCtx := buildCacheContext{} + buildCacheCtx.header = buildCacheHeaderContext{ + "ID": cacheIDHeader, + "Parent": parentHeader, + "Type": typeHeader, + "Size": sizeHeader, + "CreatedSince": createdSinceHeader, + "LastUsedSince": lastUsedSinceHeader, + "UsageCount": usageCountHeader, + "InUse": inUseHeader, + "Shared": sharedHeader, + "Description": descriptionHeader, + } + return &buildCacheCtx +} + +func (c *buildCacheContext) MarshalJSON() ([]byte, error) { + return marshalJSON(c) +} + +func (c *buildCacheContext) ID() string { + id := c.v.ID + if c.trunc { + id = stringid.TruncateID(c.v.ID) + } + if c.v.InUse { + return id + "*" + } + return id +} + +func (c *buildCacheContext) Parent() string { + if c.trunc { + return stringid.TruncateID(c.v.Parent) + } + return c.v.Parent +} + +func (c *buildCacheContext) Type() string { + return c.v.Type +} + +func (c *buildCacheContext) Description() string { + return c.v.Description +} + +func (c *buildCacheContext) Size() string { + return units.HumanSizeWithPrecision(float64(c.v.Size), 3) +} + +func (c *buildCacheContext) CreatedSince() string { + return units.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago" +} + +func (c *buildCacheContext) LastUsedSince() string { + if c.v.LastUsedAt == nil { + return "" + } + return units.HumanDuration(time.Now().UTC().Sub(*c.v.LastUsedAt)) + " ago" +} + +func (c *buildCacheContext) UsageCount() string { + return fmt.Sprintf("%d", c.v.UsageCount) +} + +func (c *buildCacheContext) InUse() string { + return fmt.Sprintf("%t", c.v.InUse) +} + +func (c *buildCacheContext) Shared() string { + return fmt.Sprintf("%t", c.v.Shared) +} diff --git a/cli/command/formatter/disk_usage.go b/cli/command/formatter/disk_usage.go index e7cd5166c2..3af0494395 100644 --- a/cli/command/formatter/disk_usage.go +++ b/cli/command/formatter/disk_usage.go @@ -12,22 +12,11 @@ import ( ) const ( - defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}" - defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Names}}" - defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}" - defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}" - defaultBuildCacheVerboseFormat = ` -ID: {{.ID}} -Parent: {{.Parent}} -Type: {{.Type}} -Description: {{.Description}} -Size: {{.Size}} -CreatedAt: {{.CreatedAt}} -LastUsedAt: {{.LastUsedAt}} -UsageCount: {{.UsageCount}} -InUse: {{.InUse}} -Shared: {{.Shared}} -` + defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}" + defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}" + defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}" + defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}" + defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}" typeHeader = "TYPE" totalHeader = "TOTAL" @@ -35,7 +24,7 @@ Shared: {{.Shared}} reclaimableHeader = "RECLAIMABLE" containersHeader = "CONTAINERS" sharedSizeHeader = "SHARED SIZE" - uniqueSizeHeader = "UNIQUE SiZE" + uniqueSizeHeader = "UNIQUE SIZE" ) // DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct. @@ -59,7 +48,6 @@ func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, return ctx.parseFormat() } -// // NewDiskUsageFormat returns a format for rendering an DiskUsageContext func NewDiskUsageFormat(source string) Format { switch source { @@ -132,6 +120,7 @@ func (ctx *DiskUsageContext) Write() (err error) { return err } +// nolint: gocyclo func (ctx *DiskUsageContext) verboseWrite() error { // First images tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat) @@ -199,11 +188,17 @@ func (ctx *DiskUsageContext) verboseWrite() error { // And build cache fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize))) - t := template.Must(template.New("buildcache").Parse(defaultBuildCacheVerboseFormat)) - - for _, v := range ctx.BuildCache { - t.Execute(ctx.Output, *v) + tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat) + if err != nil { + return err } + buildCacheSort(ctx.BuildCache) + for _, v := range ctx.BuildCache { + if err := ctx.contextFormat(tmpl, &buildCacheContext{v: v, trunc: true}); err != nil { + return err + } + } + ctx.postFormat(tmpl, newBuildCacheContext()) return nil } diff --git a/cli/command/formatter/disk_usage_test.go b/cli/command/formatter/disk_usage_test.go index 878aef0549..3bc13ebc61 100644 --- a/cli/command/formatter/disk_usage_test.go +++ b/cli/command/formatter/disk_usage_test.go @@ -32,11 +32,11 @@ Build Cache 0 0 0B DiskUsageContext{Verbose: true}, `Images space usage: -REPOSITORY TAG IMAGE ID CREATED ago SIZE SHARED SIZE UNIQUE SiZE CONTAINERS +REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS Containers space usage: -CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED ago STATUS NAMES +CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED STATUS NAMES Local Volumes space usage: @@ -44,6 +44,7 @@ VOLUME NAME LINKS SIZE Build cache usage: 0B +CACHE ID TYPE SIZE CREATED LAST USED USAGE SHARED `, }, // Errors diff --git a/cli/command/system/df.go b/cli/command/system/df.go index 43a2d74ca3..8da9e6dfdc 100644 --- a/cli/command/system/df.go +++ b/cli/command/system/df.go @@ -52,13 +52,20 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error { format = formatter.TableFormatKey } + var bsz int64 + for _, bc := range du.BuildCache { + if !bc.Shared { + bsz += bc.Size + } + } + duCtx := formatter.DiskUsageContext{ Context: formatter.Context{ Output: dockerCli.Out(), Format: formatter.NewDiskUsageFormat(format), }, LayersSize: du.LayersSize, - BuilderSize: du.BuilderSize, + BuilderSize: bsz, BuildCache: du.BuildCache, Images: du.Images, Containers: du.Containers,