From fea74598800883df4fa5fb8a22e222bdfa93d054 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 3 Mar 2025 21:54:25 -0800 Subject: [PATCH 1/4] history: add history import command Signed-off-by: Tonis Tiigi --- commands/history/import.go | 102 ++++++++++++++++++++++++ commands/history/root.go | 1 + docs/reference/buildx_history.md | 1 + docs/reference/buildx_history_import.md | 16 ++++ util/desktop/paths_darwin.go | 21 +++++ util/desktop/paths_linux.go | 21 +++++ util/desktop/paths_unsupported.go | 13 +++ util/desktop/paths_windows.go | 5 ++ 8 files changed, 180 insertions(+) create mode 100644 commands/history/import.go create mode 100644 docs/reference/buildx_history_import.md create mode 100644 util/desktop/paths_darwin.go create mode 100644 util/desktop/paths_linux.go create mode 100644 util/desktop/paths_unsupported.go create mode 100644 util/desktop/paths_windows.go diff --git a/commands/history/import.go b/commands/history/import.go new file mode 100644 index 00000000..48995d94 --- /dev/null +++ b/commands/history/import.go @@ -0,0 +1,102 @@ +package history + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "os" + "strings" + + remoteutil "github.com/docker/buildx/driver/remote/util" + "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 importOptions struct { + file string +} + +func runImport(ctx context.Context, _ command.Cli, opts importOptions) error { + sock, err := desktop.BuildServerAddr() + if err != nil { + return err + } + + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { + network, addr, ok := strings.Cut(sock, "://") + if !ok { + return nil, errors.Errorf("invalid endpoint address: %s", sock) + } + return remoteutil.DialContext(ctx, network, addr) + } + + client := &http.Client{ + Transport: tr, + } + + var rdr io.Reader = os.Stdin + if opts.file != "" { + f, err := os.Open(opts.file) + if err != nil { + return errors.Wrap(err, "failed to open file") + } + defer f.Close() + rdr = f + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://docker-desktop/upload", rdr) + if err != nil { + return errors.Wrap(err, "failed to create request") + } + + resp, err := client.Do(req) + if err != nil { + return errors.Wrap(err, "failed to send request, check if Docker Desktop is running") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return errors.Errorf("failed to import build: %s", string(body)) + } + + var refs []string + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&refs); err != nil { + return errors.Wrap(err, "failed to decode response") + } + + if len(refs) == 0 { + return errors.New("no build records found in the bundle") + } + + url := desktop.BuildURL(fmt.Sprintf(".imported/_/%s", refs[0])) + return browser.OpenURL(url) +} + +func importCmd(dockerCli command.Cli, _ RootOptions) *cobra.Command { + var options importOptions + + cmd := &cobra.Command{ + Use: "import [OPTIONS] < bundle.dockerbuild", + Short: "Import a build into Docker Desktop", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return runImport(cmd.Context(), dockerCli, options) + }, + ValidArgsFunction: completion.Disable, + } + + flags := cmd.Flags() + flags.StringVarP(&options.file, "file", "f", "", "Import from a file path") + + return cmd +} diff --git a/commands/history/root.go b/commands/history/root.go index 0b88545b..ae1f821e 100644 --- a/commands/history/root.go +++ b/commands/history/root.go @@ -25,6 +25,7 @@ func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *c inspectCmd(dockerCli, opts), openCmd(dockerCli, opts), traceCmd(dockerCli, opts), + importCmd(dockerCli, opts), ) return cmd diff --git a/docs/reference/buildx_history.md b/docs/reference/buildx_history.md index 0935efb7..e2174453 100644 --- a/docs/reference/buildx_history.md +++ b/docs/reference/buildx_history.md @@ -7,6 +7,7 @@ Commands to work on build records | Name | Description | |:---------------------------------------|:-----------------------------------------------| +| [`import`](buildx_history_import.md) | Import a build into Docker Desktop | | [`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 | diff --git a/docs/reference/buildx_history_import.md b/docs/reference/buildx_history_import.md new file mode 100644 index 00000000..ca94f65f --- /dev/null +++ b/docs/reference/buildx_history_import.md @@ -0,0 +1,16 @@ +# docker buildx history import + + +Import a build into Docker Desktop + +### Options + +| Name | Type | Default | Description | +|:----------------|:---------|:--------|:-----------------------------------------| +| `--builder` | `string` | | Override the configured builder instance | +| `-D`, `--debug` | `bool` | | Enable debug logging | +| `-f`, `--file` | `string` | | Import from a file path | + + + + diff --git a/util/desktop/paths_darwin.go b/util/desktop/paths_darwin.go new file mode 100644 index 00000000..735ec0db --- /dev/null +++ b/util/desktop/paths_darwin.go @@ -0,0 +1,21 @@ +package desktop + +import ( + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const ( + socketName = "docker-desktop-build.sock" + socketPath = "Library/Containers/com.docker.docker/Data" +) + +func BuildServerAddr() (string, error) { + dir, err := os.UserHomeDir() + if err != nil { + return "", errors.Wrap(err, "failed to get user home directory") + } + return "unix://" + filepath.Join(dir, socketPath, socketName), nil +} diff --git a/util/desktop/paths_linux.go b/util/desktop/paths_linux.go new file mode 100644 index 00000000..3fcf8ec0 --- /dev/null +++ b/util/desktop/paths_linux.go @@ -0,0 +1,21 @@ +package desktop + +import ( + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const ( + socketName = "docker-desktop-build.sock" + socketPath = ".docker/desktop" +) + +func BuildServerAddr() (string, error) { + dir, err := os.UserHomeDir() + if err != nil { + return "", errors.Wrap(err, "failed to get user home directory") + } + return "unix://" + filepath.Join(dir, socketPath, socketName), nil +} diff --git a/util/desktop/paths_unsupported.go b/util/desktop/paths_unsupported.go new file mode 100644 index 00000000..0f6fffbb --- /dev/null +++ b/util/desktop/paths_unsupported.go @@ -0,0 +1,13 @@ +//go:build !windows && !darwin && !linux + +package desktop + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func BuildServerAddr() (string, error) { + return "", errors.Errorf("Docker Desktop unsupported on %s", runtime.GOOS) +} diff --git a/util/desktop/paths_windows.go b/util/desktop/paths_windows.go new file mode 100644 index 00000000..fd8f9c6e --- /dev/null +++ b/util/desktop/paths_windows.go @@ -0,0 +1,5 @@ +package desktop + +func BuildServerAddr() (string, error) { + return "npipe:////./pipe/dockerDesktopBuildServer", nil +} From 963b9ca30dafeea6c191f0bafebead3b13953cd6 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 4 Mar 2025 16:13:49 -0800 Subject: [PATCH 2/4] history: print urls after importing builds Signed-off-by: Tonis Tiigi --- commands/history/import.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/commands/history/import.go b/commands/history/import.go index 48995d94..926f68d7 100644 --- a/commands/history/import.go +++ b/commands/history/import.go @@ -23,7 +23,7 @@ type importOptions struct { file string } -func runImport(ctx context.Context, _ command.Cli, opts importOptions) error { +func runImport(ctx context.Context, dockerCli command.Cli, opts importOptions) error { sock, err := desktop.BuildServerAddr() if err != nil { return err @@ -78,8 +78,15 @@ func runImport(ctx context.Context, _ command.Cli, opts importOptions) error { return errors.New("no build records found in the bundle") } - url := desktop.BuildURL(fmt.Sprintf(".imported/_/%s", refs[0])) - return browser.OpenURL(url) + for i, ref := range refs { + url := desktop.BuildURL(fmt.Sprintf(".imported/_/%s", ref)) + fmt.Fprintln(dockerCli.Err(), url) + if i == 0 { + err = browser.OpenURL(url) + } + } + + return err } func importCmd(dockerCli command.Cli, _ RootOptions) *cobra.Command { From cadf4a5893b321137ee63a1089373a54f50f242c Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 5 Mar 2025 11:07:53 -0800 Subject: [PATCH 3/4] history: add multi-file/stdin import Signed-off-by: Tonis Tiigi --- commands/history/import.go | 94 ++++++++++++++++--------- docs/reference/buildx_history_import.md | 10 +-- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/commands/history/import.go b/commands/history/import.go index 926f68d7..afd95608 100644 --- a/commands/history/import.go +++ b/commands/history/import.go @@ -20,7 +20,7 @@ import ( ) type importOptions struct { - file string + file []string } func runImport(ctx context.Context, dockerCli command.Cli, opts importOptions) error { @@ -42,53 +42,79 @@ func runImport(ctx context.Context, dockerCli command.Cli, opts importOptions) e Transport: tr, } - var rdr io.Reader = os.Stdin - if opts.file != "" { - f, err := os.Open(opts.file) + var urls []string + + if len(opts.file) == 0 { + u, err := importFrom(ctx, client, os.Stdin) if err != nil { - return errors.Wrap(err, "failed to open file") + return err + } + urls = append(urls, u...) + } else { + for _, fn := range opts.file { + var f *os.File + var rdr io.Reader = os.Stdin + if fn != "-" { + f, err = os.Open(fn) + if err != nil { + return errors.Wrapf(err, "failed to open file %s", fn) + } + rdr = f + } + u, err := importFrom(ctx, client, rdr) + if err != nil { + return err + } + urls = append(urls, u...) + if f != nil { + f.Close() + } } - defer f.Close() - rdr = f } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://docker-desktop/upload", rdr) - if err != nil { - return errors.Wrap(err, "failed to create request") - } - - resp, err := client.Do(req) - if err != nil { - return errors.Wrap(err, "failed to send request, check if Docker Desktop is running") - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return errors.Errorf("failed to import build: %s", string(body)) - } - - var refs []string - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&refs); err != nil { - return errors.Wrap(err, "failed to decode response") - } - - if len(refs) == 0 { + if len(urls) == 0 { return errors.New("no build records found in the bundle") } - for i, ref := range refs { - url := desktop.BuildURL(fmt.Sprintf(".imported/_/%s", ref)) + for i, url := range urls { fmt.Fprintln(dockerCli.Err(), url) if i == 0 { err = browser.OpenURL(url) } } - return err } +func importFrom(ctx context.Context, c *http.Client, rdr io.Reader) ([]string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://docker-desktop/upload", rdr) + if err != nil { + return nil, errors.Wrap(err, "failed to create request") + } + + resp, err := c.Do(req) + if err != nil { + return nil, errors.Wrap(err, "failed to send request, check if Docker Desktop is running") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, errors.Errorf("failed to import build: %s", string(body)) + } + + var refs []string + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&refs); err != nil { + return nil, errors.Wrap(err, "failed to decode response") + } + + var urls []string + for _, ref := range refs { + urls = append(urls, desktop.BuildURL(fmt.Sprintf(".imported/_/%s", ref))) + } + return urls, err +} + func importCmd(dockerCli command.Cli, _ RootOptions) *cobra.Command { var options importOptions @@ -103,7 +129,7 @@ func importCmd(dockerCli command.Cli, _ RootOptions) *cobra.Command { } flags := cmd.Flags() - flags.StringVarP(&options.file, "file", "f", "", "Import from a file path") + flags.StringArrayVarP(&options.file, "file", "f", nil, "Import from a file path") return cmd } diff --git a/docs/reference/buildx_history_import.md b/docs/reference/buildx_history_import.md index ca94f65f..618a485d 100644 --- a/docs/reference/buildx_history_import.md +++ b/docs/reference/buildx_history_import.md @@ -5,11 +5,11 @@ Import a build into Docker Desktop ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:-----------------------------------------| -| `--builder` | `string` | | Override the configured builder instance | -| `-D`, `--debug` | `bool` | | Enable debug logging | -| `-f`, `--file` | `string` | | Import from a file path | +| Name | Type | Default | Description | +|:----------------|:--------------|:--------|:-----------------------------------------| +| `--builder` | `string` | | Override the configured builder instance | +| `-D`, `--debug` | `bool` | | Enable debug logging | +| `-f`, `--file` | `stringArray` | | Import from a file path | From 812b42b3295cf07da7a562b5cd736b54c25dedb4 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 10 Mar 2025 17:05:41 +0100 Subject: [PATCH 4/4] history: desktop build backend not yet supported on WSL Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- util/desktop/paths_linux.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/util/desktop/paths_linux.go b/util/desktop/paths_linux.go index 3fcf8ec0..9b90bf67 100644 --- a/util/desktop/paths_linux.go +++ b/util/desktop/paths_linux.go @@ -8,11 +8,19 @@ import ( ) const ( - socketName = "docker-desktop-build.sock" - socketPath = ".docker/desktop" + socketName = "docker-desktop-build.sock" + socketPath = ".docker/desktop" + wslSocketPath = "/mnt/wsl/docker-desktop/shared-sockets/host-services" ) func BuildServerAddr() (string, error) { + if os.Getenv("WSL_DISTRO_NAME") != "" { + socket := filepath.Join(wslSocketPath, socketName) + if _, err := os.Stat(socket); os.IsNotExist(err) { + return "", errors.New("Docker Desktop Build backend is not yet supported on WSL. Please run this command on Windows host instead.") //nolint:revive + } + return "unix://" + socket, nil + } dir, err := os.UserHomeDir() if err != nil { return "", errors.Wrap(err, "failed to get user home directory")