Skip to content

Commit 00a0851

Browse files
committed
build: display build details link
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
1 parent e7034f6 commit 00a0851

7 files changed

Lines changed: 135 additions & 4 deletions

File tree

build/build.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/docker/buildx/builder"
2727
"github.com/docker/buildx/driver"
2828
"github.com/docker/buildx/localstate"
29+
"github.com/docker/buildx/util/desktop"
2930
"github.com/docker/buildx/util/dockerutil"
3031
"github.com/docker/buildx/util/imagetools"
3132
"github.com/docker/buildx/util/progress"
@@ -822,6 +823,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
822823

823824
for i, dp := range dps {
824825
i, dp, so := i, dp, *dp.so
826+
node := nodes[dp.driverIndex]
825827
if multiDriver {
826828
for i, e := range so.Exports {
827829
switch e.Type {
@@ -940,6 +942,16 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
940942
return res, nil
941943
}
942944
}, ch)
945+
if node.Driver.Features(ctx)[driver.HistoryAPI] && desktop.BuildBackendEnabled() {
946+
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
947+
if err != nil {
948+
return &ErrorWithBuildRef{
949+
Ref: buildRef,
950+
Err: err,
951+
}
952+
}
953+
progress.WriteBuildRef(w, k, buildRef)
954+
}
943955
if err != nil {
944956
return err
945957
}
@@ -1689,3 +1701,16 @@ func ReadSourcePolicy() (*spb.Policy, error) {
16891701

16901702
return &pol, nil
16911703
}
1704+
1705+
type ErrorWithBuildRef struct {
1706+
Ref string
1707+
Err error
1708+
}
1709+
1710+
func (e *ErrorWithBuildRef) Error() string {
1711+
return e.Err.Error()
1712+
}
1713+
1714+
func (e *ErrorWithBuildRef) Unwrap() error {
1715+
return e.Err
1716+
}

commands/bake.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88

9+
"github.com/containerd/console"
910
"github.com/containerd/containerd/platforms"
1011
"github.com/docker/buildx/bake"
1112
"github.com/docker/buildx/build"
@@ -117,8 +118,17 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
117118
progressTextDesc = fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver)
118119
}
119120

120-
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
121+
var isTerminal bool
122+
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
123+
isTerminal = true
124+
}
125+
126+
var printer *progress.Printer
127+
printer, err = progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
121128
progress.WithDesc(progressTextDesc, progressConsoleDesc),
129+
progress.WithOnClose(func() {
130+
printBuildDetails(os.Stderr, printer.BuildRefs(), isTerminal, cFlags.progress)
131+
}),
122132
)
123133
if err != nil {
124134
return err
@@ -193,7 +203,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
193203

194204
resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
195205
if err != nil {
196-
return wrapBuildError(err, true)
206+
return wrapBuildError(err, isTerminal, true)
197207
}
198208

199209
if len(in.metadataFile) > 0 {

commands/build.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,11 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
238238
return err
239239
}
240240

241+
var isTerminal bool
242+
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
243+
isTerminal = true
244+
}
245+
241246
ctx2, cancel := context.WithCancel(context.TODO())
242247
defer cancel()
243248
progressMode, err := options.toProgress()
@@ -252,6 +257,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
252257
),
253258
progress.WithOnClose(func() {
254259
printWarnings(os.Stderr, printer.Warnings(), progressMode)
260+
printBuildDetails(os.Stderr, printer.BuildRefs(), isTerminal, progressMode)
255261
}),
256262
)
257263
if err != nil {
@@ -270,7 +276,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
270276
retErr = err
271277
}
272278
if retErr != nil {
273-
return retErr
279+
return wrapBuildError(retErr, isTerminal, false)
274280
}
275281

276282
if options.quiet {
@@ -614,10 +620,15 @@ func decodeExporterResponse(exporterResponse map[string]string) map[string]inter
614620
return out
615621
}
616622

617-
func wrapBuildError(err error, bake bool) error {
623+
func wrapBuildError(err error, term bool, bake bool) error {
618624
if err == nil {
619625
return nil
620626
}
627+
if ebr, ok := err.(*build.ErrorWithBuildRef); ok {
628+
return errors.Errorf("%v\n\n%s", ebr.Err, buildDetailsOutput(map[string]string{
629+
"default": ebr.Ref,
630+
}, term, progress.PrinterModeAuto))
631+
}
621632
st, ok := grpcerrors.AsGRPCStatus(err)
622633
if ok {
623634
if st.Code() == codes.Unimplemented && strings.Contains(st.Message(), "unsupported frontend capability moby.buildkit.frontend.contexts") {
@@ -843,3 +854,37 @@ func printValue(printer printFunc, version string, format string, res map[string
843854
}
844855
return printer([]byte(res["result.json"]), os.Stdout)
845856
}
857+
858+
func buildDetailsOutput(refs map[string]string, term bool, mode string) string {
859+
if len(refs) == 0 || mode == progress.PrinterModeQuiet {
860+
return ""
861+
}
862+
refURL := func(ref string) string {
863+
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
864+
}
865+
var out bytes.Buffer
866+
out.WriteString("View build details: ")
867+
multiTargets := len(refs) > 1
868+
for target, ref := range refs {
869+
if multiTargets {
870+
out.WriteString(fmt.Sprintf("\n %s: ", target))
871+
}
872+
if term {
873+
out.WriteString(hyperlink(refURL(ref)))
874+
} else {
875+
out.WriteString(refURL(ref))
876+
}
877+
}
878+
return out.String()
879+
}
880+
881+
func hyperlink(url string) string {
882+
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
883+
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, url)
884+
}
885+
886+
func printBuildDetails(w io.Writer, refs map[string]string, term bool, mode string) {
887+
if out := buildDetailsOutput(refs, term, mode); out != "" {
888+
fmt.Fprintf(w, "\n%s\n", out)
889+
}
890+
}

controller/pb/progress.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ func (w *writer) Write(status *client.SolveStatus) {
1919
w.ch <- ToControlStatus(status)
2020
}
2121

22+
func (w *writer) WriteBuildRef(target string, ref string) {
23+
return
24+
}
25+
2226
func (w *writer) ValidateLogSource(digest.Digest, interface{}) bool {
2327
return true
2428
}

util/desktop/desktop.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package desktop
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"sync"
7+
)
8+
9+
var (
10+
bbEnabledOnce sync.Once
11+
bbEnabled bool
12+
)
13+
14+
func BuildBackendEnabled() bool {
15+
bbEnabledOnce.Do(func() {
16+
home, err := os.UserHomeDir()
17+
if err != nil {
18+
return
19+
}
20+
_, err = os.Stat(filepath.Join(home, ".docker", "desktop-build", ".lastaccess"))
21+
bbEnabled = err == nil
22+
})
23+
return bbEnabled
24+
}

util/progress/printer.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ type Printer struct {
3333
warnings []client.VertexWarning
3434
logMu sync.Mutex
3535
logSourceMap map[digest.Digest]interface{}
36+
37+
// TODO: remove once we can use result context to pass build ref
38+
// see https://github.com/docker/buildx/pull/1861
39+
buildRefsMu sync.Mutex
40+
buildRefs map[string]string
3641
}
3742

3843
func (p *Printer) Wait() error {
@@ -143,6 +148,19 @@ func NewPrinter(ctx context.Context, w io.Writer, out console.File, mode string,
143148
return pw, nil
144149
}
145150

151+
func (p *Printer) WriteBuildRef(target string, ref string) {
152+
p.buildRefsMu.Lock()
153+
defer p.buildRefsMu.Unlock()
154+
if p.buildRefs == nil {
155+
p.buildRefs = map[string]string{}
156+
}
157+
p.buildRefs[target] = ref
158+
}
159+
160+
func (p *Printer) BuildRefs() map[string]string {
161+
return p.buildRefs
162+
}
163+
146164
type printerOpts struct {
147165
displayOpts []progressui.DisplaySolveStatusOpt
148166

util/progress/writer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
type Writer interface {
1212
Write(*client.SolveStatus)
13+
WriteBuildRef(string, string)
1314
ValidateLogSource(digest.Digest, interface{}) bool
1415
ClearLogSource(interface{})
1516
}
@@ -41,6 +42,10 @@ func Write(w Writer, name string, f func() error) {
4142
})
4243
}
4344

45+
func WriteBuildRef(w Writer, target string, ref string) {
46+
w.WriteBuildRef(target, ref)
47+
}
48+
4449
func NewChannel(w Writer) (chan *client.SolveStatus, chan struct{}) {
4550
ch := make(chan *client.SolveStatus)
4651
done := make(chan struct{})

0 commit comments

Comments
 (0)