mirror of https://github.com/docker/buildx.git
297 lines
6.5 KiB
Go
297 lines
6.5 KiB
Go
package commands
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
"github.com/docker/buildx/builder"
|
|
"github.com/docker/buildx/util/cobrautil/completion"
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/cli/command/formatter"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/docker/go-units"
|
|
"github.com/moby/buildkit/client"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
const (
|
|
duIDHeader = "ID"
|
|
duParentsHeader = "PARENTS"
|
|
duCreatedAtHeader = "CREATED AT"
|
|
duMutableHeader = "MUTABLE"
|
|
duReclaimHeader = "RECLAIMABLE"
|
|
duSharedHeader = "SHARED"
|
|
duSizeHeader = "SIZE"
|
|
duDescriptionHeader = "DESCRIPTION"
|
|
duUsageHeader = "USAGE COUNT"
|
|
duLastUsedAtHeader = "LAST ACCESSED"
|
|
duTypeHeader = "TYPE"
|
|
|
|
duDefaultTableFormat = "table {{.ID}}\t{{.Reclaimable}}\t{{.Size}}\t{{.LastUsedAt}}"
|
|
|
|
duDefaultPrettyTemplate = `ID: {{.ID}}
|
|
{{- if .Parents }}
|
|
Parents:
|
|
{{- range .Parents }}
|
|
- {{.}}
|
|
{{- end }}
|
|
{{- end }}
|
|
Created at: {{.CreatedAt}}
|
|
Mutable: {{.Mutable}}
|
|
Reclaimable: {{.Reclaimable}}
|
|
Shared: {{.Shared}}
|
|
Size: {{.Size}}
|
|
{{- if .Description}}
|
|
Description: {{ .Description }}
|
|
{{- end }}
|
|
Usage count: {{.UsageCount}}
|
|
{{- if .LastUsedAt}}
|
|
Last used: {{ .LastUsedAt }}
|
|
{{- end }}
|
|
{{- if .Type}}
|
|
Type: {{ .Type }}
|
|
{{- end }}
|
|
`
|
|
)
|
|
|
|
type duOptions struct {
|
|
builder string
|
|
filter opts.FilterOpt
|
|
verbose bool
|
|
format string
|
|
}
|
|
|
|
func runDiskUsage(ctx context.Context, dockerCli command.Cli, opts duOptions) error {
|
|
if opts.format != "" && opts.verbose {
|
|
return errors.New("--format and --verbose cannot be used together")
|
|
} else if opts.format == "" {
|
|
if opts.verbose {
|
|
opts.format = duDefaultPrettyTemplate
|
|
} else {
|
|
opts.format = duDefaultTableFormat
|
|
}
|
|
} else if opts.format == formatter.PrettyFormatKey {
|
|
opts.format = duDefaultPrettyTemplate
|
|
} else if opts.format == formatter.TableFormatKey {
|
|
opts.format = duDefaultTableFormat
|
|
}
|
|
|
|
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nodes, err := b.LoadNodes(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, node := range nodes {
|
|
if node.Err != nil {
|
|
return node.Err
|
|
}
|
|
}
|
|
|
|
out := make([][]*client.UsageInfo, len(nodes))
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
for i, node := range nodes {
|
|
func(i int, node builder.Node) {
|
|
eg.Go(func() error {
|
|
if node.Driver != nil {
|
|
c, err := node.Driver.Client(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
du, err := c.DiskUsage(ctx, client.WithFilter(pi.Filter))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out[i] = du
|
|
return nil
|
|
}
|
|
return nil
|
|
})
|
|
}(i, node)
|
|
}
|
|
|
|
if err := eg.Wait(); err != nil {
|
|
return err
|
|
}
|
|
|
|
fctx := formatter.Context{
|
|
Output: dockerCli.Out(),
|
|
Format: formatter.Format(opts.format),
|
|
}
|
|
|
|
var dus []*client.UsageInfo
|
|
for _, du := range out {
|
|
if du != nil {
|
|
dus = append(dus, du...)
|
|
}
|
|
}
|
|
|
|
render := func(format func(subContext formatter.SubContext) error) error {
|
|
for _, du := range dus {
|
|
if err := format(&diskusageContext{
|
|
format: fctx.Format,
|
|
du: du,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
duCtx := diskusageContext{}
|
|
duCtx.Header = formatter.SubHeaderContext{
|
|
"ID": duIDHeader,
|
|
"Parents": duParentsHeader,
|
|
"CreatedAt": duCreatedAtHeader,
|
|
"Mutable": duMutableHeader,
|
|
"Reclaimable": duReclaimHeader,
|
|
"Shared": duSharedHeader,
|
|
"Size": duSizeHeader,
|
|
"Description": duDescriptionHeader,
|
|
"UsageCount": duUsageHeader,
|
|
"LastUsedAt": duLastUsedAtHeader,
|
|
"Type": duTypeHeader,
|
|
}
|
|
|
|
defer func() {
|
|
if (fctx.Format != duDefaultTableFormat && fctx.Format != duDefaultPrettyTemplate) || fctx.Format.IsJSON() || opts.filter.Value().Len() > 0 {
|
|
return
|
|
}
|
|
printSummary(dockerCli.Out(), out)
|
|
}()
|
|
|
|
return fctx.Write(&duCtx, render)
|
|
}
|
|
|
|
func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|
options := duOptions{filter: opts.NewFilterOpt()}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "du",
|
|
Short: "Disk usage",
|
|
Args: cli.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
options.builder = rootOpts.builder
|
|
return runDiskUsage(cmd.Context(), dockerCli, options)
|
|
},
|
|
ValidArgsFunction: completion.Disable,
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
|
|
flags := cmd.Flags()
|
|
flags.Var(&options.filter, "filter", "Provide filter values")
|
|
flags.BoolVar(&options.verbose, "verbose", false, `Shorthand for "--format=pretty"`)
|
|
flags.StringVar(&options.format, "format", "", "Format the output")
|
|
|
|
return cmd
|
|
}
|
|
|
|
type diskusageContext struct {
|
|
formatter.HeaderContext
|
|
format formatter.Format
|
|
du *client.UsageInfo
|
|
}
|
|
|
|
func (d *diskusageContext) MarshalJSON() ([]byte, error) {
|
|
return formatter.MarshalJSON(d)
|
|
}
|
|
|
|
func (d *diskusageContext) ID() string {
|
|
id := d.du.ID
|
|
if d.format.IsTable() && d.du.Mutable {
|
|
id += "*"
|
|
}
|
|
return id
|
|
}
|
|
|
|
func (d *diskusageContext) Parents() []string {
|
|
return d.du.Parents
|
|
}
|
|
|
|
func (d *diskusageContext) CreatedAt() string {
|
|
return d.du.CreatedAt.String()
|
|
}
|
|
|
|
func (d *diskusageContext) Mutable() bool {
|
|
return d.du.Mutable
|
|
}
|
|
|
|
func (d *diskusageContext) Reclaimable() bool {
|
|
return !d.du.InUse
|
|
}
|
|
|
|
func (d *diskusageContext) Shared() bool {
|
|
return d.du.Shared
|
|
}
|
|
|
|
func (d *diskusageContext) Size() string {
|
|
size := units.HumanSize(float64(d.du.Size))
|
|
if d.format.IsTable() && d.du.Shared {
|
|
size += "*"
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (d *diskusageContext) Description() string {
|
|
return d.du.Description
|
|
}
|
|
|
|
func (d *diskusageContext) UsageCount() int {
|
|
return d.du.UsageCount
|
|
}
|
|
|
|
func (d *diskusageContext) LastUsedAt() string {
|
|
if d.du.LastUsedAt != nil {
|
|
return units.HumanDuration(time.Since(*d.du.LastUsedAt)) + " ago"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *diskusageContext) Type() string {
|
|
return string(d.du.RecordType)
|
|
}
|
|
|
|
func printSummary(w io.Writer, dus [][]*client.UsageInfo) {
|
|
total := int64(0)
|
|
reclaimable := int64(0)
|
|
shared := int64(0)
|
|
|
|
for _, du := range dus {
|
|
for _, di := range du {
|
|
if di.Size > 0 {
|
|
total += di.Size
|
|
if !di.InUse {
|
|
reclaimable += di.Size
|
|
}
|
|
}
|
|
if di.Shared {
|
|
shared += di.Size
|
|
}
|
|
}
|
|
}
|
|
|
|
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
|
if shared > 0 {
|
|
fmt.Fprintf(tw, "Shared:\t%s\n", units.HumanSize(float64(shared)))
|
|
fmt.Fprintf(tw, "Private:\t%s\n", units.HumanSize(float64(total-shared)))
|
|
}
|
|
fmt.Fprintf(tw, "Reclaimable:\t%s\n", units.HumanSize(float64(reclaimable)))
|
|
fmt.Fprintf(tw, "Total:\t%s\n", units.HumanSize(float64(total)))
|
|
tw.Flush()
|
|
}
|