diff --git a/cli/command/formatter/buildcache.go b/cli/command/formatter/buildcache.go new file mode 100644 index 000000000000..ea9be823d2f4 --- /dev/null +++ b/cli/command/formatter/buildcache.go @@ -0,0 +1,160 @@ +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{{.InUse}}\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: {{.CreatedAt}} +last_used_at: {{.LastUsedAt}} +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 == luj: + 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 + } + 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 { + if c.trunc { + return stringid.TruncateID(c.v.ID) + } + return c.v.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 e7cd5166c2ef..ffcc5e897f1a 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{{.InUse}}" 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. @@ -132,6 +121,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 +189,19 @@ 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)) - + tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat) + if err != nil { + return err + } + buildCacheSort(ctx.BuildCache) for _, v := range ctx.BuildCache { - t.Execute(ctx.Output, *v) + if !v.Shared { + 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 878aef054954..8f955cb84924 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: diff --git a/cli/command/system/df.go b/cli/command/system/df.go index 43a2d74ca374..e693e115a107 100644 --- a/cli/command/system/df.go +++ b/cli/command/system/df.go @@ -3,6 +3,8 @@ package system import ( "context" "errors" + "sort" + "strings" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -52,6 +54,22 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error { format = formatter.TableFormatKey } + buildCache := du.BuildCache + sort.Slice(buildCache, func(i, j int) bool { + lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt + switch { + case lui == luj: + 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 + } + return lui.Before(*luj) + }) + duCtx := formatter.DiskUsageContext{ Context: formatter.Context{ Output: dockerCli.Out(), @@ -59,7 +77,7 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error { }, LayersSize: du.LayersSize, BuilderSize: du.BuilderSize, - BuildCache: du.BuildCache, + BuildCache: buildCache, Images: du.Images, Containers: du.Containers, Volumes: du.Volumes,