Skip to content

Commit 5af58a4

Browse files
committed
build: display build details link
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
1 parent 0758a9b commit 5af58a4

5 files changed

Lines changed: 125 additions & 5 deletions

File tree

build/build.go

Lines changed: 17 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 {
@@ -885,6 +887,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
885887

886888
cc := c
887889
var printRes map[string][]byte
890+
var buildDetailsOpt *desktop.BuildDetailsOpt
888891
rr, err := c.Build(ctx, so, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
889892
var isFallback bool
890893
var origErr error
@@ -928,6 +931,13 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
928931
if opt.PrintFunc != nil {
929932
printRes = res.Metadata
930933
}
934+
if node.Driver.Features(ctx)[driver.HistoryAPI] && desktop.BuildBackendEnabled() {
935+
buildDetailsOpt = &desktop.BuildDetailsOpt{
936+
Builder: node.Builder,
937+
Node: node.Name,
938+
Ref: so.Ref,
939+
}
940+
}
931941
results.Set(resultKey(dp.driverIndex, k), res)
932942
if resultHandleFunc != nil {
933943
resultCtx, err := NewResultContext(ctx, cc, so, res)
@@ -951,6 +961,13 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
951961
for k, v := range printRes {
952962
rr.ExporterResponse[k] = string(v)
953963
}
964+
if buildDetailsOpt != nil {
965+
bddt, err := json.Marshal(buildDetailsOpt)
966+
if err != nil {
967+
return err
968+
}
969+
rr.ExporterResponse[desktop.BuildDetailsKey] = base64.StdEncoding.EncodeToString(bddt)
970+
}
954971

955972
node := nodes[dp.driverIndex].Driver
956973
if node.IsMobyDriver() {

commands/bake.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/docker/buildx/util/buildflags"
1414
"github.com/docker/buildx/util/cobrautil/completion"
1515
"github.com/docker/buildx/util/confutil"
16+
"github.com/docker/buildx/util/desktop"
1617
"github.com/docker/buildx/util/dockerutil"
1718
"github.com/docker/buildx/util/progress"
1819
"github.com/docker/buildx/util/tracing"
@@ -196,11 +197,20 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
196197
return wrapBuildError(err, true)
197198
}
198199

199-
if len(in.metadataFile) > 0 {
200-
dt := make(map[string]interface{})
201-
for t, r := range resp {
202-
dt[t] = decodeExporterResponse(r.ExporterResponse)
200+
dt := make(map[string]interface{})
201+
bddt := make(map[string]string)
202+
for t, r := range resp {
203+
dt[t] = decodeExporterResponse(r.ExporterResponse)
204+
if bd, ok := r.ExporterResponse[desktop.BuildDetailsKey]; ok && bd != "" {
205+
bddt[t] = bd
203206
}
207+
}
208+
209+
if !in.printOnly && cFlags.progress != progress.PrinterModeQuiet {
210+
desktop.PrintBuildDetails(bddt)
211+
}
212+
213+
if len(in.metadataFile) > 0 {
204214
if err := writeMetadataFile(in.metadataFile, dt); err != nil {
205215
return err
206216
}

commands/build.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/docker/buildx/store"
2727
"github.com/docker/buildx/store/storeutil"
2828
"github.com/docker/buildx/util/buildflags"
29+
"github.com/docker/buildx/util/desktop"
2930
"github.com/docker/buildx/util/ioset"
3031
"github.com/docker/buildx/util/progress"
3132
"github.com/docker/buildx/util/tracing"
@@ -273,9 +274,14 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
273274
return retErr
274275
}
275276

276-
if options.quiet {
277+
if progressMode == progress.PrinterModeQuiet {
277278
fmt.Println(getImageID(resp.ExporterResponse))
279+
} else if bd, ok := resp.ExporterResponse[desktop.BuildDetailsKey]; ok && bd != "" {
280+
desktop.PrintBuildDetails(map[string]string{
281+
"default": bd,
282+
})
278283
}
284+
279285
if options.imageIDFile != "" {
280286
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
281287
return errors.Wrap(err, "writing image ID file")

util/desktop/desktop.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package desktop
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"encoding/json"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"sync"
11+
12+
"github.com/docker/buildx/util/logutil"
13+
"github.com/sirupsen/logrus"
14+
)
15+
16+
const BuildDetailsKey = "desktop.build.details"
17+
18+
var (
19+
bbEnabledOnce sync.Once
20+
bbEnabled bool
21+
)
22+
23+
func BuildBackendEnabled() bool {
24+
bbEnabledOnce.Do(func() {
25+
home, err := os.UserHomeDir()
26+
if err != nil {
27+
return
28+
}
29+
_, err = os.Stat(filepath.Join(home, ".docker", "desktop-build", ".lastaccess"))
30+
bbEnabled = err == nil
31+
})
32+
return bbEnabled
33+
}
34+
35+
type BuildDetailsOpt struct {
36+
Builder string `json:"builder"`
37+
Node string `json:"node"`
38+
Ref string `json:"ref"`
39+
}
40+
41+
func PrintBuildDetails(resps map[string]string) {
42+
var out bytes.Buffer
43+
out.WriteString("View build details: ")
44+
multiTargets := len(resps) > 1
45+
if multiTargets {
46+
out.WriteString("\n")
47+
}
48+
for target, dtbd := range resps {
49+
opt := decodeBuildDetailsOpt(dtbd)
50+
if opt == nil {
51+
continue
52+
}
53+
url := fmt.Sprintf("https://open.docker.com://dashboard/build/%s/%s/%s", opt.Builder, opt.Node, opt.Ref)
54+
if multiTargets {
55+
out.WriteString(fmt.Sprintf(" %s: ", target))
56+
}
57+
out.WriteString(hyperlink(url, url))
58+
}
59+
l := logrus.StandardLogger()
60+
l.SetFormatter(&logutil.NoFormatter{})
61+
l.Printf("\n%s\n", out.String())
62+
}
63+
64+
func decodeBuildDetailsOpt(v string) *BuildDetailsOpt {
65+
dt, err := base64.StdEncoding.DecodeString(v)
66+
if err != nil {
67+
return nil
68+
}
69+
var opt BuildDetailsOpt
70+
if err = json.Unmarshal(dt, &opt); err != nil {
71+
return nil
72+
}
73+
return &opt
74+
}
75+
76+
func hyperlink(url, text string) string {
77+
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
78+
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\\n", url, text)
79+
}

util/logutil/format.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
2121
fmt.Fprintf(msg, "\n")
2222
return msg.Bytes(), nil
2323
}
24+
25+
type NoFormatter struct {
26+
logrus.TextFormatter
27+
}
28+
29+
func (f *NoFormatter) Format(entry *logrus.Entry) ([]byte, error) {
30+
return []byte(entry.Message), nil
31+
}

0 commit comments

Comments
 (0)