Skip to content

Commit dbcbaa0

Browse files
committed
history: add export command
Allow builds to be exported into .dockerbuild bundles that can be shared and imported into Docker Desktop. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 18ccba0 commit dbcbaa0

File tree

6 files changed

+498
-0
lines changed

6 files changed

+498
-0
lines changed

commands/history/export.go

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package history
2+
3+
import (
4+
"context"
5+
"io"
6+
"os"
7+
"slices"
8+
9+
"github.com/containerd/console"
10+
"github.com/containerd/platforms"
11+
"github.com/docker/buildx/builder"
12+
"github.com/docker/buildx/localstate"
13+
"github.com/docker/buildx/util/cobrautil/completion"
14+
"github.com/docker/buildx/util/confutil"
15+
"github.com/docker/buildx/util/desktop/bundle"
16+
"github.com/docker/cli/cli/command"
17+
"github.com/pkg/errors"
18+
"github.com/spf13/cobra"
19+
)
20+
21+
type exportOptions struct {
22+
builder string
23+
ref string
24+
output string
25+
}
26+
27+
func runExport(ctx context.Context, dockerCli command.Cli, opts exportOptions) error {
28+
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
29+
if err != nil {
30+
return err
31+
}
32+
33+
nodes, err := b.LoadNodes(ctx, builder.WithData())
34+
if err != nil {
35+
return err
36+
}
37+
for _, node := range nodes {
38+
if node.Err != nil {
39+
return node.Err
40+
}
41+
}
42+
43+
recs, err := queryRecords(ctx, opts.ref, nodes, &queryOptions{
44+
CompletedOnly: true,
45+
})
46+
if err != nil {
47+
return err
48+
}
49+
50+
if len(recs) == 0 {
51+
if opts.ref == "" {
52+
return errors.New("no records found")
53+
}
54+
return errors.Errorf("no record found for ref %q", opts.ref)
55+
}
56+
57+
if opts.ref == "" {
58+
slices.SortFunc(recs, func(a, b historyRecord) int {
59+
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
60+
})
61+
}
62+
63+
recs = recs[:1]
64+
65+
ls, err := localstate.New(confutil.NewConfig(dockerCli))
66+
if err != nil {
67+
return err
68+
}
69+
70+
c, err := recs[0].node.Driver.Client(ctx)
71+
if err != nil {
72+
return err
73+
}
74+
75+
toExport := make([]*bundle.Record, 0, len(recs))
76+
for _, rec := range recs {
77+
var defaultPlatform string
78+
if p := rec.node.Platforms; len(p) > 0 {
79+
defaultPlatform = platforms.FormatAll(platforms.Normalize(p[0]))
80+
}
81+
82+
var stg *localstate.StateGroup
83+
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
84+
if st != nil && st.GroupRef != "" {
85+
stg, err = ls.ReadGroup(st.GroupRef)
86+
if err != nil {
87+
return err
88+
}
89+
}
90+
91+
toExport = append(toExport, &bundle.Record{
92+
BuildHistoryRecord: rec.BuildHistoryRecord,
93+
DefaultPlatform: defaultPlatform,
94+
LocalState: st,
95+
StateGroup: stg,
96+
})
97+
}
98+
99+
var w io.Writer = os.Stdout
100+
if opts.output != "" {
101+
f, err := os.Create(opts.output)
102+
if err != nil {
103+
return errors.Wrapf(err, "failed to create output file %q", opts.output)
104+
}
105+
defer f.Close()
106+
w = f
107+
} else {
108+
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
109+
return errors.Errorf("refusing to write to console, use --output to specify a file")
110+
}
111+
}
112+
113+
return bundle.Export(ctx, c, w, toExport)
114+
}
115+
116+
func exportCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
117+
var options exportOptions
118+
119+
cmd := &cobra.Command{
120+
Use: "export [OPTIONS] [REF]",
121+
Short: "Export a build into Docker Desktop bundle",
122+
Args: cobra.MaximumNArgs(1),
123+
RunE: func(cmd *cobra.Command, args []string) error {
124+
if len(args) > 0 {
125+
options.ref = args[0]
126+
}
127+
options.builder = *rootOpts.Builder
128+
return runExport(cmd.Context(), dockerCli, options)
129+
},
130+
ValidArgsFunction: completion.Disable,
131+
}
132+
133+
flags := cmd.Flags()
134+
flags.StringVarP(&options.output, "output", "o", "", "Output file path")
135+
136+
return cmd
137+
}

commands/history/root.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *c
2626
openCmd(dockerCli, opts),
2727
traceCmd(dockerCli, opts),
2828
importCmd(dockerCli, opts),
29+
exportCmd(dockerCli, opts),
2930
)
3031

3132
return cmd

docs/reference/buildx_history.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Commands to work on build records
77

88
| Name | Description |
99
|:---------------------------------------|:-----------------------------------------------|
10+
| [`export`](buildx_history_export.md) | Export a build into Docker Desktop bundle |
1011
| [`import`](buildx_history_import.md) | Import a build into Docker Desktop |
1112
| [`inspect`](buildx_history_inspect.md) | Inspect a build |
1213
| [`logs`](buildx_history_logs.md) | Print the logs of a build |
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# docker buildx history export
2+
3+
<!---MARKER_GEN_START-->
4+
Export a build into Docker Desktop bundle
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+
| `-o`, `--output` | `string` | | Output file path |
13+
14+
15+
<!---MARKER_GEN_END-->
16+

0 commit comments

Comments
 (0)