mirror of https://github.com/docker/buildx.git
build: display build details link
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
parent
ab5f5e4169
commit
0a2f35970c
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"github.com/docker/buildx/localstate"
|
"github.com/docker/buildx/localstate"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
|
@ -822,6 +823,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
||||||
|
|
||||||
for i, dp := range dps {
|
for i, dp := range dps {
|
||||||
i, dp, so := i, dp, *dp.so
|
i, dp, so := i, dp, *dp.so
|
||||||
|
node := nodes[dp.driverIndex]
|
||||||
if multiDriver {
|
if multiDriver {
|
||||||
for i, e := range so.Exports {
|
for i, e := range so.Exports {
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
|
@ -940,6 +942,16 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
||||||
} else {
|
} else {
|
||||||
rr, err = c.Build(ctx, so, "buildx", buildFunc, ch)
|
rr, err = c.Build(ctx, so, "buildx", buildFunc, ch)
|
||||||
}
|
}
|
||||||
|
if node.Driver.Features(ctx)[driver.HistoryAPI] && desktop.BuildBackendEnabled() {
|
||||||
|
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return &desktop.ErrorWithBuildRef{
|
||||||
|
Ref: buildRef,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress.WriteBuildRef(w, k, buildRef)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/docker/buildx/commands"
|
"github.com/docker/buildx/commands"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/version"
|
"github.com/docker/buildx/version"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli-plugins/manager"
|
"github.com/docker/cli/cli-plugins/manager"
|
||||||
|
@ -86,6 +87,9 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
|
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
|
||||||
}
|
}
|
||||||
|
if ebr, ok := err.(*desktop.ErrorWithBuildRef); ok {
|
||||||
|
ebr.Print(cmd.Err())
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/buildx/bake"
|
"github.com/docker/buildx/bake"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
|
@ -117,6 +119,11 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||||
progressTextDesc = fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver)
|
progressTextDesc = fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
|
||||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
|
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
|
||||||
progress.WithDesc(progressTextDesc, progressConsoleDesc),
|
progress.WithDesc(progressTextDesc, progressConsoleDesc),
|
||||||
)
|
)
|
||||||
|
@ -130,6 +137,9 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
|
if err == nil && cFlags.progress != progress.PrinterModeQuiet {
|
||||||
|
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/util/ioset"
|
"github.com/docker/buildx/util/ioset"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/buildx/util/tracing"
|
"github.com/docker/buildx/util/tracing"
|
||||||
|
@ -238,6 +239,11 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
progressMode, err := options.toProgress()
|
progressMode, err := options.toProgress()
|
||||||
|
@ -273,7 +279,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if progressMode == progress.PrinterModeQuiet {
|
if progressMode != progress.PrinterModeQuiet {
|
||||||
|
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||||
|
} else {
|
||||||
fmt.Println(getImageID(resp.ExporterResponse))
|
fmt.Println(getImageID(resp.ExporterResponse))
|
||||||
}
|
}
|
||||||
if options.imageIDFile != "" {
|
if options.imageIDFile != "" {
|
||||||
|
|
|
@ -19,6 +19,10 @@ func (w *writer) Write(status *client.SolveStatus) {
|
||||||
w.ch <- ToControlStatus(status)
|
w.ch <- ToControlStatus(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *writer) WriteBuildRef(target string, ref string) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (w *writer) ValidateLogSource(digest.Digest, interface{}) bool {
|
func (w *writer) ValidateLogSource(digest.Digest, interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package desktop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bbEnabledOnce sync.Once
|
||||||
|
bbEnabled bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildBackendEnabled() bool {
|
||||||
|
bbEnabledOnce.Do(func() {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = os.Stat(filepath.Join(home, ".docker", "desktop-build", ".lastaccess"))
|
||||||
|
bbEnabled = err == nil
|
||||||
|
})
|
||||||
|
return bbEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildDetailsOutput(refs map[string]string, term bool) string {
|
||||||
|
if len(refs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
refURL := func(ref string) string {
|
||||||
|
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
||||||
|
}
|
||||||
|
var out bytes.Buffer
|
||||||
|
out.WriteString("View build details: ")
|
||||||
|
multiTargets := len(refs) > 1
|
||||||
|
for target, ref := range refs {
|
||||||
|
if multiTargets {
|
||||||
|
out.WriteString(fmt.Sprintf("\n %s: ", target))
|
||||||
|
}
|
||||||
|
if term {
|
||||||
|
out.WriteString(hyperlink(refURL(ref)))
|
||||||
|
} else {
|
||||||
|
out.WriteString(refURL(ref))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintBuildDetails(w io.Writer, refs map[string]string, term bool) {
|
||||||
|
if out := BuildDetailsOutput(refs, term); out != "" {
|
||||||
|
fmt.Fprintf(w, "\n%s\n", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hyperlink(url string) string {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorWithBuildRef struct {
|
||||||
|
Ref string
|
||||||
|
Err error
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrorWithBuildRef) Error() string {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrorWithBuildRef) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrorWithBuildRef) Print(w io.Writer) error {
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n%s", BuildDetailsOutput(map[string]string{"default": e.Ref}, term))
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -33,6 +33,11 @@ type Printer struct {
|
||||||
warnings []client.VertexWarning
|
warnings []client.VertexWarning
|
||||||
logMu sync.Mutex
|
logMu sync.Mutex
|
||||||
logSourceMap map[digest.Digest]interface{}
|
logSourceMap map[digest.Digest]interface{}
|
||||||
|
|
||||||
|
// TODO: remove once we can use result context to pass build ref
|
||||||
|
// see https://github.com/docker/buildx/pull/1861
|
||||||
|
buildRefsMu sync.Mutex
|
||||||
|
buildRefs map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Printer) Wait() error {
|
func (p *Printer) Wait() error {
|
||||||
|
@ -143,6 +148,19 @@ func NewPrinter(ctx context.Context, w io.Writer, out console.File, mode string,
|
||||||
return pw, nil
|
return pw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Printer) WriteBuildRef(target string, ref string) {
|
||||||
|
p.buildRefsMu.Lock()
|
||||||
|
defer p.buildRefsMu.Unlock()
|
||||||
|
if p.buildRefs == nil {
|
||||||
|
p.buildRefs = map[string]string{}
|
||||||
|
}
|
||||||
|
p.buildRefs[target] = ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Printer) BuildRefs() map[string]string {
|
||||||
|
return p.buildRefs
|
||||||
|
}
|
||||||
|
|
||||||
type printerOpts struct {
|
type printerOpts struct {
|
||||||
displayOpts []progressui.DisplaySolveStatusOpt
|
displayOpts []progressui.DisplaySolveStatusOpt
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
Write(*client.SolveStatus)
|
Write(*client.SolveStatus)
|
||||||
|
WriteBuildRef(string, string)
|
||||||
ValidateLogSource(digest.Digest, interface{}) bool
|
ValidateLogSource(digest.Digest, interface{}) bool
|
||||||
ClearLogSource(interface{})
|
ClearLogSource(interface{})
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,10 @@ func Write(w Writer, name string, f func() error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WriteBuildRef(w Writer, target string, ref string) {
|
||||||
|
w.WriteBuildRef(target, ref)
|
||||||
|
}
|
||||||
|
|
||||||
func NewChannel(w Writer) (chan *client.SolveStatus, chan struct{}) {
|
func NewChannel(w Writer) (chan *client.SolveStatus, chan struct{}) {
|
||||||
ch := make(chan *client.SolveStatus)
|
ch := make(chan *client.SolveStatus)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
Loading…
Reference in New Issue