@@ -4,29 +4,84 @@ import (
44 "context"
55 "fmt"
66 "io"
7- "os"
8- "strings"
97 "text/tabwriter"
108 "time"
119
1210 "github.com/docker/buildx/builder"
1311 "github.com/docker/buildx/util/cobrautil/completion"
1412 "github.com/docker/cli/cli"
1513 "github.com/docker/cli/cli/command"
14+ "github.com/docker/cli/cli/command/formatter"
1615 "github.com/docker/cli/opts"
1716 "github.com/docker/go-units"
1817 "github.com/moby/buildkit/client"
18+ "github.com/pkg/errors"
1919 "github.com/spf13/cobra"
2020 "golang.org/x/sync/errgroup"
2121)
2222
23+ const (
24+ duIDHeader = "ID"
25+ duParentsHeader = "PARENTS"
26+ duCreatedAtHeader = "CREATED AT"
27+ duMutableHeader = "MUTABLE"
28+ duReclaimHeader = "RECLAIMABLE"
29+ duSharedHeader = "SHARED"
30+ duSizeHeader = "SIZE"
31+ duDescriptionHeader = "DESCRIPTION"
32+ duUsageHeader = "USAGE COUNT"
33+ duLastUsedAtHeader = "LAST ACCESSED"
34+ duTypeHeader = "TYPE"
35+
36+ duDefaultTableFormat = "table {{.ID}}\t {{.Reclaimable}}\t {{.Size}}\t {{.LastUsedAt}}"
37+
38+ duDefaultPrettyTemplate = `ID: {{.ID}}
39+ {{- if .Parents }}
40+ Parents:
41+ {{- range .Parents }}
42+ - {{.}}
43+ {{- end }}
44+ {{- end }}
45+ Created at: {{.CreatedAt}}
46+ Mutable: {{.Mutable}}
47+ Reclaimable: {{.Reclaimable}}
48+ Shared: {{.Shared}}
49+ Size: {{.Size}}
50+ {{- if .Description}}
51+ Description: {{ .Description }}
52+ {{- end }}
53+ Usage count: {{.UsageCount}}
54+ {{- if .LastUsedAt}}
55+ Last used: {{ .LastUsedAt }}
56+ {{- end }}
57+ {{- if .Type}}
58+ Type: {{ .Type }}
59+ {{- end }}
60+ `
61+ )
62+
2363type duOptions struct {
2464 builder string
2565 filter opts.FilterOpt
2666 verbose bool
67+ format string
2768}
2869
2970func runDiskUsage (ctx context.Context , dockerCli command.Cli , opts duOptions ) error {
71+ if opts .format != "" && opts .verbose {
72+ return errors .New ("--format and --verbose cannot be used together" )
73+ } else if opts .format == "" {
74+ if opts .verbose {
75+ opts .format = duDefaultPrettyTemplate
76+ } else {
77+ opts .format = duDefaultTableFormat
78+ }
79+ } else if opts .format == formatter .PrettyFormatKey {
80+ opts .format = duDefaultPrettyTemplate
81+ } else if opts .format == formatter .TableFormatKey {
82+ opts .format = duDefaultTableFormat
83+ }
84+
3085 pi , err := toBuildkitPruneInfo (opts .filter .Value ())
3186 if err != nil {
3287 return err
@@ -74,33 +129,53 @@ func runDiskUsage(ctx context.Context, dockerCli command.Cli, opts duOptions) er
74129 return err
75130 }
76131
77- tw := tabwriter .NewWriter (os .Stdout , 1 , 8 , 1 , '\t' , 0 )
78- first := true
132+ fctx := formatter.Context {
133+ Output : dockerCli .Out (),
134+ Format : formatter .Format (opts .format ),
135+ }
136+
137+ var dus []* client.UsageInfo
79138 for _ , du := range out {
80- if du = = nil {
81- continue
139+ if du ! = nil {
140+ dus = append ( dus , du ... )
82141 }
83- if opts .verbose {
84- printVerbose (tw , du )
85- } else {
86- if first {
87- printTableHeader (tw )
88- first = false
89- }
90- for _ , di := range du {
91- printTableRow (tw , di )
92- }
142+ }
93143
94- tw .Flush ()
144+ render := func (format func (subContext formatter.SubContext ) error ) error {
145+ for _ , du := range dus {
146+ if err := format (& diskusageContext {
147+ format : fctx .Format ,
148+ du : du ,
149+ }); err != nil {
150+ return err
151+ }
95152 }
153+ return nil
96154 }
97155
98- if opts .filter .Value ().Len () == 0 {
99- printSummary (tw , out )
156+ duCtx := diskusageContext {}
157+ duCtx .Header = formatter.SubHeaderContext {
158+ "ID" : duIDHeader ,
159+ "Parents" : duParentsHeader ,
160+ "CreatedAt" : duCreatedAtHeader ,
161+ "Mutable" : duMutableHeader ,
162+ "Reclaimable" : duReclaimHeader ,
163+ "Shared" : duSharedHeader ,
164+ "Size" : duSizeHeader ,
165+ "Description" : duDescriptionHeader ,
166+ "UsageCount" : duUsageHeader ,
167+ "LastUsedAt" : duLastUsedAtHeader ,
168+ "Type" : duTypeHeader ,
100169 }
101170
102- tw .Flush ()
103- return nil
171+ defer func () {
172+ if (fctx .Format != duDefaultTableFormat && fctx .Format != duDefaultPrettyTemplate ) || fctx .Format .IsJSON () || opts .filter .Value ().Len () > 0 {
173+ return
174+ }
175+ printSummary (dockerCli .Out (), out )
176+ }()
177+
178+ return fctx .Write (& duCtx , render )
104179}
105180
106181func duCmd (dockerCli command.Cli , rootOpts * rootOptions ) * cobra.Command {
@@ -119,64 +194,78 @@ func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
119194
120195 flags := cmd .Flags ()
121196 flags .Var (& options .filter , "filter" , "Provide filter values" )
122- flags .BoolVar (& options .verbose , "verbose" , false , "Provide a more verbose output" )
197+ flags .BoolVar (& options .verbose , "verbose" , false , `Shorthand for "--format=pretty"` )
198+ flags .StringVar (& options .format , "format" , "" , "Format the output" )
123199
124200 return cmd
125201}
126202
127- func printKV (w io.Writer , k string , v any ) {
128- fmt .Fprintf (w , "%s:\t %v\n " , k , v )
203+ type diskusageContext struct {
204+ formatter.HeaderContext
205+ format formatter.Format
206+ du * client.UsageInfo
129207}
130208
131- func printVerbose (tw * tabwriter.Writer , du []* client.UsageInfo ) {
132- for _ , di := range du {
133- printKV (tw , "ID" , di .ID )
134- if len (di .Parents ) != 0 {
135- printKV (tw , "Parent" , strings .Join (di .Parents , "," ))
136- }
137- printKV (tw , "Created at" , di .CreatedAt )
138- printKV (tw , "Mutable" , di .Mutable )
139- printKV (tw , "Reclaimable" , ! di .InUse )
140- printKV (tw , "Shared" , di .Shared )
141- printKV (tw , "Size" , units .HumanSize (float64 (di .Size )))
142- if di .Description != "" {
143- printKV (tw , "Description" , di .Description )
144- }
145- printKV (tw , "Usage count" , di .UsageCount )
146- if di .LastUsedAt != nil {
147- printKV (tw , "Last used" , units .HumanDuration (time .Since (* di .LastUsedAt ))+ " ago" )
148- }
149- if di .RecordType != "" {
150- printKV (tw , "Type" , di .RecordType )
151- }
209+ func (d * diskusageContext ) MarshalJSON () ([]byte , error ) {
210+ return formatter .MarshalJSON (d )
211+ }
152212
153- fmt .Fprintf (tw , "\n " )
213+ func (d * diskusageContext ) ID () string {
214+ id := d .du .ID
215+ if d .format .IsTable () && d .du .Mutable {
216+ id += "*"
154217 }
218+ return id
219+ }
155220
156- tw .Flush ()
221+ func (d * diskusageContext ) Parents () []string {
222+ return d .du .Parents
157223}
158224
159- func printTableHeader ( tw * tabwriter. Writer ) {
160- fmt . Fprintln ( tw , "ID \t RECLAIMABLE \t SIZE \t LAST ACCESSED" )
225+ func ( d * diskusageContext ) CreatedAt () string {
226+ return d . du . CreatedAt . String ( )
161227}
162228
163- func printTableRow (tw * tabwriter.Writer , di * client.UsageInfo ) {
164- id := di .ID
165- if di .Mutable {
166- id += "*"
167- }
168- size := units .HumanSize (float64 (di .Size ))
169- if di .Shared {
229+ func (d * diskusageContext ) Mutable () bool {
230+ return d .du .Mutable
231+ }
232+
233+ func (d * diskusageContext ) Reclaimable () bool {
234+ return ! d .du .InUse
235+ }
236+
237+ func (d * diskusageContext ) Shared () bool {
238+ return d .du .Shared
239+ }
240+
241+ func (d * diskusageContext ) Size () string {
242+ size := units .HumanSize (float64 (d .du .Size ))
243+ if d .format .IsTable () && d .du .Shared {
170244 size += "*"
171245 }
172- lastAccessed := ""
173- if di .LastUsedAt != nil {
174- lastAccessed = units .HumanDuration (time .Since (* di .LastUsedAt )) + " ago"
246+ return size
247+ }
248+
249+ func (d * diskusageContext ) Description () string {
250+ return d .du .Description
251+ }
252+
253+ func (d * diskusageContext ) UsageCount () int {
254+ return d .du .UsageCount
255+ }
256+
257+ func (d * diskusageContext ) LastUsedAt () string {
258+ if d .du .LastUsedAt != nil {
259+ return units .HumanDuration (time .Since (* d .du .LastUsedAt )) + " ago"
175260 }
176- fmt . Fprintf ( tw , "%-40s \t %-5v \t %-10s \t %s \n " , id , ! di . InUse , size , lastAccessed )
261+ return ""
177262}
178263
179- func printSummary (tw * tabwriter.Writer , dus [][]* client.UsageInfo ) {
264+ func (d * diskusageContext ) Type () string {
265+ return string (d .du .RecordType )
266+ }
267+
268+ func printSummary (w io.Writer , dus [][]* client.UsageInfo ) {
180269 total := int64 (0 )
181270 reclaimable := int64 (0 )
182271 shared := int64 (0 )
@@ -195,11 +284,11 @@ func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
195284 }
196285 }
197286
287+ tw := tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
198288 if shared > 0 {
199289 fmt .Fprintf (tw , "Shared:\t %s\n " , units .HumanSize (float64 (shared )))
200290 fmt .Fprintf (tw , "Private:\t %s\n " , units .HumanSize (float64 (total - shared )))
201291 }
202-
203292 fmt .Fprintf (tw , "Reclaimable:\t %s\n " , units .HumanSize (float64 (reclaimable )))
204293 fmt .Fprintf (tw , "Total:\t %s\n " , units .HumanSize (float64 (total )))
205294 tw .Flush ()
0 commit comments