Skip to content

Commit bc7bb88

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 963b9ca commit bc7bb88

File tree

4 files changed

+494
-0
lines changed

4 files changed

+494
-0
lines changed

commands/history/export.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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)
44+
if err != nil {
45+
return err
46+
}
47+
48+
if len(recs) == 0 {
49+
if opts.ref == "" {
50+
return errors.New("no records found")
51+
}
52+
return errors.Errorf("no record found for ref %q", opts.ref)
53+
}
54+
55+
if opts.ref == "" {
56+
slices.SortFunc(recs, func(a, b historyRecord) int {
57+
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
58+
})
59+
}
60+
61+
recs = recs[:1]
62+
63+
ls, err := localstate.New(confutil.NewConfig(dockerCli))
64+
if err != nil {
65+
return err
66+
}
67+
68+
c, err := recs[0].node.Driver.Client(ctx)
69+
if err != nil {
70+
return err
71+
}
72+
73+
toExport := make([]*bundle.Record, 0, len(recs))
74+
for _, rec := range recs {
75+
var defaultPlatform string
76+
if p := rec.node.Platforms; len(p) > 0 {
77+
defaultPlatform = platforms.FormatAll(platforms.Normalize(p[0]))
78+
}
79+
80+
var stg *localstate.StateGroup
81+
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
82+
if st != nil && st.GroupRef != "" {
83+
stg, err = ls.ReadGroup(st.GroupRef)
84+
if err != nil {
85+
return err
86+
}
87+
// avoid exporting definition for now, possible security impact
88+
stg.Inputs = nil
89+
stg.Definition = nil
90+
}
91+
92+
toExport = append(toExport, &bundle.Record{
93+
BuildHistoryRecord: rec.BuildHistoryRecord,
94+
DefaultPlatform: defaultPlatform,
95+
LocalState: st,
96+
StateGroup: stg,
97+
})
98+
}
99+
100+
var w io.Writer = os.Stdout
101+
if opts.output != "" {
102+
f, err := os.Create(opts.output)
103+
if err != nil {
104+
return errors.Wrapf(err, "failed to create output file %q", opts.output)
105+
}
106+
defer f.Close()
107+
w = f
108+
} else {
109+
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
110+
return errors.Errorf("refusing to write to console, use --output to specify a file")
111+
}
112+
}
113+
114+
return bundle.Export(ctx, c, w, toExport)
115+
}
116+
117+
func exportCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
118+
var options exportOptions
119+
120+
cmd := &cobra.Command{
121+
Use: "export [OPTIONS] [REF]",
122+
Short: "Export a build into Docker Desktop bundle",
123+
Args: cobra.MaximumNArgs(1),
124+
RunE: func(cmd *cobra.Command, args []string) error {
125+
if len(args) > 0 {
126+
options.ref = args[0]
127+
}
128+
options.builder = *rootOpts.Builder
129+
return runExport(cmd.Context(), dockerCli, options)
130+
},
131+
ValidArgsFunction: completion.Disable,
132+
}
133+
134+
flags := cmd.Flags()
135+
flags.StringVarP(&options.output, "output", "o", "", "Output file path")
136+
137+
return cmd
138+
}

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

0 commit comments

Comments
 (0)