mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-17 16:37:46 +08:00
Add buildx history command
These commands allow working with build records of completed and running builds. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
cde0e9814d
commit
06912aa24c
109
commands/history/inspect.go
Normal file
109
commands/history/inspect.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inspectOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
|
||||||
|
ls, err := localstate.New(confutil.NewConfig(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
|
||||||
|
|
||||||
|
log.Printf("rec %+v", rec)
|
||||||
|
log.Printf("st %+v", st)
|
||||||
|
|
||||||
|
// Context
|
||||||
|
// Dockerfile
|
||||||
|
// Target
|
||||||
|
// VCS Repo / Commit
|
||||||
|
// Platform
|
||||||
|
|
||||||
|
// Started
|
||||||
|
// Duration
|
||||||
|
// Number of steps
|
||||||
|
// Cached steps
|
||||||
|
// Status
|
||||||
|
|
||||||
|
// build-args
|
||||||
|
// exporters (image)
|
||||||
|
|
||||||
|
// commands
|
||||||
|
// error
|
||||||
|
// materials
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options inspectOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "inspect [OPTIONS] [REF]",
|
||||||
|
Short: "Inspect a build",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
options.ref = args[0]
|
||||||
|
}
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runInspect(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
// flags := cmd.Flags()
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
124
commands/history/logs.go
Normal file
124
commands/history/logs.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logsOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
progress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLogs(ctx context.Context, dockerCli command.Cli, opts logsOptions) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := c.ControlClient().Status(ctx, &controlapi.StatusRequest{
|
||||||
|
Ref: rec.Ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode progressui.DisplayMode = progressui.DisplayMode(opts.progress)
|
||||||
|
if mode == progressui.AutoMode {
|
||||||
|
mode = progressui.PlainMode
|
||||||
|
}
|
||||||
|
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
loop0:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
cl.CloseSend()
|
||||||
|
return context.Cause(ctx)
|
||||||
|
default:
|
||||||
|
ev, err := cl.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break loop0
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printer.Write(client.NewSolveStatus(ev))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return printer.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func logsCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options logsOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "logs [OPTIONS] [REF]",
|
||||||
|
Short: "Print the logs of a build",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
options.ref = args[0]
|
||||||
|
}
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runLogs(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.progress, "progress", "plain", "Set type of progress output (plain, rawjson, tty)")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
234
commands/history/ls.go
Normal file
234
commands/history/ls.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lsHeaderBuildID = "BUILD ID"
|
||||||
|
lsHeaderName = "NAME"
|
||||||
|
lsHeaderStatus = "STATUS"
|
||||||
|
lsHeaderCreated = "CREATED AT"
|
||||||
|
lsHeaderDuration = "DURATION"
|
||||||
|
lsHeaderLink = ""
|
||||||
|
|
||||||
|
lsDefaultTableFormat = "table {{.Ref}}\t{{.Name}}\t{{.Status}}\t{{.CreatedAt}}\t{{.Duration}}\t{{.Link}}"
|
||||||
|
|
||||||
|
headerKeyTimestamp = "buildkit-current-timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lsOptions struct {
|
||||||
|
builder string
|
||||||
|
format string
|
||||||
|
noTrunc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLs(ctx context.Context, dockerCli command.Cli, opts lsOptions) error {
|
||||||
|
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, err := queryRecords(ctx, "", nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ls, err := localstate.New(confutil.NewConfig(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rec := range out {
|
||||||
|
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
|
||||||
|
rec.name = buildName(rec.FrontendAttrs, st)
|
||||||
|
out[i] = rec
|
||||||
|
}
|
||||||
|
|
||||||
|
return lsPrint(dockerCli, out, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options lsOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "List build records",
|
||||||
|
Args: cli.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runLs(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
||||||
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsPrint(dockerCli command.Cli, records []historyRecord, in lsOptions) error {
|
||||||
|
if in.format == formatter.TableFormatKey {
|
||||||
|
in.format = lsDefaultTableFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := formatter.Context{
|
||||||
|
Output: dockerCli.Out(),
|
||||||
|
Format: formatter.Format(in.format),
|
||||||
|
Trunc: !in.noTrunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(records, func(a, b historyRecord) int {
|
||||||
|
if a.CompletedAt == nil && b.CompletedAt != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.CompletedAt != nil && b.CompletedAt == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
|
for _, r := range records {
|
||||||
|
if err := format(&lsContext{
|
||||||
|
format: formatter.Format(in.format),
|
||||||
|
isTerm: term,
|
||||||
|
trunc: !in.noTrunc,
|
||||||
|
record: &r,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lsCtx := lsContext{
|
||||||
|
isTerm: term,
|
||||||
|
trunc: !in.noTrunc,
|
||||||
|
}
|
||||||
|
lsCtx.Header = formatter.SubHeaderContext{
|
||||||
|
"Ref": lsHeaderBuildID,
|
||||||
|
"Name": lsHeaderName,
|
||||||
|
"Status": lsHeaderStatus,
|
||||||
|
"CreatedAt": lsHeaderCreated,
|
||||||
|
"Duration": lsHeaderDuration,
|
||||||
|
"Link": lsHeaderLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Write(&lsCtx, render)
|
||||||
|
}
|
||||||
|
|
||||||
|
type lsContext struct {
|
||||||
|
formatter.HeaderContext
|
||||||
|
|
||||||
|
isTerm bool
|
||||||
|
trunc bool
|
||||||
|
format formatter.Format
|
||||||
|
record *historyRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) MarshalJSON() ([]byte, error) {
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"ref": c.FullRef(),
|
||||||
|
"name": c.Name(),
|
||||||
|
"status": c.Status(),
|
||||||
|
"created_at": c.record.CreatedAt.AsTime().Format(time.RFC3339Nano),
|
||||||
|
"total_steps": c.record.NumTotalSteps,
|
||||||
|
"completed_steps": c.record.NumCompletedSteps,
|
||||||
|
"cached_steps": c.record.NumCachedSteps,
|
||||||
|
}
|
||||||
|
if c.record.CompletedAt != nil {
|
||||||
|
m["completed_at"] = c.record.CompletedAt.AsTime().Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
return json.Marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Ref() string {
|
||||||
|
return c.record.Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) FullRef() string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s", c.record.node.Builder, c.record.node.Name, c.record.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Name() string {
|
||||||
|
name := c.record.name
|
||||||
|
if c.trunc && c.format.IsTable() {
|
||||||
|
return trimBeginning(name, 36)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Status() string {
|
||||||
|
if c.record.CompletedAt != nil {
|
||||||
|
if c.record.Error != nil {
|
||||||
|
return "Error"
|
||||||
|
}
|
||||||
|
return "Completed"
|
||||||
|
}
|
||||||
|
return "Running"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) CreatedAt() string {
|
||||||
|
return units.HumanDuration(time.Since(c.record.CreatedAt.AsTime())) + " ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Duration() string {
|
||||||
|
lastTime := c.record.currentTimestamp
|
||||||
|
if c.record.CompletedAt != nil {
|
||||||
|
tm := c.record.CompletedAt.AsTime()
|
||||||
|
lastTime = &tm
|
||||||
|
}
|
||||||
|
if lastTime == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v := formatDuration(lastTime.Sub(c.record.CreatedAt.AsTime()))
|
||||||
|
if c.record.CompletedAt == nil {
|
||||||
|
v += "+"
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Link() string {
|
||||||
|
url := desktop.BuildURL(c.FullRef())
|
||||||
|
if c.format.IsTable() {
|
||||||
|
if c.isTerm {
|
||||||
|
return desktop.ANSIHyperlink(url, "Open")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
80
commands/history/open.go
Normal file
80
commands/history/open.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/browser"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type openOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runOpen(ctx context.Context, dockerCli command.Cli, opts openOptions) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
|
||||||
|
url := desktop.BuildURL(fmt.Sprintf("%s/%s/%s", rec.node.Builder, rec.node.Name, rec.Ref))
|
||||||
|
return browser.OpenURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options openOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "open [OPTIONS] [REF]",
|
||||||
|
Short: "Open a build in Docker Desktop",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
options.ref = args[0]
|
||||||
|
}
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runOpen(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
151
commands/history/rm.go
Normal file
151
commands/history/rm.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rmOptions struct {
|
||||||
|
builder string
|
||||||
|
refs []string
|
||||||
|
all bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runRm(ctx context.Context, dockerCli command.Cli, opts rmOptions) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([][]error, len(opts.refs))
|
||||||
|
for i := range errs {
|
||||||
|
errs[i] = make([]error, len(nodes))
|
||||||
|
}
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
for i, node := range nodes {
|
||||||
|
node := node
|
||||||
|
eg.Go(func() error {
|
||||||
|
if node.Driver == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c, err := node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
refs := opts.refs
|
||||||
|
|
||||||
|
if opts.all {
|
||||||
|
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||||
|
EarlyExit: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer serv.CloseSend()
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := serv.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Type == controlapi.BuildHistoryEventType_COMPLETE {
|
||||||
|
refs = append(refs, resp.Record.Ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, ref := range refs {
|
||||||
|
_, err = c.ControlClient().UpdateBuildHistory(ctx, &controlapi.UpdateBuildHistoryRequest{
|
||||||
|
Ref: ref,
|
||||||
|
Delete: true,
|
||||||
|
})
|
||||||
|
if opts.all {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errs[j][i] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []error
|
||||||
|
loop0:
|
||||||
|
for _, nodeErrs := range errs {
|
||||||
|
var nodeErr error
|
||||||
|
for _, err1 := range nodeErrs {
|
||||||
|
if err1 == nil {
|
||||||
|
continue loop0
|
||||||
|
}
|
||||||
|
if nodeErr == nil {
|
||||||
|
nodeErr = err1
|
||||||
|
} else {
|
||||||
|
nodeErr = multierror.Append(nodeErr, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, nodeErr)
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(out) == 1 {
|
||||||
|
return out[0]
|
||||||
|
}
|
||||||
|
return multierror.Append(out[0], out[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rmCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options rmOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "rm [OPTIONS] [REF...]",
|
||||||
|
Short: "Remove build records",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 && !options.all {
|
||||||
|
return errors.New("rm requires at least one argument")
|
||||||
|
}
|
||||||
|
if len(args) > 0 && options.all {
|
||||||
|
return errors.New("rm requires either --all or at least one argument")
|
||||||
|
}
|
||||||
|
options.refs = args
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runRm(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVar(&options.all, "all", false, "Remove all build records")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
30
commands/history/root.go
Normal file
30
commands/history/root.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RootOptions struct {
|
||||||
|
Builder *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "history",
|
||||||
|
Short: "Commands to work on build records",
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
RunE: rootcmd.RunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(
|
||||||
|
lsCmd(dockerCli, opts),
|
||||||
|
rmCmd(dockerCli, opts),
|
||||||
|
logsCmd(dockerCli, opts),
|
||||||
|
inspectCmd(dockerCli, opts),
|
||||||
|
openCmd(dockerCli, opts),
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
180
commands/history/utils.go
Normal file
180
commands/history/utils.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildName(fattrs map[string]string, ls *localstate.State) string {
|
||||||
|
var res string
|
||||||
|
|
||||||
|
var target, contextPath, dockerfilePath, vcsSource string
|
||||||
|
if v, ok := fattrs["target"]; ok {
|
||||||
|
target = v
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["context"]; ok {
|
||||||
|
contextPath = filepath.ToSlash(v)
|
||||||
|
} else if v, ok := fattrs["vcs:localdir:context"]; ok && v != "." {
|
||||||
|
contextPath = filepath.ToSlash(v)
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["vcs:source"]; ok {
|
||||||
|
vcsSource = v
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["filename"]; ok && v != "Dockerfile" {
|
||||||
|
dockerfilePath = filepath.ToSlash(v)
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["vcs:localdir:dockerfile"]; ok && v != "." {
|
||||||
|
dockerfilePath = filepath.ToSlash(filepath.Join(v, dockerfilePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
var localPath string
|
||||||
|
if ls != nil && !build.IsRemoteURL(ls.LocalPath) {
|
||||||
|
if ls.LocalPath != "" && ls.LocalPath != "-" {
|
||||||
|
localPath = filepath.ToSlash(ls.LocalPath)
|
||||||
|
}
|
||||||
|
if ls.DockerfilePath != "" && ls.DockerfilePath != "-" && ls.DockerfilePath != "Dockerfile" {
|
||||||
|
dockerfilePath = filepath.ToSlash(ls.DockerfilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove default dockerfile name
|
||||||
|
const defaultFilename = "/Dockerfile"
|
||||||
|
hasDefaultFileName := strings.HasSuffix(dockerfilePath, defaultFilename) || dockerfilePath == ""
|
||||||
|
dockerfilePath = strings.TrimSuffix(dockerfilePath, defaultFilename)
|
||||||
|
|
||||||
|
// dockerfile is a subpath of context
|
||||||
|
if strings.HasPrefix(dockerfilePath, localPath) && len(dockerfilePath) > len(localPath) {
|
||||||
|
res = dockerfilePath[strings.LastIndex(localPath, "/")+1:]
|
||||||
|
} else {
|
||||||
|
// Otherwise, use basename
|
||||||
|
bpath := localPath
|
||||||
|
if len(dockerfilePath) > 0 {
|
||||||
|
bpath = dockerfilePath
|
||||||
|
}
|
||||||
|
if len(bpath) > 0 {
|
||||||
|
lidx := strings.LastIndex(bpath, "/")
|
||||||
|
res = bpath[lidx+1:]
|
||||||
|
if !hasDefaultFileName {
|
||||||
|
if lidx != -1 {
|
||||||
|
res = filepath.ToSlash(filepath.Join(filepath.Base(bpath[:lidx]), res))
|
||||||
|
} else {
|
||||||
|
res = filepath.ToSlash(filepath.Join(filepath.Base(bpath), res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contextPath) > 0 {
|
||||||
|
res = contextPath
|
||||||
|
}
|
||||||
|
if len(target) > 0 {
|
||||||
|
if len(res) > 0 {
|
||||||
|
res = res + " (" + target + ")"
|
||||||
|
} else {
|
||||||
|
res = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res == "" && vcsSource != "" {
|
||||||
|
return vcsSource
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimBeginning(s string, n int) string {
|
||||||
|
if len(s) <= n {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ".." + s[len(s)-n+2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
type historyRecord struct {
|
||||||
|
*controlapi.BuildHistoryRecord
|
||||||
|
currentTimestamp *time.Time
|
||||||
|
node *builder.Node
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryRecords(ctx context.Context, ref string, nodes []builder.Node) ([]historyRecord, error) {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var out []historyRecord
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
for _, node := range nodes {
|
||||||
|
node := node
|
||||||
|
eg.Go(func() error {
|
||||||
|
if node.Driver == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var records []historyRecord
|
||||||
|
c, err := node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||||
|
EarlyExit: true,
|
||||||
|
Ref: ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md, err := serv.Header()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ts *time.Time
|
||||||
|
if v, ok := md[headerKeyTimestamp]; ok {
|
||||||
|
t, err := time.Parse(time.RFC3339Nano, v[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ts = &t
|
||||||
|
}
|
||||||
|
defer serv.CloseSend()
|
||||||
|
for {
|
||||||
|
he, err := serv.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if he.Type == controlapi.BuildHistoryEventType_DELETED || he.Record == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, historyRecord{
|
||||||
|
BuildHistoryRecord: he.Record,
|
||||||
|
currentTimestamp: ts,
|
||||||
|
node: &node,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
out = append(out, records...)
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
if d < time.Minute {
|
||||||
|
return fmt.Sprintf("%.1fs", d.Seconds())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dm %2ds", int(d.Minutes()), int(d.Seconds())%60)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
debugcmd "github.com/docker/buildx/commands/debug"
|
debugcmd "github.com/docker/buildx/commands/debug"
|
||||||
|
historycmd "github.com/docker/buildx/commands/history"
|
||||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||||
"github.com/docker/buildx/controller/remote"
|
"github.com/docker/buildx/controller/remote"
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
@ -106,6 +107,7 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) {
|
|||||||
pruneCmd(dockerCli, opts),
|
pruneCmd(dockerCli, opts),
|
||||||
duCmd(dockerCli, opts),
|
duCmd(dockerCli, opts),
|
||||||
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||||
|
historycmd.RootCmd(cmd, dockerCli, historycmd.RootOptions{Builder: &opts.builder}),
|
||||||
)
|
)
|
||||||
if confutil.IsExperimental() {
|
if confutil.IsExperimental() {
|
||||||
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
||||||
|
@ -17,6 +17,7 @@ Extended build capabilities with BuildKit
|
|||||||
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
||||||
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
||||||
| [`du`](buildx_du.md) | Disk usage |
|
| [`du`](buildx_du.md) | Disk usage |
|
||||||
|
| [`history`](buildx_history.md) | Commands to work on build records |
|
||||||
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||||
| [`ls`](buildx_ls.md) | List builder instances |
|
| [`ls`](buildx_ls.md) | List builder instances |
|
||||||
|
26
docs/reference/buildx_history.md
Normal file
26
docs/reference/buildx_history.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# docker buildx history
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Commands to work on build records
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|:---------------------------------------|:-------------------------------|
|
||||||
|
| [`inspect`](buildx_history_inspect.md) | Inspect a build |
|
||||||
|
| [`logs`](buildx_history_logs.md) | Print the logs of a build |
|
||||||
|
| [`ls`](buildx_history_ls.md) | List build records |
|
||||||
|
| [`open`](buildx_history_open.md) | Open a build in Docker Desktop |
|
||||||
|
| [`rm`](buildx_history_rm.md) | Remove build records |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
15
docs/reference/buildx_history_inspect.md
Normal file
15
docs/reference/buildx_history_inspect.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# docker buildx history inspect
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Inspect a build
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
16
docs/reference/buildx_history_logs.md
Normal file
16
docs/reference/buildx_history_logs.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# docker buildx history logs
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Print the logs of a build
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:--------------------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| `--progress` | `string` | `plain` | Set type of progress output (plain, rawjson, tty) |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
17
docs/reference/buildx_history_ls.md
Normal file
17
docs/reference/buildx_history_ls.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# docker buildx history ls
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List build records
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| `--format` | `string` | `table` | Format the output |
|
||||||
|
| `--no-trunc` | `bool` | | Don't truncate output |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
15
docs/reference/buildx_history_open.md
Normal file
15
docs/reference/buildx_history_open.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# docker buildx history open
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Open a build in Docker Desktop
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
16
docs/reference/buildx_history_rm.md
Normal file
16
docs/reference/buildx_history_rm.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# docker buildx history rm
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Remove build records
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--all` | `bool` | | Remove all build records |
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
5
go.mod
5
go.mod
@ -25,6 +25,7 @@ require (
|
|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/go-cty-funcs v0.0.0-20241120183456-c51673e0b3dd
|
github.com/hashicorp/go-cty-funcs v0.0.0-20241120183456-c51673e0b3dd
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0
|
github.com/hashicorp/hcl/v2 v2.23.0
|
||||||
github.com/in-toto/in-toto-golang v0.5.0
|
github.com/in-toto/in-toto-golang v0.5.0
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
@ -35,6 +36,7 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.0
|
github.com/opencontainers/image-spec v1.1.0
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
|
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
|
||||||
@ -49,6 +51,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/metric v1.31.0
|
go.opentelemetry.io/otel/metric v1.31.0
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0
|
go.opentelemetry.io/otel/sdk v1.31.0
|
||||||
go.opentelemetry.io/otel/trace v1.31.0
|
go.opentelemetry.io/otel/trace v1.31.0
|
||||||
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
||||||
golang.org/x/mod v0.21.0
|
golang.org/x/mod v0.21.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.10.0
|
||||||
golang.org/x/sys v0.28.0
|
golang.org/x/sys v0.28.0
|
||||||
@ -114,7 +117,6 @@ require (
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
@ -166,7 +168,6 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/oauth2 v0.23.0 // indirect
|
golang.org/x/oauth2 v0.23.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -359,6 +359,8 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq
|
|||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
@ -28,13 +28,14 @@ func BuildBackendEnabled() bool {
|
|||||||
return bbEnabled
|
return bbEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildURL(ref string) string {
|
||||||
|
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
||||||
|
}
|
||||||
|
|
||||||
func BuildDetailsOutput(refs map[string]string, term bool) string {
|
func BuildDetailsOutput(refs map[string]string, term bool) string {
|
||||||
if len(refs) == 0 {
|
if len(refs) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
refURL := func(ref string) string {
|
|
||||||
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
|
||||||
}
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
out.WriteString("View build details: ")
|
out.WriteString("View build details: ")
|
||||||
multiTargets := len(refs) > 1
|
multiTargets := len(refs) > 1
|
||||||
@ -43,9 +44,10 @@ func BuildDetailsOutput(refs map[string]string, term bool) string {
|
|||||||
out.WriteString(fmt.Sprintf("\n %s: ", target))
|
out.WriteString(fmt.Sprintf("\n %s: ", target))
|
||||||
}
|
}
|
||||||
if term {
|
if term {
|
||||||
out.WriteString(hyperlink(refURL(ref)))
|
url := BuildURL(ref)
|
||||||
|
out.WriteString(ANSIHyperlink(url, url))
|
||||||
} else {
|
} else {
|
||||||
out.WriteString(refURL(ref))
|
out.WriteString(BuildURL(ref))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
@ -57,9 +59,9 @@ func PrintBuildDetails(w io.Writer, refs map[string]string, term bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hyperlink(url string) string {
|
func ANSIHyperlink(url, text string) string {
|
||||||
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||||
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, url)
|
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorWithBuildRef struct {
|
type ErrorWithBuildRef struct {
|
||||||
|
23
vendor/github.com/pkg/browser/LICENSE
generated
vendored
Normal file
23
vendor/github.com/pkg/browser/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2014, Dave Cheney <dave@cheney.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
55
vendor/github.com/pkg/browser/README.md
generated
vendored
Normal file
55
vendor/github.com/pkg/browser/README.md
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
# browser
|
||||||
|
import "github.com/pkg/browser"
|
||||||
|
|
||||||
|
Package browser provides helpers to open files, readers, and urls in a browser window.
|
||||||
|
|
||||||
|
The choice of which browser is started is entirely client dependant.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
``` go
|
||||||
|
var Stderr io.Writer = os.Stderr
|
||||||
|
```
|
||||||
|
Stderr is the io.Writer to which executed commands write standard error.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var Stdout io.Writer = os.Stdout
|
||||||
|
```
|
||||||
|
Stdout is the io.Writer to which executed commands write standard output.
|
||||||
|
|
||||||
|
|
||||||
|
## func OpenFile
|
||||||
|
``` go
|
||||||
|
func OpenFile(path string) error
|
||||||
|
```
|
||||||
|
OpenFile opens new browser window for the file path.
|
||||||
|
|
||||||
|
|
||||||
|
## func OpenReader
|
||||||
|
``` go
|
||||||
|
func OpenReader(r io.Reader) error
|
||||||
|
```
|
||||||
|
OpenReader consumes the contents of r and presents the
|
||||||
|
results in a new browser window.
|
||||||
|
|
||||||
|
|
||||||
|
## func OpenURL
|
||||||
|
``` go
|
||||||
|
func OpenURL(url string) error
|
||||||
|
```
|
||||||
|
OpenURL opens a new browser window pointing to url.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- - -
|
||||||
|
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
|
57
vendor/github.com/pkg/browser/browser.go
generated
vendored
Normal file
57
vendor/github.com/pkg/browser/browser.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Package browser provides helpers to open files, readers, and urls in a browser window.
|
||||||
|
//
|
||||||
|
// The choice of which browser is started is entirely client dependant.
|
||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stdout is the io.Writer to which executed commands write standard output.
|
||||||
|
var Stdout io.Writer = os.Stdout
|
||||||
|
|
||||||
|
// Stderr is the io.Writer to which executed commands write standard error.
|
||||||
|
var Stderr io.Writer = os.Stderr
|
||||||
|
|
||||||
|
// OpenFile opens new browser window for the file path.
|
||||||
|
func OpenFile(path string) error {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return OpenURL("file://" + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenReader consumes the contents of r and presents the
|
||||||
|
// results in a new browser window.
|
||||||
|
func OpenReader(r io.Reader) error {
|
||||||
|
f, err := ioutil.TempFile("", "browser.*.html")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("browser: could not create temporary file: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(f, r); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return fmt.Errorf("browser: caching temporary file failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("browser: caching temporary file failed: %v", err)
|
||||||
|
}
|
||||||
|
return OpenFile(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenURL opens a new browser window pointing to url.
|
||||||
|
func OpenURL(url string) error {
|
||||||
|
return openBrowser(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCmd(prog string, args ...string) error {
|
||||||
|
cmd := exec.Command(prog, args...)
|
||||||
|
cmd.Stdout = Stdout
|
||||||
|
cmd.Stderr = Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
5
vendor/github.com/pkg/browser/browser_darwin.go
generated
vendored
Normal file
5
vendor/github.com/pkg/browser/browser_darwin.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
return runCmd("open", url)
|
||||||
|
}
|
14
vendor/github.com/pkg/browser/browser_freebsd.go
generated
vendored
Normal file
14
vendor/github.com/pkg/browser/browser_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
err := runCmd("xdg-open", url)
|
||||||
|
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||||
|
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
21
vendor/github.com/pkg/browser/browser_linux.go
generated
vendored
Normal file
21
vendor/github.com/pkg/browser/browser_linux.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
providers := []string{"xdg-open", "x-www-browser", "www-browser"}
|
||||||
|
|
||||||
|
// There are multiple possible providers to open a browser on linux
|
||||||
|
// One of them is xdg-open, another is x-www-browser, then there's www-browser, etc.
|
||||||
|
// Look for one that exists and run it
|
||||||
|
for _, provider := range providers {
|
||||||
|
if _, err := exec.LookPath(provider); err == nil {
|
||||||
|
return runCmd(provider, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &exec.Error{Name: strings.Join(providers, ","), Err: exec.ErrNotFound}
|
||||||
|
}
|
14
vendor/github.com/pkg/browser/browser_netbsd.go
generated
vendored
Normal file
14
vendor/github.com/pkg/browser/browser_netbsd.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
err := runCmd("xdg-open", url)
|
||||||
|
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||||
|
return errors.New("xdg-open: command not found - install xdg-utils from pkgsrc(7)")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
14
vendor/github.com/pkg/browser/browser_openbsd.go
generated
vendored
Normal file
14
vendor/github.com/pkg/browser/browser_openbsd.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
err := runCmd("xdg-open", url)
|
||||||
|
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||||
|
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
12
vendor/github.com/pkg/browser/browser_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/pkg/browser/browser_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build !linux,!windows,!darwin,!openbsd,!freebsd,!netbsd
|
||||||
|
|
||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS)
|
||||||
|
}
|
7
vendor/github.com/pkg/browser/browser_windows.go
generated
vendored
Normal file
7
vendor/github.com/pkg/browser/browser_windows.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL)
|
||||||
|
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@ -636,6 +636,9 @@ github.com/opencontainers/image-spec/specs-go/v1
|
|||||||
# github.com/pelletier/go-toml v1.9.5
|
# github.com/pelletier/go-toml v1.9.5
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/pelletier/go-toml
|
github.com/pelletier/go-toml
|
||||||
|
# github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
|
## explicit; go 1.14
|
||||||
|
github.com/pkg/browser
|
||||||
# github.com/pkg/errors v0.9.1
|
# github.com/pkg/errors v0.9.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/pkg/errors
|
github.com/pkg/errors
|
||||||
|
Loading…
x
Reference in New Issue
Block a user