package commands import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/docker/buildx/builder" "github.com/docker/buildx/store" "github.com/docker/buildx/store/storeutil" "github.com/docker/buildx/util/cobrautil" "github.com/docker/buildx/util/cobrautil/completion" "github.com/docker/buildx/util/platformutil" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/moby/buildkit/util/appcontext" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" ) const ( lsNameNodeHeader = "NAME/NODE" lsDriverEndpointHeader = "DRIVER/ENDPOINT" lsStatusHeader = "STATUS" lsLastActivityHeader = "LAST ACTIVITY" lsBuildkitHeader = "BUILDKIT" lsPlatformsHeader = "PLATFORMS" lsIndent = ` \_ ` lsDefaultTableFormat = "table {{.NameNode}}\t{{.DriverEndpoint}}\t{{.Status}}\t{{.Buildkit}}\t{{.Platforms}}" ) type lsOptions struct { format string } func runLs(dockerCli command.Cli, in lsOptions) error { ctx := appcontext.Context() txn, release, err := storeutil.GetStore(dockerCli) if err != nil { return err } defer release() current, err := storeutil.GetCurrentInstance(txn, dockerCli) if err != nil { return err } builders, err := builder.GetBuilders(dockerCli, txn) if err != nil { return err } timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second) defer cancel() eg, _ := errgroup.WithContext(timeoutCtx) for _, b := range builders { func(b *builder.Builder) { eg.Go(func() error { _, _ = b.LoadNodes(timeoutCtx, true) return nil }) }(b) } if err := eg.Wait(); err != nil { return err } if hasErrors, err := lsPrint(dockerCli, current, builders, in.format); err != nil { return err } else if hasErrors { _, _ = fmt.Fprintf(dockerCli.Err(), "\n") for _, b := range builders { if b.Err() != nil { _, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.Name, strings.TrimSpace(b.Err().Error())) } else { for _, d := range b.Nodes() { if d.Err != nil { _, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.Name, d.Name, strings.TrimSpace(d.Err.Error())) } } } } } return nil } func lsCmd(dockerCli command.Cli) *cobra.Command { var options lsOptions cmd := &cobra.Command{ Use: "ls", Short: "List builder instances", Args: cli.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return runLs(dockerCli, options) }, ValidArgsFunction: completion.Disable, } flags := cmd.Flags() flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output") // hide builder persistent flag for this command cobrautil.HideInheritedFlags(cmd, "builder") return cmd } func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, format string) (hasErrors bool, _ error) { if format == formatter.TableFormatKey { format = lsDefaultTableFormat } ctx := formatter.Context{ Output: dockerCli.Out(), Format: formatter.Format(format), } render := func(format func(subContext formatter.SubContext) error) error { for _, b := range builders { if err := format(&lsContext{ Builder: b, format: ctx.Format, current: current, }); err != nil { return err } if b.Err() != nil { if ctx.Format.IsTable() { hasErrors = true } continue } if !ctx.Format.IsTable() { continue } for _, n := range b.Nodes() { if n.Err != nil { hasErrors = true } if err := format(&lsContext{ format: ctx.Format, Builder: b, node: n, }); err != nil { return err } } } return nil } lsCtx := lsContext{} lsCtx.Header = formatter.SubHeaderContext{ "NameNode": lsNameNodeHeader, "DriverEndpoint": lsDriverEndpointHeader, "LastActivity": lsLastActivityHeader, "Status": lsStatusHeader, "Buildkit": lsBuildkitHeader, "Platforms": lsPlatformsHeader, } return hasErrors, ctx.Write(&lsCtx, render) } type lsContext struct { formatter.HeaderContext Builder *builder.Builder format formatter.Format current *store.NodeGroup node builder.Node } func (c *lsContext) MarshalJSON() ([]byte, error) { return json.Marshal(c.Builder) } func (c *lsContext) IsNode() bool { return c.node.Name != "" } func (c *lsContext) IsCurrent() bool { if c.IsNode() || c.current == nil { return false } return c.current.Name == c.Builder.Name } func (c *lsContext) NameNode() string { if !c.IsNode() { name := c.Builder.Name if c.IsCurrent() { name += "*" } return name } if c.format.IsTable() { return lsIndent + c.node.Name } return c.node.Name } func (c *lsContext) DriverEndpoint() string { if !c.IsNode() { return c.Builder.Driver } if c.format.IsTable() { return lsIndent + c.node.Endpoint } return c.node.Endpoint } func (c *lsContext) LastActivity() string { if c.IsNode() || c.Builder.LastActivity.IsZero() { return "" } return c.Builder.LastActivity.UTC().Format(time.RFC3339) } func (c *lsContext) Status() string { if !c.IsNode() { if c.Builder.Err() != nil { return "error" } return "" } if c.node.Err != nil { return "error" } if c.node.DriverInfo != nil { return c.node.DriverInfo.Status.String() } return "" } func (c *lsContext) Buildkit() string { if !c.IsNode() { return "" } return c.node.Version } func (c *lsContext) Platforms() string { if !c.IsNode() { return "" } return strings.Join(platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms), ", ") } func (c *lsContext) Error() string { if c.IsNode() && c.node.Err != nil { return c.node.Err.Error() } else if err := c.Builder.Err(); err != nil { return err.Error() } return "" }