Skip to content

Commit d94a6cf

Browse files
authored
Merge pull request #3377 from crazy-max/du-json
cmd: multiple formats output support for du command
2 parents df7c46b + f0646ee commit d94a6cf

File tree

5 files changed

+393
-85
lines changed

5 files changed

+393
-85
lines changed

commands/diskusage.go

Lines changed: 151 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2363
type duOptions struct {
2464
builder string
2565
filter opts.FilterOpt
2666
verbose bool
67+
format string
2768
}
2869

2970
func 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

106181
func 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\tRECLAIMABLE\tSIZE\tLAST 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()

commands/prune.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package commands
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"os"
78
"strings"
89
"text/tabwriter"
@@ -241,3 +242,55 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
241242
Filter: []string{strings.Join(filters, ",")},
242243
}, nil
243244
}
245+
246+
func printKV(w io.Writer, k string, v any) {
247+
fmt.Fprintf(w, "%s:\t%v\n", k, v)
248+
}
249+
250+
func printVerbose(tw *tabwriter.Writer, du []*client.UsageInfo) {
251+
for _, di := range du {
252+
printKV(tw, "ID", di.ID)
253+
if len(di.Parents) != 0 {
254+
printKV(tw, "Parent", strings.Join(di.Parents, ","))
255+
}
256+
printKV(tw, "Created at", di.CreatedAt)
257+
printKV(tw, "Mutable", di.Mutable)
258+
printKV(tw, "Reclaimable", !di.InUse)
259+
printKV(tw, "Shared", di.Shared)
260+
printKV(tw, "Size", units.HumanSize(float64(di.Size)))
261+
if di.Description != "" {
262+
printKV(tw, "Description", di.Description)
263+
}
264+
printKV(tw, "Usage count", di.UsageCount)
265+
if di.LastUsedAt != nil {
266+
printKV(tw, "Last used", units.HumanDuration(time.Since(*di.LastUsedAt))+" ago")
267+
}
268+
if di.RecordType != "" {
269+
printKV(tw, "Type", di.RecordType)
270+
}
271+
272+
fmt.Fprintf(tw, "\n")
273+
}
274+
275+
tw.Flush()
276+
}
277+
278+
func printTableHeader(tw *tabwriter.Writer) {
279+
fmt.Fprintln(tw, "ID\tRECLAIMABLE\tSIZE\tLAST ACCESSED")
280+
}
281+
282+
func printTableRow(tw *tabwriter.Writer, di *client.UsageInfo) {
283+
id := di.ID
284+
if di.Mutable {
285+
id += "*"
286+
}
287+
size := units.HumanSize(float64(di.Size))
288+
if di.Shared {
289+
size += "*"
290+
}
291+
lastAccessed := ""
292+
if di.LastUsedAt != nil {
293+
lastAccessed = units.HumanDuration(time.Since(*di.LastUsedAt)) + " ago"
294+
}
295+
fmt.Fprintf(tw, "%-40s\t%-5v\t%-10s\t%s\n", id, !di.InUse, size, lastAccessed)
296+
}

0 commit comments

Comments
 (0)