From eaa9bd58cd26c817da7a972a3659241705d0ec5d Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 3 Mar 2025 21:54:25 -0800 Subject: [PATCH] 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.go | 3 + util/desktop/paths_darwin.go | 18 +++++ util/desktop/paths_linux.go | 18 +++++ util/desktop/paths_unsupported.go | 13 +++ util/desktop/paths_windows.go | 5 ++ 9 files changed, 177 insertions(+) create mode 100644 commands/history/import.go create mode 100644 docs/reference/buildx_history_import.md create mode 100644 util/desktop/paths.go 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 000000000000..48995d94f3e1 --- /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 0b88545bc1ec..ae1f821effcc 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 0935efb7f59a..e2174453963f 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 000000000000..ca94f65f94f7 --- /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.go b/util/desktop/paths.go new file mode 100644 index 000000000000..ed82e9d054db --- /dev/null +++ b/util/desktop/paths.go @@ -0,0 +1,3 @@ +package desktop + +var socketName = "docker-desktop-build.sock" diff --git a/util/desktop/paths_darwin.go b/util/desktop/paths_darwin.go new file mode 100644 index 000000000000..dcfa321b6d08 --- /dev/null +++ b/util/desktop/paths_darwin.go @@ -0,0 +1,18 @@ +package desktop + +import ( + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const 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 000000000000..b9910f7c8a54 --- /dev/null +++ b/util/desktop/paths_linux.go @@ -0,0 +1,18 @@ +package desktop + +import ( + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const 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 000000000000..0f6fffbbddb5 --- /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 000000000000..fd8f9c6e8e27 --- /dev/null +++ b/util/desktop/paths_windows.go @@ -0,0 +1,5 @@ +package desktop + +func BuildServerAddr() (string, error) { + return "npipe:////./pipe/dockerDesktopBuildServer", nil +}