Files
buildx/commands/history/export.go
T

189 lines
4.4 KiB
Go

package history
import (
"context"
"io"
"os"
"github.com/containerd/console"
"github.com/containerd/platforms"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/localstate"
"github.com/docker/buildx/util/cobrautil/completion"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop/bundle"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type exportOptions struct {
builder string
refs []string
output string
all bool
finalize bool
}
func runExport(ctx context.Context, dockerCli command.Cli, opts exportOptions) error {
nodes, err := loadNodes(ctx, dockerCli, opts.builder)
if err != nil {
return err
}
if len(opts.refs) == 0 {
opts.refs = []string{""}
}
var res []historyRecord
for _, ref := range opts.refs {
recs, err := queryRecords(ctx, ref, nodes, &queryOptions{
CompletedOnly: true,
})
if err != nil {
return err
}
if len(recs) == 0 {
if ref == "" {
return errors.New("no records found")
}
return errors.Errorf("no record found for ref %q", ref)
}
toExport := recs
if !opts.all && ref == "" {
latestRef := recs[0].Ref
recCount := 0
for _, rec := range recs {
if rec.Ref != latestRef {
break
}
recCount++
}
toExport = recs[:recCount]
}
if opts.finalize {
seen := make(map[string]struct{}, len(toExport))
var finalized bool
for _, rec := range toExport {
if rec.node == nil || rec.Trace != nil {
continue
}
key := rec.node.Builder + "\x00" + rec.node.Name + "\x00" + rec.Ref
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
finalized = true
if err := finalizeRecord(ctx, rec.Ref, *rec.node); err != nil {
return err
}
}
if finalized {
queryRef := ref
if !opts.all {
queryRef = toExport[0].Ref
}
recs, err = queryRecords(ctx, queryRef, nodes, &queryOptions{
CompletedOnly: true,
})
if err != nil {
return err
}
toExport = recs
}
}
res = append(res, toExport...)
if opts.all {
break
}
}
ls, err := localstate.New(confutil.NewConfig(dockerCli))
if err != nil {
return err
}
visited := map[*builder.Node]struct{}{}
var clients []*client.Client
for _, rec := range res {
if _, ok := visited[rec.node]; ok {
continue
}
c, err := rec.node.Driver.Client(ctx)
if err != nil {
return err
}
visited[rec.node] = struct{}{}
clients = append(clients, c)
}
toExport := make([]*bundle.Record, 0, len(res))
for _, rec := range res {
var defaultPlatform string
if p := rec.node.Platforms; len(p) > 0 {
defaultPlatform = platforms.FormatAll(platforms.Normalize(p[0]))
}
var stg *localstate.StateGroup
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
if st != nil && st.GroupRef != "" {
stg, err = ls.ReadGroup(st.GroupRef)
if err != nil {
return err
}
}
toExport = append(toExport, &bundle.Record{
BuildHistoryRecord: rec.BuildHistoryRecord,
DefaultPlatform: defaultPlatform,
LocalState: st,
StateGroup: stg,
})
}
var w io.Writer = os.Stdout
if opts.output != "" {
f, err := os.Create(opts.output)
if err != nil {
return errors.Wrapf(err, "failed to create output file %q", opts.output)
}
defer f.Close()
w = f
} else {
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
return errors.Errorf("refusing to write to console, use --output to specify a file")
}
}
return bundle.Export(ctx, clients, w, toExport)
}
func exportCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
var options exportOptions
cmd := &cobra.Command{
Use: "export [OPTIONS] [REF...]",
Short: "Export build records into Docker Desktop bundle",
RunE: func(cmd *cobra.Command, args []string) error {
if options.all && len(args) > 0 {
return errors.New("cannot specify refs when using --all")
}
options.refs = args
options.builder = *rootOpts.Builder
return runExport(cmd.Context(), dockerCli, options)
},
ValidArgsFunction: completion.Disable,
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.StringVarP(&options.output, "output", "o", "", "Output file path")
flags.BoolVar(&options.all, "all", false, "Export all build records for the builder")
flags.BoolVar(&options.finalize, "finalize", false, "Ensure build records are finalized before exporting")
return cmd
}