Skip to content

Commit 23afb70

Browse files
authored
Merge pull request #3039 from tonistiigi/history-import
history: add history import command
2 parents e19c729 + 812b42b commit 23afb70

File tree

8 files changed

+221
-0
lines changed

8 files changed

+221
-0
lines changed

commands/history/import.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package history
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net"
9+
"net/http"
10+
"os"
11+
"strings"
12+
13+
remoteutil "github.com/docker/buildx/driver/remote/util"
14+
"github.com/docker/buildx/util/cobrautil/completion"
15+
"github.com/docker/buildx/util/desktop"
16+
"github.com/docker/cli/cli/command"
17+
"github.com/pkg/browser"
18+
"github.com/pkg/errors"
19+
"github.com/spf13/cobra"
20+
)
21+
22+
type importOptions struct {
23+
file []string
24+
}
25+
26+
func runImport(ctx context.Context, dockerCli command.Cli, opts importOptions) error {
27+
sock, err := desktop.BuildServerAddr()
28+
if err != nil {
29+
return err
30+
}
31+
32+
tr := http.DefaultTransport.(*http.Transport).Clone()
33+
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
34+
network, addr, ok := strings.Cut(sock, "://")
35+
if !ok {
36+
return nil, errors.Errorf("invalid endpoint address: %s", sock)
37+
}
38+
return remoteutil.DialContext(ctx, network, addr)
39+
}
40+
41+
client := &http.Client{
42+
Transport: tr,
43+
}
44+
45+
var urls []string
46+
47+
if len(opts.file) == 0 {
48+
u, err := importFrom(ctx, client, os.Stdin)
49+
if err != nil {
50+
return err
51+
}
52+
urls = append(urls, u...)
53+
} else {
54+
for _, fn := range opts.file {
55+
var f *os.File
56+
var rdr io.Reader = os.Stdin
57+
if fn != "-" {
58+
f, err = os.Open(fn)
59+
if err != nil {
60+
return errors.Wrapf(err, "failed to open file %s", fn)
61+
}
62+
rdr = f
63+
}
64+
u, err := importFrom(ctx, client, rdr)
65+
if err != nil {
66+
return err
67+
}
68+
urls = append(urls, u...)
69+
if f != nil {
70+
f.Close()
71+
}
72+
}
73+
}
74+
75+
if len(urls) == 0 {
76+
return errors.New("no build records found in the bundle")
77+
}
78+
79+
for i, url := range urls {
80+
fmt.Fprintln(dockerCli.Err(), url)
81+
if i == 0 {
82+
err = browser.OpenURL(url)
83+
}
84+
}
85+
return err
86+
}
87+
88+
func importFrom(ctx context.Context, c *http.Client, rdr io.Reader) ([]string, error) {
89+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://docker-desktop/upload", rdr)
90+
if err != nil {
91+
return nil, errors.Wrap(err, "failed to create request")
92+
}
93+
94+
resp, err := c.Do(req)
95+
if err != nil {
96+
return nil, errors.Wrap(err, "failed to send request, check if Docker Desktop is running")
97+
}
98+
defer resp.Body.Close()
99+
100+
if resp.StatusCode != http.StatusOK {
101+
body, _ := io.ReadAll(resp.Body)
102+
return nil, errors.Errorf("failed to import build: %s", string(body))
103+
}
104+
105+
var refs []string
106+
dec := json.NewDecoder(resp.Body)
107+
if err := dec.Decode(&refs); err != nil {
108+
return nil, errors.Wrap(err, "failed to decode response")
109+
}
110+
111+
var urls []string
112+
for _, ref := range refs {
113+
urls = append(urls, desktop.BuildURL(fmt.Sprintf(".imported/_/%s", ref)))
114+
}
115+
return urls, err
116+
}
117+
118+
func importCmd(dockerCli command.Cli, _ RootOptions) *cobra.Command {
119+
var options importOptions
120+
121+
cmd := &cobra.Command{
122+
Use: "import [OPTIONS] < bundle.dockerbuild",
123+
Short: "Import a build into Docker Desktop",
124+
Args: cobra.NoArgs,
125+
RunE: func(cmd *cobra.Command, args []string) error {
126+
return runImport(cmd.Context(), dockerCli, options)
127+
},
128+
ValidArgsFunction: completion.Disable,
129+
}
130+
131+
flags := cmd.Flags()
132+
flags.StringArrayVarP(&options.file, "file", "f", nil, "Import from a file path")
133+
134+
return cmd
135+
}

commands/history/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *c
2525
inspectCmd(dockerCli, opts),
2626
openCmd(dockerCli, opts),
2727
traceCmd(dockerCli, opts),
28+
importCmd(dockerCli, opts),
2829
)
2930

3031
return cmd

docs/reference/buildx_history.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Commands to work on build records
77

88
| Name | Description |
99
|:---------------------------------------|:-----------------------------------------------|
10+
| [`import`](buildx_history_import.md) | Import a build into Docker Desktop |
1011
| [`inspect`](buildx_history_inspect.md) | Inspect a build |
1112
| [`logs`](buildx_history_logs.md) | Print the logs of a build |
1213
| [`ls`](buildx_history_ls.md) | List build records |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# docker buildx history import
2+
3+
<!---MARKER_GEN_START-->
4+
Import a build into Docker Desktop
5+
6+
### Options
7+
8+
| Name | Type | Default | Description |
9+
|:----------------|:--------------|:--------|:-----------------------------------------|
10+
| `--builder` | `string` | | Override the configured builder instance |
11+
| `-D`, `--debug` | `bool` | | Enable debug logging |
12+
| `-f`, `--file` | `stringArray` | | Import from a file path |
13+
14+
15+
<!---MARKER_GEN_END-->
16+

util/desktop/paths_darwin.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package desktop
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
const (
11+
socketName = "docker-desktop-build.sock"
12+
socketPath = "Library/Containers/com.docker.docker/Data"
13+
)
14+
15+
func BuildServerAddr() (string, error) {
16+
dir, err := os.UserHomeDir()
17+
if err != nil {
18+
return "", errors.Wrap(err, "failed to get user home directory")
19+
}
20+
return "unix://" + filepath.Join(dir, socketPath, socketName), nil
21+
}

util/desktop/paths_linux.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package desktop
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
const (
11+
socketName = "docker-desktop-build.sock"
12+
socketPath = ".docker/desktop"
13+
wslSocketPath = "/mnt/wsl/docker-desktop/shared-sockets/host-services"
14+
)
15+
16+
func BuildServerAddr() (string, error) {
17+
if os.Getenv("WSL_DISTRO_NAME") != "" {
18+
socket := filepath.Join(wslSocketPath, socketName)
19+
if _, err := os.Stat(socket); os.IsNotExist(err) {
20+
return "", errors.New("Docker Desktop Build backend is not yet supported on WSL. Please run this command on Windows host instead.") //nolint:revive
21+
}
22+
return "unix://" + socket, nil
23+
}
24+
dir, err := os.UserHomeDir()
25+
if err != nil {
26+
return "", errors.Wrap(err, "failed to get user home directory")
27+
}
28+
return "unix://" + filepath.Join(dir, socketPath, socketName), nil
29+
}

util/desktop/paths_unsupported.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !windows && !darwin && !linux
2+
3+
package desktop
4+
5+
import (
6+
"runtime"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
func BuildServerAddr() (string, error) {
12+
return "", errors.Errorf("Docker Desktop unsupported on %s", runtime.GOOS)
13+
}

util/desktop/paths_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package desktop
2+
3+
func BuildServerAddr() (string, error) {
4+
return "npipe:////./pipe/dockerDesktopBuildServer", nil
5+
}

0 commit comments

Comments
 (0)