diff --git a/cli/command/formatter/disk_usage.go b/cli/command/formatter/disk_usage.go index 7170411e1b..07e39826ed 100644 --- a/cli/command/formatter/disk_usage.go +++ b/cli/command/formatter/disk_usage.go @@ -45,15 +45,33 @@ func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, return ctx.parseFormat() } -func (ctx *DiskUsageContext) Write() { +// +// NewDiskUsageFormat returns a format for rendering an DiskUsageContext +func NewDiskUsageFormat(source string) Format { + switch source { + case TableFormatKey: + format := defaultDiskUsageTableFormat + return Format(format) + case RawFormatKey: + format := `type: {{.Type}} +total: {{.TotalCount}} +active: {{.Active}} +size: {{.Size}} +reclaimable: {{.Reclaimable}} +` + return Format(format) + } + return Format(source) +} + +func (ctx *DiskUsageContext) Write() (err error) { if ctx.Verbose == false { ctx.buffer = bytes.NewBufferString("") - ctx.Format = defaultDiskUsageTableFormat ctx.preFormat() tmpl, err := ctx.parseFormat() if err != nil { - return + return err } err = ctx.contextFormat(tmpl, &diskUsageImagesContext{ @@ -61,20 +79,20 @@ func (ctx *DiskUsageContext) Write() { images: ctx.Images, }) if err != nil { - return + return err } err = ctx.contextFormat(tmpl, &diskUsageContainersContext{ containers: ctx.Containers, }) if err != nil { - return + return err } err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{ volumes: ctx.Volumes, }) if err != nil { - return + return err } diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}} @@ -87,7 +105,7 @@ func (ctx *DiskUsageContext) Write() { } ctx.postFormat(tmpl, &diskUsageContainersCtx) - return + return err } // First images @@ -158,6 +176,7 @@ func (ctx *DiskUsageContext) Write() { } } ctx.postFormat(tmpl, newVolumeContext()) + return } type diskUsageImagesContext struct { diff --git a/cli/command/formatter/disk_usage_test.go b/cli/command/formatter/disk_usage_test.go index 318e1692be..7093cfe85a 100644 --- a/cli/command/formatter/disk_usage_test.go +++ b/cli/command/formatter/disk_usage_test.go @@ -8,13 +8,17 @@ import ( ) func TestDiskUsageContextFormatWrite(t *testing.T) { - // Check default output format (verbose and non-verbose mode) for table headers cases := []struct { context DiskUsageContext expected string }{ + // Check default output format (verbose and non-verbose mode) for table headers { - DiskUsageContext{Verbose: false}, + DiskUsageContext{ + Context: Context{ + Format: NewDiskUsageFormat("table"), + }, + Verbose: false}, `TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 0 0 0B 0B Containers 0 0 0B 0B @@ -34,6 +38,77 @@ CONTAINER ID IMAGE COMMAND LOCAL VOLUMES Local Volumes space usage: VOLUME NAME LINKS SIZE +`, + }, + // Errors + { + DiskUsageContext{ + Context: Context{ + Format: "{{InvalidFunction}}", + }, + }, + `Template parsing error: template: :1: function "InvalidFunction" not defined +`, + }, + { + DiskUsageContext{ + Context: Context{ + Format: "{{nil}}", + }, + }, + `Template parsing error: template: :1:2: executing "" at : nil is not a command +`, + }, + // Table Format + { + DiskUsageContext{ + Context: Context{ + Format: NewDiskUsageFormat("table"), + }, + }, + `TYPE TOTAL ACTIVE SIZE RECLAIMABLE +Images 0 0 0B 0B +Containers 0 0 0B 0B +Local Volumes 0 0 0B 0B +`, + }, + { + DiskUsageContext{ + Context: Context{ + Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}"), + }, + }, + `TYPE ACTIVE +Images 0 +Containers 0 +Local Volumes 0 +`, + }, + // Raw Format + { + DiskUsageContext{ + Context: Context{ + Format: NewDiskUsageFormat("raw"), + }, + }, + `type: Images +total: 0 +active: 0 +size: 0B +reclaimable: 0B + +type: Containers +total: 0 +active: 0 +size: 0B +reclaimable: 0B + +type: Local Volumes +total: 0 +active: 0 +size: 0B +reclaimable: 0B + `, }, } @@ -41,7 +116,10 @@ VOLUME NAME LINKS SIZE for _, testcase := range cases { out := bytes.NewBufferString("") testcase.context.Output = out - testcase.context.Write() - assert.Equal(t, out.String(), testcase.expected) + if err := testcase.context.Write(); err != nil { + assert.Equal(t, err.Error(), testcase.expected) + } else { + assert.Equal(t, out.String(), testcase.expected) + } } } diff --git a/cli/command/system/df.go b/cli/command/system/df.go index 9f712484aa..67b3b31d87 100644 --- a/cli/command/system/df.go +++ b/cli/command/system/df.go @@ -1,6 +1,8 @@ package system import ( + "errors" + "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/formatter" @@ -10,6 +12,7 @@ import ( type diskUsageOptions struct { verbose bool + format string } // NewDiskUsageCommand creates a new cobra.Command for `docker df` @@ -29,19 +32,30 @@ func NewDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&opts.verbose, "verbose", "v", false, "Show detailed information on space usage") + flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template") return cmd } func runDiskUsage(dockerCli *command.DockerCli, 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 } + format := opts.format + if len(format) == 0 { + format = formatter.TableFormatKey + } + duCtx := formatter.DiskUsageContext{ Context: formatter.Context{ Output: dockerCli.Out(), + Format: formatter.NewDiskUsageFormat(format), }, LayersSize: du.LayersSize, Images: du.Images, @@ -50,7 +64,5 @@ func runDiskUsage(dockerCli *command.DockerCli, opts diskUsageOptions) error { Verbose: opts.verbose, } - duCtx.Write() - - return nil + return duCtx.Write() } diff --git a/docs/reference/commandline/system_df.md b/docs/reference/commandline/system_df.md index 86cc9896c0..04f8ad7aa6 100644 --- a/docs/reference/commandline/system_df.md +++ b/docs/reference/commandline/system_df.md @@ -86,6 +86,51 @@ volumes or in systems where some images, containers, or volumes have very large filesystems with many files. You should also be careful not to run this command in systems where performance is critical. +## Format the output + +The formatting option (`--format`) pretty prints the disk usage output +using a Go template. + +Valid placeholders for the Go template are listed below: + +| Placeholder | Description | +| -------------- | ------------------------------------------ | +| `.Type` | `Images`, `Containers` and `Local Volumes` | +| `.TotalCount` | Total number of items | +| `.Active` | Number of active items | +| `.Size` | Available size | +| `.Reclaimable` | Reclaimable size | + +When using the `--format` option, the `system df` command outputs +the data exactly as the template declares or, when using the +`table` directive, will include column headers as well. + +The following example uses a template without headers and outputs the +`Type` and `TotalCount` entries separated by a colon: + +```bash +$ docker system df --format "{{.Type}}: {{.TotalCount}}" + +Images: 2 +Containers: 4 +Local Volumes: 1 +``` + +To list the disk usage with size and reclaimable size in a table format you +can use: + +```bash +$ docker system df --format "table {{.Type}}\t{{.Size}}\t{{.Reclaimable}}" + +TYPE SIZE RECLAIMABLE +Images 2.547 GB 2.342 GB (91%) +Containers 0 B 0 B +Local Volumes 150.3 MB 150.3 MB (100%) + +``` + +**Note** the format option is meaningless when verbose is true. + ## Related commands * [system prune](system_prune.md) * [container prune](container_prune.md)