diff --git a/cli/command/formatter/buildcache.go b/cli/command/formatter/buildcache.go index 9e9d10bd68..813e41c5c7 100644 --- a/cli/command/formatter/buildcache.go +++ b/cli/command/formatter/buildcache.go @@ -15,6 +15,7 @@ const ( defaultBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}\t{{.Description}}" cacheIDHeader = "CACHE ID" + cacheTypeHeader = "CACHE TYPE" parentHeader = "PARENT" lastUsedSinceHeader = "LAST USED" usageCountHeader = "USAGE" @@ -36,10 +37,12 @@ func NewBuildCacheFormat(source string, quiet bool) Format { } format := `build_cache_id: {{.ID}} parent_id: {{.Parent}} -type: {{.Type}} +build_cache_type: {{.CacheType}} description: {{.Description}} -created_at: {{.CreatedSince}} -last_used_at: {{.LastUsedSince}} +created_at: {{.CreatedAt}} +created_since: {{.CreatedSince}} +last_used_at: {{.LastUsedAt}} +last_used_since: {{.LastUsedSince}} usage_count: {{.UsageCount}} in_use: {{.InUse}} shared: {{.Shared}} @@ -95,7 +98,7 @@ func newBuildCacheContext() *buildCacheContext { buildCacheCtx.header = buildCacheHeaderContext{ "ID": cacheIDHeader, "Parent": parentHeader, - "Type": typeHeader, + "CacheType": cacheTypeHeader, "Size": sizeHeader, "CreatedSince": createdSinceHeader, "LastUsedSince": lastUsedSinceHeader, @@ -129,7 +132,7 @@ func (c *buildCacheContext) Parent() string { return c.v.Parent } -func (c *buildCacheContext) Type() string { +func (c *buildCacheContext) CacheType() string { return c.v.Type } @@ -141,10 +144,21 @@ func (c *buildCacheContext) Size() string { return units.HumanSizeWithPrecision(float64(c.v.Size), 3) } +func (c *buildCacheContext) CreatedAt() string { + return c.v.CreatedAt.String() +} + func (c *buildCacheContext) CreatedSince() string { return units.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago" } +func (c *buildCacheContext) LastUsedAt() string { + if c.v.LastUsedAt == nil { + return "" + } + return c.v.LastUsedAt.String() +} + func (c *buildCacheContext) LastUsedSince() string { if c.v.LastUsedAt == nil { return "" diff --git a/cli/command/formatter/disk_usage.go b/cli/command/formatter/disk_usage.go index 3af0494395..3b235497de 100644 --- a/cli/command/formatter/disk_usage.go +++ b/cli/command/formatter/disk_usage.go @@ -15,8 +15,8 @@ const ( 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}}" + defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}" 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" @@ -49,12 +49,25 @@ func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, } // NewDiskUsageFormat returns a format for rendering an DiskUsageContext -func NewDiskUsageFormat(source string) Format { - switch source { - case TableFormatKey: - format := defaultDiskUsageTableFormat - return Format(format) - case RawFormatKey: +func NewDiskUsageFormat(source string, verbose bool) Format { + switch { + case verbose && source == RawFormatKey: + format := `{{range .Images}}type: Image +` + NewImageFormat(source, false, true) + ` +{{end -}} +{{range .Containers}}type: Container +` + NewContainerFormat(source, false, true) + ` +{{end -}} +{{range .Volumes}}type: Volume +` + NewVolumeFormat(source, false) + ` +{{end -}} +{{range .BuildCache}}type: Build Cache +` + NewBuildCacheFormat(source, false) + ` +{{end -}}` + return format + case !verbose && source == TableFormatKey: + return Format(defaultDiskUsageTableFormat) + case !verbose && source == RawFormatKey: format := `type: {{.Type}} total: {{.TotalCount}} active: {{.Active}} @@ -62,8 +75,9 @@ size: {{.Size}} reclaimable: {{.Reclaimable}} ` return Format(format) + default: + return Format(source) } - return Format(source) } func (ctx *DiskUsageContext) Write() (err error) { @@ -120,15 +134,23 @@ func (ctx *DiskUsageContext) Write() (err error) { return err } -// nolint: gocyclo -func (ctx *DiskUsageContext) verboseWrite() error { - // First images - tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat) - if err != nil { - return err - } +type diskUsageContext struct { + Images []*imageContext + Containers []*containerContext + Volumes []*volumeContext + BuildCache []*buildCacheContext +} - ctx.Output.Write([]byte("Images space usage:\n\n")) +func (ctx *DiskUsageContext) verboseWrite() error { + duc := &diskUsageContext{ + Images: make([]*imageContext, 0, len(ctx.Images)), + Containers: make([]*containerContext, 0, len(ctx.Containers)), + Volumes: make([]*volumeContext, 0, len(ctx.Volumes)), + BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)), + } + trunc := ctx.Format.IsTable() + + // First images for _, i := range ctx.Images { repo := "" tag := "" @@ -144,57 +166,88 @@ func (ctx *DiskUsageContext) verboseWrite() error { } } - err := ctx.contextFormat(tmpl, &imageContext{ + duc.Images = append(duc.Images, &imageContext{ repo: repo, tag: tag, - trunc: true, + trunc: trunc, i: *i, }) - if err != nil { + } + + // Now containers + for _, c := range ctx.Containers { + // Don't display the virtual size + c.SizeRootFs = 0 + duc.Containers = append(duc.Containers, &containerContext{trunc: trunc, c: *c}) + } + + // And volumes + for _, v := range ctx.Volumes { + duc.Volumes = append(duc.Volumes, &volumeContext{v: *v}) + } + + // And build cache + buildCacheSort(ctx.BuildCache) + for _, v := range ctx.BuildCache { + duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc}) + } + + if ctx.Format == TableFormatKey { + return ctx.verboseWriteTable(duc) + } + + ctx.preFormat() + tmpl, err := ctx.parseFormat() + if err != nil { + return err + } + return tmpl.Execute(ctx.Output, duc) +} + +func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error { + tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat) + if err != nil { + return err + } + ctx.Output.Write([]byte("Images space usage:\n\n")) + for _, img := range duc.Images { + if err := ctx.contextFormat(tmpl, img); err != nil { return err } } ctx.postFormat(tmpl, newImageContext()) - // Now containers - ctx.Output.Write([]byte("\nContainers space usage:\n\n")) tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat) if err != nil { return err } - for _, c := range ctx.Containers { - // Don't display the virtual size - c.SizeRootFs = 0 - err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c}) - if err != nil { + ctx.Output.Write([]byte("\nContainers space usage:\n\n")) + for _, c := range duc.Containers { + if err := ctx.contextFormat(tmpl, c); err != nil { return err } } ctx.postFormat(tmpl, newContainerContext()) - // And volumes - ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n")) tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat) if err != nil { return err } - for _, v := range ctx.Volumes { - if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil { + ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n")) + for _, v := range duc.Volumes { + if err := ctx.contextFormat(tmpl, v); err != nil { return err } } ctx.postFormat(tmpl, newVolumeContext()) - // And build cache - fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize))) - 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 { + fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize))) + for _, v := range duc.BuildCache { + if err := ctx.contextFormat(tmpl, v); err != nil { return err } } diff --git a/cli/command/formatter/disk_usage_test.go b/cli/command/formatter/disk_usage_test.go index 3bc13ebc61..9a0b829ed1 100644 --- a/cli/command/formatter/disk_usage_test.go +++ b/cli/command/formatter/disk_usage_test.go @@ -18,7 +18,7 @@ func TestDiskUsageContextFormatWrite(t *testing.T) { { DiskUsageContext{ Context: Context{ - Format: NewDiskUsageFormat("table"), + Format: NewDiskUsageFormat("table", false), }, Verbose: false}, `TYPE TOTAL ACTIVE SIZE RECLAIMABLE @@ -29,7 +29,7 @@ Build Cache 0 0 0B `, }, { - DiskUsageContext{Verbose: true}, + DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("table", true)}}, `Images space usage: REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS @@ -44,9 +44,17 @@ VOLUME NAME LINKS SIZE Build cache usage: 0B -CACHE ID TYPE SIZE CREATED LAST USED USAGE SHARED +CACHE ID CACHE TYPE SIZE CREATED LAST USED USAGE SHARED `, }, + { + DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("raw", true)}}, + ``, + }, + { + DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("{{json .}}", true)}}, + `{"Images":[],"Containers":[],"Volumes":[],"BuildCache":[]}`, + }, // Errors { DiskUsageContext{ @@ -70,7 +78,7 @@ CACHE ID TYPE SIZE CREATED { DiskUsageContext{ Context: Context{ - Format: NewDiskUsageFormat("table"), + Format: NewDiskUsageFormat("table", false), }, }, `TYPE TOTAL ACTIVE SIZE RECLAIMABLE @@ -83,7 +91,7 @@ Build Cache 0 0 0B { DiskUsageContext{ Context: Context{ - Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}"), + Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}", false), }, }, string(golden.Get(t, "disk-usage-context-write-custom.golden")), @@ -92,7 +100,7 @@ Build Cache 0 0 0B { DiskUsageContext{ Context: Context{ - Format: NewDiskUsageFormat("raw"), + Format: NewDiskUsageFormat("raw", false), }, }, string(golden.Get(t, "disk-usage-raw-format.golden")), diff --git a/cli/command/system/df.go b/cli/command/system/df.go index 8da9e6dfdc..116b9093e9 100644 --- a/cli/command/system/df.go +++ b/cli/command/system/df.go @@ -2,7 +2,6 @@ package system import ( "context" - "errors" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -38,10 +37,6 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command { } func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error { - if opts.verbose && len(opts.format) != 0 { - return errors.New("the verbose and the format options conflict") - } - du, err := dockerCli.Client().DiskUsage(context.Background()) if err != nil { return err @@ -62,7 +57,7 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error { duCtx := formatter.DiskUsageContext{ Context: formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewDiskUsageFormat(format), + Format: formatter.NewDiskUsageFormat(format, opts.verbose), }, LayersSize: du.LayersSize, BuilderSize: bsz,