Skip to content

Commit

Permalink
history: inspect json format
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <[email protected]>
  • Loading branch information
crazy-max committed Feb 3, 2025
1 parent b066ee1 commit dc65c53
Show file tree
Hide file tree
Showing 2 changed files with 280 additions and 32 deletions.
187 changes: 159 additions & 28 deletions commands/history/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/debug"
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
Expand All @@ -48,6 +49,7 @@ import (
type inspectOptions struct {
builder string
ref string
format string
}

func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
Expand Down Expand Up @@ -92,7 +94,135 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
}
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)

tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
var sg *localstate.StateGroup
if st != nil && st.GroupRef != "" {
sg, _ = ls.ReadGroup(st.GroupRef)
}

switch opts.format {
case formatter.JSONFormatKey:
return inspectPrintJSON(ctx, rec, st, sg, dockerCli.Out())
case formatter.RawFormatKey:
return inspectPrintRaw(ctx, rec, st, dockerCli.Out())
default:
return errors.Errorf("unsupported format %q", opts.format)
}
}

func inspectPrintJSON(ctx context.Context, rec *historyRecord, ls *localstate.State, lsg *localstate.StateGroup, w io.Writer) error {
type buildError struct {
Name string `json:"name,omitempty"`
Sources string `json:"sources,omitempty"`
Logs string `json:"logs,omitempty"`
}

out := struct {
Record *controlapi.BuildHistoryRecord `json:"record"`
LocalState *localstate.State `json:"localState,omitempty"`
LocalStateGroup *localstate.StateGroup `json:"localStateGroup,omitempty"`
Name string `json:"name,omitempty"`
DefaultPlatform string `json:"defaultPlatform,omitempty"`
Status string `json:"status,omitempty"`
Duration time.Duration `json:"duration,omitempty"`
Error *buildError `json:"error,omitempty"`
}{
Record: rec.BuildHistoryRecord,
Name: buildName(rec.FrontendAttrs, ls),
LocalState: ls,
LocalStateGroup: lsg,
Status: "completed",
}

if rec.CompletedAt != nil {
out.Duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
} else {
out.Duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
out.Status = "running"
}

c, err := rec.node.Driver.Client(ctx)
if err != nil {
return err
}

workers, err := c.ListWorkers(ctx)
if err != nil {
return errors.Wrap(err, "failed to list workers")
}
workers0:
for _, w := range workers {
for _, p := range w.Platforms {
out.DefaultPlatform = platforms.FormatAll(platforms.Normalize(p))
break workers0
}
}

store := proxy.NewContentStore(c.ContentClient())

if rec.Error != nil || rec.ExternalError != nil {
out.Error = new(buildError)
if rec.Error != nil {
if rec.Error.Code == int32(codes.Canceled) {
out.Status = "canceled"
} else {
out.Status = "error"
out.Error.Logs = rec.Error.Message
}
}
if rec.ExternalError != nil {
dt, err := content.ReadBlob(ctx, store, ociDesc(rec.ExternalError))
if err != nil {
return errors.Wrapf(err, "failed to read external error %s", rec.ExternalError.Digest)
}
var st spb.Status
if err := proto.Unmarshal(dt, &st); err != nil {
return errors.Wrapf(err, "failed to unmarshal external error %s", rec.ExternalError.Digest)
}

if st.Code == int32(codes.Canceled) {
out.Status = "canceled"
} else {
out.Status = "error"
}

retErr := grpcerrors.FromGRPC(status.ErrorProto(&st))

var bsources bytes.Buffer
for _, s := range errdefs.Sources(retErr) {
s.Print(&bsources)
bsources.WriteString("\n")
}
out.Error.Sources = bsources.String()

var ve *errdefs.VertexError
if errors.As(retErr, &ve) {
dgst, err := digest.Parse(ve.Vertex.Digest)
if err != nil {
return errors.Wrapf(err, "failed to parse vertex digest %s", ve.Vertex.Digest)
}
name, logs, err := loadVertexLogs(ctx, c, rec.Ref, dgst, -1)
if err != nil {
return errors.Wrapf(err, "failed to load vertex logs %s", dgst)
}
out.Error.Name = name
if len(logs) > 0 {
var blogs bytes.Buffer
for _, l := range logs {
fmt.Fprintln(&blogs, l)
}
out.Error.Logs = blogs.String()
}
}
}
}

enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(out)
}

func inspectPrintRaw(ctx context.Context, rec *historyRecord, ls *localstate.State, w io.Writer) error {
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)

attrs := rec.FrontendAttrs
delete(attrs, "frontend.caps")
Expand All @@ -111,9 +241,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)

var context string
var dockerfile string
if st != nil {
context = st.LocalPath
dockerfile = st.DockerfilePath
if ls != nil {
context = ls.LocalPath
dockerfile = ls.DockerfilePath
wd, _ := os.Getwd()

if dockerfile != "" && dockerfile != "-" {
Expand Down Expand Up @@ -187,11 +317,11 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)

tw.Flush()

fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)

printTable(dockerCli.Out(), attrs, "context:", "Named Context")
printTable(w, attrs, "context:", "Named Context")

tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)

fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Local().Format("2006-01-02 15:04:05"))
var duration time.Duration
Expand All @@ -213,9 +343,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
fmt.Fprintf(tw, "Build Steps:\t%d/%d (%.0f%% cached)\n", rec.NumCompletedSteps, rec.NumTotalSteps, float64(rec.NumCachedSteps)/float64(rec.NumTotalSteps)*100)
tw.Flush()

fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)

tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)

writeAttr("force-network-mode", "Network", nil)
writeAttr("hostname", "Hostname", nil)
Expand Down Expand Up @@ -260,10 +390,10 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)

tw.Flush()

fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)

printTable(dockerCli.Out(), attrs, "build-arg:", "Build Arg")
printTable(dockerCli.Out(), attrs, "label:", "Label")
printTable(w, attrs, "build-arg:", "Build Arg")
printTable(w, attrs, "label:", "Label")

c, err := rec.node.Driver.Client(ctx)
if err != nil {
Expand Down Expand Up @@ -293,19 +423,19 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err)
}

fmt.Fprintln(dockerCli.Out(), "Materials:")
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
fmt.Fprintln(w, "Materials:")
tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
fmt.Fprintf(tw, "URI\tDIGEST\n")
for _, m := range pred.Materials {
fmt.Fprintf(tw, "%s\t%s\n", m.URI, strings.Join(digestSetToDigests(m.Digest), ", "))
}
tw.Flush()
fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)
}

if len(attachments) > 0 {
fmt.Fprintf(tw, "Attachments:\n")
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
fmt.Fprintf(tw, "DIGEST\tPLATFORM\tTYPE\n")
for _, a := range attachments {
p := ""
Expand All @@ -315,7 +445,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
fmt.Fprintf(tw, "%s\t%s\t%s\n", a.descr.Digest, p, descrType(a.descr))
}
tw.Flush()
fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)
}

if rec.ExternalError != nil {
Expand All @@ -329,9 +459,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
}
retErr := grpcerrors.FromGRPC(status.ErrorProto(&st))
for _, s := range errdefs.Sources(retErr) {
s.Print(dockerCli.Out())
s.Print(w)
}
fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)

var ve *errdefs.VertexError
if errors.As(retErr, &ve) {
Expand All @@ -344,25 +474,25 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
return errors.Wrapf(err, "failed to load vertex logs %s", dgst)
}
if len(logs) > 0 {
fmt.Fprintln(dockerCli.Out(), "Logs:")
fmt.Fprintf(dockerCli.Out(), "> => %s:\n", name)
fmt.Fprintln(w, "Logs:")
fmt.Fprintf(w, "> => %s:\n", name)
for _, l := range logs {
fmt.Fprintln(dockerCli.Out(), "> "+l)
fmt.Fprintln(w, "> "+l)
}
fmt.Fprintln(dockerCli.Out())
fmt.Fprintln(w)
}
}

if debug.IsEnabled() {
fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(retErr))
fmt.Fprintf(w, "\n%+v\n", stack.Formatter(retErr))
} else if len(stack.Traces(retErr)) > 0 {
fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n")
fmt.Fprintf(w, "Enable --debug to see stack traces for error\n")
}
}

fmt.Fprintf(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
fmt.Fprintf(w, "Print build logs: docker buildx history logs %s\n", rec.Ref)

fmt.Fprintf(dockerCli.Out(), "View build in Docker Desktop: %s\n", desktop.BuildURL(fmt.Sprintf("%s/%s/%s", rec.node.Builder, rec.node.Name, rec.Ref)))
fmt.Fprintf(w, "View build in Docker Desktop: %s\n", desktop.BuildURL(fmt.Sprintf("%s/%s/%s", rec.node.Builder, rec.node.Name, rec.Ref)))

return nil
}
Expand All @@ -388,7 +518,8 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
attachmentCmd(dockerCli, rootOpts),
)

// flags := cmd.Flags()
flags := cmd.Flags()
flags.StringVar(&options.format, "format", formatter.RawFormatKey, "Format the output")

return cmd
}
Expand Down
Loading

0 comments on commit dc65c53

Please sign in to comment.