build: display build details link

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2023-05-26 10:30:58 +02:00 committed by CrazyMax
parent ab5f5e4169
commit 0a2f35970c
No known key found for this signature in database
GPG Key ID: 3248E46B6BB8C7F7
8 changed files with 148 additions and 1 deletions

View File

@ -26,6 +26,7 @@ import (
"github.com/docker/buildx/builder"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/localstate"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/imagetools"
"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 {
i, dp, so := i, dp, *dp.so
node := nodes[dp.driverIndex]
if multiDriver {
for i, e := range so.Exports {
switch e.Type {
@ -940,6 +942,16 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} else {
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 {
return err
}

View File

@ -5,6 +5,7 @@ import (
"os"
"github.com/docker/buildx/commands"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/version"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
@ -86,6 +87,9 @@ func main() {
} else {
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
}
if ebr, ok := err.(*desktop.ErrorWithBuildRef); ok {
ebr.Print(cmd.Err())
}
os.Exit(1)
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"github.com/containerd/console"
"github.com/containerd/containerd/platforms"
"github.com/docker/buildx/bake"
"github.com/docker/buildx/build"
@ -13,6 +14,7 @@ import (
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/cobrautil/completion"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/progress"
"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)
}
var term bool
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
term = true
}
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
progress.WithDesc(progressTextDesc, progressConsoleDesc),
)
@ -130,6 +137,9 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
if err == nil {
err = err1
}
if err == nil && cFlags.progress != progress.PrinterModeQuiet {
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
}
}
}()

View File

@ -26,6 +26,7 @@ import (
"github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/ioset"
"github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/tracing"
@ -238,6 +239,11 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
return err
}
var term bool
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
term = true
}
ctx2, cancel := context.WithCancel(context.TODO())
defer cancel()
progressMode, err := options.toProgress()
@ -273,7 +279,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
return retErr
}
if progressMode == progress.PrinterModeQuiet {
if progressMode != progress.PrinterModeQuiet {
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
} else {
fmt.Println(getImageID(resp.ExporterResponse))
}
if options.imageIDFile != "" {

View File

@ -19,6 +19,10 @@ func (w *writer) Write(status *client.SolveStatus) {
w.ch <- ToControlStatus(status)
}
func (w *writer) WriteBuildRef(target string, ref string) {
return
}
func (w *writer) ValidateLogSource(digest.Digest, interface{}) bool {
return true
}

86
util/desktop/desktop.go Normal file
View File

@ -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
}

View File

@ -33,6 +33,11 @@ type Printer struct {
warnings []client.VertexWarning
logMu sync.Mutex
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 {
@ -143,6 +148,19 @@ func NewPrinter(ctx context.Context, w io.Writer, out console.File, mode string,
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 {
displayOpts []progressui.DisplaySolveStatusOpt

View File

@ -10,6 +10,7 @@ import (
type Writer interface {
Write(*client.SolveStatus)
WriteBuildRef(string, string)
ValidateLogSource(digest.Digest, interface{}) bool
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{}) {
ch := make(chan *client.SolveStatus)
done := make(chan struct{})