Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

history: add history import command #3039

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions commands/history/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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, dockerCli 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 urls []string

if len(opts.file) == 0 {
u, err := importFrom(ctx, client, os.Stdin)
if err != nil {
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()
}
}
}

if len(urls) == 0 {
return errors.New("no build records found in the bundle")
}

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

cmd := &cobra.Command{
Use: "import [OPTIONS] < bundle.dockerbuild",
Short: "Import a build into Docker Desktop",
Args: cobra.NoArgs,
Copy link
Member

@crazy-max crazy-max Mar 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use args as file input instead of a flag and require at least one arg. Multiple files should be supported like Docker Desktop does atm imo:

docker buildx history import FILE...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not usually the convention I think when the file is optional and defaults to stdin. If file is arg then for stdin, the user should write import -

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing multiple files together, I'm ok with.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah indeed I missed it was reading from stdin. Sounds good to support multiple --file then

RunE: func(cmd *cobra.Command, args []string) error {
return runImport(cmd.Context(), dockerCli, options)
},
ValidArgsFunction: completion.Disable,
}

flags := cmd.Flags()
flags.StringArrayVarP(&options.file, "file", "f", nil, "Import from a file path")

return cmd
}
1 change: 1 addition & 0 deletions commands/history/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/reference/buildx_history.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
16 changes: 16 additions & 0 deletions docs/reference/buildx_history_import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# docker buildx history import

<!---MARKER_GEN_START-->
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` | `stringArray` | | Import from a file path |


<!---MARKER_GEN_END-->

21 changes: 21 additions & 0 deletions util/desktop/paths_darwin.go
Original file line number Diff line number Diff line change
@@ -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
}
25 changes: 25 additions & 0 deletions util/desktop/paths_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package desktop

import (
"os"
"path/filepath"

"github.com/pkg/errors"
)

const (
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") != "" {
return "unix://" + filepath.Join(wslSocketPath, socketName), nil
}
Comment on lines +17 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed this change for testing WSL support but doesn't work. I'm taking a look if this can be solved but in the meantime we could disable support for import command on WSL and explain that it should be invoked on Windows host instead while this is being investigated.

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
}
13 changes: 13 additions & 0 deletions util/desktop/paths_unsupported.go
Original file line number Diff line number Diff line change
@@ -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)
}
5 changes: 5 additions & 0 deletions util/desktop/paths_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package desktop

func BuildServerAddr() (string, error) {
return "npipe:////./pipe/dockerDesktopBuildServer", nil
}