From 9d640f0e339e96287f2a83b96fa8ccf7f4e1b94b Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 3 Feb 2025 16:11:50 -0800 Subject: [PATCH 1/7] history: add formatting support to inspect Signed-off-by: Tonis Tiigi --- commands/history/inspect.go | 398 +++++++++++++++++++++++++++--------- 1 file changed, 298 insertions(+), 100 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index b79eb401..64daddff 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -45,11 +45,94 @@ import ( proto "google.golang.org/protobuf/proto" ) +type statusT string + +const ( + statusComplete statusT = "completed" + statusRunning statusT = "running" + statusError statusT = "failed" + statusCanceled statusT = "canceled" +) + type inspectOptions struct { builder string ref string } +type inspectOutput struct { + Context string `json:"context,omitempty"` + Dockerfile string `json:"dockerfile,omitempty"` + VCSRepository string `json:"vcs_repository,omitempty"` + VCSRevision string `json:"vcs_revision,omitempty"` + Target string `json:"target,omitempty"` + Platform []string `json:"platform,omitempty"` + KeepGitDir bool `json:"keep_git_dir,omitempty"` + + NamedContexts []keyValueOutput `json:"named_contexts,omitempty"` + + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"complete_at,omitempty"` + Duration time.Duration `json:"duration,omitempty"` + Status statusT `json:"status,omitempty"` + Error *errorOutput `json:"error,omitempty"` + + NumCompletedSteps int `json:"num_completed_steps"` + NumTotalSteps int `json:"num_total_steps"` + NumCachedSteps int `json:"num_cached_steps"` + + BuildArgs []keyValueOutput `json:"build_args,omitempty"` + Labels []keyValueOutput `json:"labels,omitempty"` + + Config configOutput `json:"config,omitempty"` + + Errors []string `json:"errors,omitempty"` +} + +type configOutput struct { + Network string `json:"network,omitempty"` + ExtraHosts []string `json:"extra_hosts,omitempty"` + Hostname string `json:"hostname,omitempty"` + CgroupParent string `json:"cgroup_parent,omitempty"` + ImageResolveMode string `json:"image_resolve_mode,omitempty"` + MultiPlatform bool `json:"multi_platform,omitempty"` + NoCache bool `json:"no_cache,omitempty"` + NoCacheFilter []string `json:"no_cache_filter,omitempty"` + + ShmSize string `json:"shm_size,omitempty"` + Ulimit string `json:"ulimit,omitempty"` + CacheMountNS string `json:"cache_mount_ns,omitempty"` + DockerfileCheckConfig string `json:"dockerfile_check_config,omitempty"` + SourceDateEpoch string `json:"source_date_epoch,omitempty"` + SandboxHostname string `json:"sandbox_hostname,omitempty"` + + RestRaw []keyValueOutput `json:"rest_raw,omitempty"` +} + +type errorOutput struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +type keyValueOutput struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +func readAttr[T any](attrs map[string]string, k string, dest *T, f func(v string) (T, bool)) { + if sv, ok := attrs[k]; ok { + if f != nil { + v, ok := f(sv) + if ok { + *dest = v + } + } + if d, ok := any(dest).(*string); ok { + *d = sv + } + } + delete(attrs, k) +} + func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { b, err := builder.New(dockerCli, builder.WithName(opts.builder)) if err != nil { @@ -97,17 +180,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) attrs := rec.FrontendAttrs delete(attrs, "frontend.caps") - writeAttr := func(k, name string, f func(v string) (string, bool)) { - if v, ok := attrs[k]; ok { - if f != nil { - v, ok = f(v) - } - if ok { - fmt.Fprintf(tw, "%s:\t%s\n", name, v) - } - } - delete(attrs, k) - } + var out inspectOutput var context string var dockerfile string @@ -146,124 +219,237 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } delete(attrs, "filename") - if context != "" { - fmt.Fprintf(tw, "Context:\t%s\n", context) - } - if dockerfile != "" { - fmt.Fprintf(tw, "Dockerfile:\t%s\n", dockerfile) - } + out.Context = context + out.Dockerfile = dockerfile + if _, ok := attrs["context"]; !ok { if src, ok := attrs["vcs:source"]; ok { - fmt.Fprintf(tw, "VCS Repository:\t%s\n", src) + out.VCSRepository = src } if rev, ok := attrs["vcs:revision"]; ok { - fmt.Fprintf(tw, "VCS Revision:\t%s\n", rev) + out.VCSRevision = rev } } - writeAttr("target", "Target", nil) - writeAttr("platform", "Platform", func(v string) (string, bool) { - return tryParseValue(v, func(v string) (string, error) { + readAttr(attrs, "target", &out.Target, nil) + + readAttr(attrs, "platform", &out.Platform, func(v string) ([]string, bool) { + return tryParseValue(v, &out.Errors, func(v string) ([]string, error) { var pp []string for _, v := range strings.Split(v, ",") { p, err := platforms.Parse(v) if err != nil { - return "", err + return nil, err } pp = append(pp, platforms.FormatAll(platforms.Normalize(p))) } - return strings.Join(pp, ", "), nil - }), true - }) - writeAttr("build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR", "Keep Git Dir", func(v string) (string, bool) { - return tryParseValue(v, func(v string) (string, error) { - b, err := strconv.ParseBool(v) - if err != nil { - return "", err - } - return strconv.FormatBool(b), nil - }), true + return pp, nil + }) }) - tw.Flush() + readAttr(attrs, "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR", &out.KeepGitDir, func(v string) (bool, bool) { + return tryParseValue(v, &out.Errors, strconv.ParseBool) + }) - fmt.Fprintln(dockerCli.Out()) + out.NamedContexts = readKeyValues(attrs, "context:") - printTable(dockerCli.Out(), attrs, "context:", "Named Context") - - tw = tabwriter.NewWriter(dockerCli.Out(), 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 - var statusStr string - if rec.CompletedAt != nil { - duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime()) - } else { - duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime()) - statusStr = " (running)" + if rec.CreatedAt != nil { + tm := rec.CreatedAt.AsTime().Local() + out.StartedAt = &tm } - fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), statusStr) + out.Status = statusRunning + + if rec.CompletedAt != nil { + tm := rec.CompletedAt.AsTime().Local() + out.CompletedAt = &tm + out.Status = statusComplete + } + if rec.Error != nil { if codes.Code(rec.Error.Code) == codes.Canceled { - fmt.Fprintf(tw, "Status:\tCanceled\n") + out.Status = statusCanceled } else { - fmt.Fprintf(tw, "Error:\t%s %s\n", codes.Code(rec.Error.Code).String(), rec.Error.Message) + out.Status = statusError + } + out.Error = &errorOutput{ + Code: int(codes.Code(rec.Error.Code)), + Message: rec.Error.Message, } } - 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()) + if out.StartedAt != nil { + if out.CompletedAt != nil { + out.Duration = out.CompletedAt.Sub(*out.StartedAt) + } else { + out.Duration = rec.currentTimestamp.Sub(*out.StartedAt) + } + } - tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) + out.BuildArgs = readKeyValues(attrs, "build-arg:") + out.Labels = readKeyValues(attrs, "label:") - writeAttr("force-network-mode", "Network", nil) - writeAttr("hostname", "Hostname", nil) - writeAttr("add-hosts", "Extra Hosts", func(v string) (string, bool) { - return tryParseValue(v, func(v string) (string, error) { + readAttr(attrs, "force-network-mode", &out.Config.Network, nil) + readAttr(attrs, "hostname", &out.Config.Hostname, nil) + readAttr(attrs, "cgroup-parent", &out.Config.CgroupParent, nil) + readAttr(attrs, "image-resolve-mode", &out.Config.ImageResolveMode, nil) + readAttr(attrs, "build-arg:BUILDKIT_MULTI_PLATFORM", &out.Config.MultiPlatform, func(v string) (bool, bool) { + return tryParseValue(v, &out.Errors, strconv.ParseBool) + }) + readAttr(attrs, "multi-platform", &out.Config.MultiPlatform, func(v string) (bool, bool) { + return tryParseValue(v, &out.Errors, strconv.ParseBool) + }) + readAttr(attrs, "no-cache", &out.Config.NoCache, func(v string) (bool, bool) { + if v == "" { + return true, true + } + return false, false + }) + readAttr(attrs, "no-cache", &out.Config.NoCacheFilter, func(v string) ([]string, bool) { + if v == "" { + return nil, false + } + return strings.Split(v, ","), true + }) + + readAttr(attrs, "add-hosts", &out.Config.ExtraHosts, func(v string) ([]string, bool) { + return tryParseValue(v, &out.Errors, func(v string) ([]string, error) { fields, err := csvvalue.Fields(v, nil) if err != nil { - return "", err + return nil, err } - return strings.Join(fields, ", "), nil - }), true + return fields, nil + }) }) - writeAttr("cgroup-parent", "Cgroup Parent", nil) - writeAttr("image-resolve-mode", "Image Resolve Mode", nil) - writeAttr("multi-platform", "Force Multi-Platform", nil) - writeAttr("build-arg:BUILDKIT_MULTI_PLATFORM", "Force Multi-Platform", nil) - writeAttr("no-cache", "Disable Cache", func(v string) (string, bool) { - if v == "" { - return "true", true - } - return v, true - }) - writeAttr("shm-size", "Shm Size", nil) - writeAttr("ulimit", "Resource Limits", nil) - writeAttr("build-arg:BUILDKIT_CACHE_MOUNT_NS", "Cache Mount Namespace", nil) - writeAttr("build-arg:BUILDKIT_DOCKERFILE_CHECK", "Dockerfile Check Config", nil) - writeAttr("build-arg:SOURCE_DATE_EPOCH", "Source Date Epoch", nil) - writeAttr("build-arg:SANDBOX_HOSTNAME", "Sandbox Hostname", nil) - var unusedAttrs []string + readAttr(attrs, "shm-size", &out.Config.ShmSize, nil) + readAttr(attrs, "ulimit", &out.Config.Ulimit, nil) + readAttr(attrs, "build-arg:BUILDKIT_CACHE_MOUNT_NS", &out.Config.CacheMountNS, nil) + readAttr(attrs, "build-arg:BUILDKIT_DOCKERFILE_CHECK", &out.Config.DockerfileCheckConfig, nil) + readAttr(attrs, "build-arg:SOURCE_DATE_EPOCH", &out.Config.SourceDateEpoch, nil) + readAttr(attrs, "build-arg:SANDBOX_HOSTNAME", &out.Config.SandboxHostname, nil) + + var unusedAttrs []keyValueOutput for k := range attrs { if strings.HasPrefix(k, "vcs:") || strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "label:") || strings.HasPrefix(k, "context:") || strings.HasPrefix(k, "attest:") { continue } - unusedAttrs = append(unusedAttrs, k) + unusedAttrs = append(unusedAttrs, keyValueOutput{ + Name: k, + Value: attrs[k], + }) } - slices.Sort(unusedAttrs) + slices.SortFunc(unusedAttrs, func(a, b keyValueOutput) int { + return cmp.Compare(a.Name, b.Name) + }) + out.Config.RestRaw = unusedAttrs - for _, k := range unusedAttrs { - fmt.Fprintf(tw, "%s:\t%s\n", k, attrs[k]) + if out.Context != "" { + fmt.Fprintf(tw, "Context:\t%s\n", out.Context) + } + if out.Dockerfile != "" { + fmt.Fprintf(tw, "Dockerfile:\t%s\n", out.Dockerfile) + } + if out.VCSRepository != "" { + fmt.Fprintf(tw, "VCS Repository:\t%s\n", out.VCSRepository) + } + if out.VCSRevision != "" { + fmt.Fprintf(tw, "VCS Revision:\t%s\n", out.VCSRevision) + } + + if out.Target != "" { + fmt.Fprintf(tw, "Target:\t%s\n", out.Target) + } + + if len(out.Platform) > 0 { + fmt.Fprintf(tw, "Platforms:\t%s\n", strings.Join(out.Platform, ", ")) + } + + if out.KeepGitDir { + fmt.Fprintf(tw, "Keep Git Dir:\t%s\n", strconv.FormatBool(out.KeepGitDir)) } tw.Flush() fmt.Fprintln(dockerCli.Out()) - printTable(dockerCli.Out(), attrs, "build-arg:", "Build Arg") - printTable(dockerCli.Out(), attrs, "label:", "Label") + printTable(dockerCli.Out(), out.NamedContexts, "Named Context") + + tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) + + fmt.Fprintf(tw, "Started:\t%s\n", out.StartedAt.Format("2006-01-02 15:04:05")) + var statusStr string + if out.Status == statusRunning { + statusStr = " (running)" + } + fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(out.Duration), statusStr) + + if out.Status == statusError { + fmt.Fprintf(tw, "Error:\t%s %s\n", codes.Code(rec.Error.Code).String(), rec.Error.Message) + } else if out.Status == statusCanceled { + fmt.Fprintf(tw, "Status:\tCanceled\n") + } + + fmt.Fprintf(tw, "Build Steps:\t%d/%d (%.0f%% cached)\n", out.NumCompletedSteps, out.NumTotalSteps, float64(out.NumCachedSteps)/float64(out.NumTotalSteps)*100) + tw.Flush() + + fmt.Fprintln(dockerCli.Out()) + + tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) + + if out.Config.Network != "" { + fmt.Fprintf(tw, "Network:\t%s\n", out.Config.Network) + } + if out.Config.Hostname != "" { + fmt.Fprintf(tw, "Hostname:\t%s\n", out.Config.Hostname) + } + if len(out.Config.ExtraHosts) > 0 { + fmt.Fprintf(tw, "Extra Hosts:\t%s\n", strings.Join(out.Config.ExtraHosts, ", ")) + } + if out.Config.CgroupParent != "" { + fmt.Fprintf(tw, "Cgroup Parent:\t%s\n", out.Config.CgroupParent) + } + if out.Config.ImageResolveMode != "" { + fmt.Fprintf(tw, "Image Resolve Mode:\t%s\n", out.Config.ImageResolveMode) + } + if out.Config.MultiPlatform { + fmt.Fprintf(tw, "Multi-Platform:\t%s\n", strconv.FormatBool(out.Config.MultiPlatform)) + } + if out.Config.NoCache { + fmt.Fprintf(tw, "No Cache:\t%s\n", strconv.FormatBool(out.Config.NoCache)) + } + if len(out.Config.NoCacheFilter) > 0 { + fmt.Fprintf(tw, "No Cache Filter:\t%s\n", strings.Join(out.Config.NoCacheFilter, ", ")) + } + + if out.Config.ShmSize != "" { + fmt.Fprintf(tw, "Shm Size:\t%s\n", out.Config.ShmSize) + } + if out.Config.Ulimit != "" { + fmt.Fprintf(tw, "Resource Limits:\t%s\n", out.Config.Ulimit) + } + if out.Config.CacheMountNS != "" { + fmt.Fprintf(tw, "Cache Mount Namespace:\t%s\n", out.Config.CacheMountNS) + } + if out.Config.DockerfileCheckConfig != "" { + fmt.Fprintf(tw, "Dockerfile Check Config:\t%s\n", out.Config.DockerfileCheckConfig) + } + if out.Config.SourceDateEpoch != "" { + fmt.Fprintf(tw, "Source Date Epoch:\t%s\n", out.Config.SourceDateEpoch) + } + if out.Config.SandboxHostname != "" { + fmt.Fprintf(tw, "Sandbox Hostname:\t%s\n", out.Config.SandboxHostname) + } + + for _, kv := range out.Config.RestRaw { + fmt.Fprintf(tw, "%s:\t%s\n", kv.Name, kv.Value) + } + + tw.Flush() + + fmt.Fprintln(dockerCli.Out()) + + printTable(dockerCli.Out(), out.BuildArgs, "Build Arg") + printTable(dockerCli.Out(), out.Labels, "Label") c, err := rec.node.Driver.Client(ctx) if err != nil { @@ -565,36 +751,48 @@ func descrType(desc ocispecs.Descriptor) string { return desc.MediaType } -func tryParseValue(s string, f func(string) (string, error)) string { +func tryParseValue[T any](s string, errs *[]string, f func(string) (T, error)) (T, bool) { v, err := f(s) if err != nil { - return fmt.Sprintf("%s (%v)", s, err) + errStr := fmt.Sprintf("failed to parse %s: (%v)", s, err) + *errs = append(*errs, errStr) } - return v + return v, true } -func printTable(w io.Writer, attrs map[string]string, prefix, title string) { - var keys []string - for k := range attrs { - if strings.HasPrefix(k, prefix) { - keys = append(keys, strings.TrimPrefix(k, prefix)) - } - } - slices.Sort(keys) - - if len(keys) == 0 { +func printTable(w io.Writer, kvs []keyValueOutput, title string) { + if len(kvs) == 0 { return } tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) fmt.Fprintf(tw, "%s\tVALUE\n", strings.ToUpper(title)) - for _, k := range keys { - fmt.Fprintf(tw, "%s\t%s\n", k, attrs[prefix+k]) + for _, k := range kvs { + fmt.Fprintf(tw, "%s\t%s\n", k.Name, k.Value) } tw.Flush() fmt.Fprintln(w) } +func readKeyValues(attrs map[string]string, prefix string) []keyValueOutput { + var out []keyValueOutput + for k, v := range attrs { + if strings.HasPrefix(k, prefix) { + out = append(out, keyValueOutput{ + Name: strings.TrimPrefix(k, prefix), + Value: v, + }) + } + } + if len(out) == 0 { + return nil + } + slices.SortFunc(out, func(a, b keyValueOutput) int { + return cmp.Compare(a.Name, b.Name) + }) + return out +} + func digestSetToDigests(ds slsa.DigestSet) []string { var out []string for k, v := range ds { From 04aab6958cb5feb012a3c607569573b5cab141e1 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Thu, 6 Feb 2025 16:12:26 +0100 Subject: [PATCH 2/7] history: set num steps, name, default platform and error logs to inspect Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/history/inspect.go | 137 ++++++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 54 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index 64daddff..a5e69900 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -60,6 +60,7 @@ type inspectOptions struct { } type inspectOutput struct { + Name string `json:"name,omitempty"` Context string `json:"context,omitempty"` Dockerfile string `json:"dockerfile,omitempty"` VCSRepository string `json:"vcs_repository,omitempty"` @@ -76,9 +77,9 @@ type inspectOutput struct { Status statusT `json:"status,omitempty"` Error *errorOutput `json:"error,omitempty"` - NumCompletedSteps int `json:"num_completed_steps"` - NumTotalSteps int `json:"num_total_steps"` - NumCachedSteps int `json:"num_cached_steps"` + NumCompletedSteps int32 `json:"num_completed_steps"` + NumTotalSteps int32 `json:"num_total_steps"` + NumCachedSteps int32 `json:"num_cached_steps"` BuildArgs []keyValueOutput `json:"build_args,omitempty"` Labels []keyValueOutput `json:"labels,omitempty"` @@ -109,8 +110,12 @@ type configOutput struct { } type errorOutput struct { - Code int `json:"code,omitempty"` - Message string `json:"message,omitempty"` + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Name string `json:"name,omitempty"` + Logs []string `json:"logs,omitempty"` + + statusErr error } type keyValueOutput struct { @@ -169,14 +174,32 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) rec := &recs[0] + c, err := rec.node.Driver.Client(ctx) + if err != nil { + return err + } + + store := proxy.NewContentStore(c.ContentClient()) + + var defaultPlatform string + 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 { + defaultPlatform = platforms.FormatAll(platforms.Normalize(p)) + break workers0 + } + } + ls, err := localstate.New(confutil.NewConfig(dockerCli)) if err != nil { return err } st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref) - tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) - attrs := rec.FrontendAttrs delete(attrs, "frontend.caps") @@ -219,6 +242,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } delete(attrs, "filename") + out.Name = buildName(rec.FrontendAttrs, st) out.Context = context out.Dockerfile = dockerfile @@ -243,6 +267,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } pp = append(pp, platforms.FormatAll(platforms.Normalize(p))) } + if len(pp) == 0 { + pp = append(pp, defaultPlatform) + } return pp, nil }) }) @@ -265,15 +292,40 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) out.Status = statusComplete } - if rec.Error != nil { - if codes.Code(rec.Error.Code) == codes.Canceled { - out.Status = statusCanceled - } else { - out.Status = statusError + if rec.Error != nil || rec.ExternalError != nil { + out.Error = &errorOutput{} + if rec.Error != nil { + if codes.Code(rec.Error.Code) == codes.Canceled { + out.Status = statusCanceled + } else { + out.Status = statusError + } + out.Error.Code = int(codes.Code(rec.Error.Code)) + out.Error.Message = rec.Error.Message } - out.Error = &errorOutput{ - Code: int(codes.Code(rec.Error.Code)), - Message: 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) + } + out.Error.statusErr = grpcerrors.FromGRPC(status.ErrorProto(&st)) + var ve *errdefs.VertexError + if errors.As(out.Error.statusErr, &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, 16) + if err != nil { + return errors.Wrapf(err, "failed to load vertex logs %s", dgst) + } + out.Error.Name = name + out.Error.Logs = logs + } } } @@ -285,6 +337,10 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } } + out.NumCompletedSteps = rec.NumCompletedSteps + out.NumTotalSteps = rec.NumTotalSteps + out.NumCachedSteps = rec.NumCachedSteps + out.BuildArgs = readKeyValues(attrs, "build-arg:") out.Labels = readKeyValues(attrs, "label:") @@ -343,6 +399,8 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) }) out.Config.RestRaw = unusedAttrs + tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) + if out.Context != "" { fmt.Fprintf(tw, "Context:\t%s\n", out.Context) } @@ -451,13 +509,6 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) printTable(dockerCli.Out(), out.BuildArgs, "Build Arg") printTable(dockerCli.Out(), out.Labels, "Label") - c, err := rec.node.Driver.Client(ctx) - if err != nil { - return err - } - - store := proxy.NewContentStore(c.ContentClient()) - attachments, err := allAttachments(ctx, store, *rec) if err != nil { return err @@ -504,44 +555,22 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) fmt.Fprintln(dockerCli.Out()) } - 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) - } - retErr := grpcerrors.FromGRPC(status.ErrorProto(&st)) - for _, s := range errdefs.Sources(retErr) { + if out.Error != nil { + for _, s := range errdefs.Sources(out.Error.statusErr) { s.Print(dockerCli.Out()) } fmt.Fprintln(dockerCli.Out()) - - 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, 16) - if err != nil { - 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) - for _, l := range logs { - fmt.Fprintln(dockerCli.Out(), "> "+l) - } - fmt.Fprintln(dockerCli.Out()) + if len(out.Error.Logs) > 0 { + fmt.Fprintln(dockerCli.Out(), "Logs:") + fmt.Fprintf(dockerCli.Out(), "> => %s:\n", out.Error.Name) + for _, l := range out.Error.Logs { + fmt.Fprintln(dockerCli.Out(), "> "+l) } + fmt.Fprintln(dockerCli.Out()) } - if debug.IsEnabled() { - fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(retErr)) - } else if len(stack.Traces(retErr)) > 0 { + fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(out.Error.statusErr)) + } else if len(stack.Traces(out.Error.statusErr)) > 0 { fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n") } } From ccca7c795ab0f10d4d02158a495549509220b976 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:25:49 +0100 Subject: [PATCH 3/7] history: json format support for inspect command Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/history/inspect.go | 13 ++++- docs/reference/buildx_history_inspect.md | 66 ++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index a5e69900..29b46769 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -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" @@ -57,6 +58,7 @@ const ( type inspectOptions struct { builder string ref string + format string } type inspectOutput struct { @@ -399,6 +401,14 @@ workers0: }) out.Config.RestRaw = unusedAttrs + if opts.format == formatter.JSONFormatKey { + enc := json.NewEncoder(dockerCli.Out()) + enc.SetIndent("", " ") + return enc.Encode(out) + } else if opts.format != formatter.RawFormatKey { + return errors.Errorf("unsupported format %q", opts.format) + } + tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) if out.Context != "" { @@ -603,7 +613,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 } diff --git a/docs/reference/buildx_history_inspect.md b/docs/reference/buildx_history_inspect.md index d3d6637a..e6f54cdb 100644 --- a/docs/reference/buildx_history_inspect.md +++ b/docs/reference/buildx_history_inspect.md @@ -12,11 +12,69 @@ Inspect a build ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:-----------------------------------------| -| `--builder` | `string` | | Override the configured builder instance | -| `-D`, `--debug` | `bool` | | Enable debug logging | +| Name | Type | Default | Description | +|:----------------------|:---------|:--------|:-----------------------------------------| +| `--builder` | `string` | | Override the configured builder instance | +| `-D`, `--debug` | `bool` | | Enable debug logging | +| [`--format`](#format) | `string` | `raw` | Format the output | +## Examples + +### Format the output (--format) + +Output format can be one of `raw`, `json`. + +```console +$ docker buildx history inspect --format raw +Context: . +Dockerfile: Dockerfile +VCS Repository: https://github.com/crazy-max/buildx.git +VCS Revision: 04aab6958cb5feb012a3c607569573b5cab141e1 +Target: binaries +Platforms: linux/amd64 +Keep Git Dir: true + +Started: 2025-02-06 16:15:13 +Duration: 1m 3s +Build Steps: 16/16 (25% cached) + + +Materials: +URI DIGEST +pkg:docker/docker/dockerfile@1 sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d801e66d25 +pkg:docker/golang@1.23-alpine3.21?platform=linux%2Famd64 sha256:2c49857f2295e89b23b28386e57e018a86620a8fede5003900f2d138ba9c4037 +pkg:docker/tonistiigi/xx@1.6.1?platform=linux%2Famd64 sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3 + +Attachments: +DIGEST PLATFORM TYPE +sha256:1b44912514074d3e309d80f8a5886a4d89eeeb52bef4d3e57ced17d1781bfce1 https://slsa.dev/provenance/v0.2 + +Print build logs: docker buildx history logs qrdbfvaoarfz42ye54lzx9aoy +``` + +```console +$ docker buildx history inspect --format json +{ + "name": "buildx (binaries)", + "context": ".", + "dockerfile": "Dockerfile", + "vcs_repository": "https://github.com/crazy-max/buildx.git", + "vcs_revision": "04aab6958cb5feb012a3c607569573b5cab141e1", + "target": "binaries", + "platform": [ + "linux/amd64" + ], + "keep_git_dir": true, + "started_at": "2025-02-06T16:15:13.077644732+01:00", + "complete_at": "2025-02-06T16:16:17.046656296+01:00", + "duration": 63969011564, + "status": "completed", + "num_completed_steps": 16, + "num_total_steps": 16, + "num_cached_steps": 4, + "config": {} +} +``` From 633e8a0881e409ee7e999412c64682ed53d7e0b1 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 7 Feb 2025 11:37:22 +0100 Subject: [PATCH 4/7] history: add error sources and stack to json output for inspect Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/history/inspect.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index 29b46769..d18be481 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -116,8 +116,8 @@ type errorOutput struct { Message string `json:"message,omitempty"` Name string `json:"name,omitempty"` Logs []string `json:"logs,omitempty"` - - statusErr error + Sources []byte `json:"sources,omitempty"` + Stack []byte `json:"stack,omitempty"` } type keyValueOutput struct { @@ -314,9 +314,15 @@ workers0: if err := proto.Unmarshal(dt, &st); err != nil { return errors.Wrapf(err, "failed to unmarshal external error %s", rec.ExternalError.Digest) } - out.Error.statusErr = grpcerrors.FromGRPC(status.ErrorProto(&st)) + retErr := grpcerrors.FromGRPC(status.ErrorProto(&st)) + var errsources bytes.Buffer + for _, s := range errdefs.Sources(retErr) { + s.Print(&errsources) + errsources.WriteString("\n") + } + out.Error.Sources = errsources.Bytes() var ve *errdefs.VertexError - if errors.As(out.Error.statusErr, &ve) { + 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) @@ -328,6 +334,7 @@ workers0: out.Error.Name = name out.Error.Logs = logs } + out.Error.Stack = []byte(fmt.Sprintf("%+v", stack.Formatter(retErr))) } } @@ -566,10 +573,9 @@ workers0: } if out.Error != nil { - for _, s := range errdefs.Sources(out.Error.statusErr) { - s.Print(dockerCli.Out()) + if out.Error.Sources != nil { + fmt.Fprint(dockerCli.Out(), string(out.Error.Sources)) } - fmt.Fprintln(dockerCli.Out()) if len(out.Error.Logs) > 0 { fmt.Fprintln(dockerCli.Out(), "Logs:") fmt.Fprintf(dockerCli.Out(), "> => %s:\n", out.Error.Name) @@ -578,10 +584,12 @@ workers0: } fmt.Fprintln(dockerCli.Out()) } - if debug.IsEnabled() { - fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(out.Error.statusErr)) - } else if len(stack.Traces(out.Error.statusErr)) > 0 { - fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n") + if len(out.Error.Stack) > 0 { + if debug.IsEnabled() { + fmt.Fprintf(dockerCli.Out(), "\n%s\n", out.Error.Stack) + } else { + fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n") + } } } From e236b862973af8e688fac7b2c947fcfd41a8b13a Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:57:05 +0100 Subject: [PATCH 5/7] history: set materials and attachments to json output for inspect Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/history/inspect.go | 90 ++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index d18be481..432f8d26 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -88,6 +88,9 @@ type inspectOutput struct { Config configOutput `json:"config,omitempty"` + Materials []materialOutput `json:"materials,omitempty"` + Attachments []attachmentOutput `json:"attachments,omitempty"` + Errors []string `json:"errors,omitempty"` } @@ -111,6 +114,17 @@ type configOutput struct { RestRaw []keyValueOutput `json:"rest_raw,omitempty"` } +type materialOutput struct { + URI string `json:"uri,omitempty"` + Digests []string `json:"digests,omitempty"` +} + +type attachmentOutput struct { + Digest string `json:"digest,omitempty"` + Platform string `json:"platform,omitempty"` + Type string `json:"type,omitempty"` +} + type errorOutput struct { Code int `json:"code,omitempty"` Message string `json:"message,omitempty"` @@ -408,6 +422,46 @@ workers0: }) out.Config.RestRaw = unusedAttrs + attachments, err := allAttachments(ctx, store, *rec) + if err != nil { + return err + } + + provIndex := slices.IndexFunc(attachments, func(a attachment) bool { + return descrType(a.descr) == slsa02.PredicateSLSAProvenance + }) + if provIndex != -1 { + prov := attachments[provIndex] + dt, err := content.ReadBlob(ctx, store, prov.descr) + if err != nil { + return errors.Errorf("failed to read provenance %s: %v", prov.descr.Digest, err) + } + var pred provenancetypes.ProvenancePredicate + if err := json.Unmarshal(dt, &pred); err != nil { + return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err) + } + for _, m := range pred.Materials { + out.Materials = append(out.Materials, materialOutput{ + URI: m.URI, + Digests: digestSetToDigests(m.Digest), + }) + } + } + + if len(attachments) > 0 { + for _, a := range attachments { + p := "" + if a.platform != nil { + p = platforms.FormatAll(*a.platform) + } + out.Attachments = append(out.Attachments, attachmentOutput{ + Digest: a.descr.Digest.String(), + Platform: p, + Type: descrType(a.descr), + }) + } + } + if opts.format == formatter.JSONFormatKey { enc := json.NewEncoder(dockerCli.Out()) enc.SetIndent("", " ") @@ -526,47 +580,23 @@ workers0: printTable(dockerCli.Out(), out.BuildArgs, "Build Arg") printTable(dockerCli.Out(), out.Labels, "Label") - attachments, err := allAttachments(ctx, store, *rec) - if err != nil { - return err - } - - provIndex := slices.IndexFunc(attachments, func(a attachment) bool { - return descrType(a.descr) == slsa02.PredicateSLSAProvenance - }) - if provIndex != -1 { - prov := attachments[provIndex] - - dt, err := content.ReadBlob(ctx, store, prov.descr) - if err != nil { - return errors.Errorf("failed to read provenance %s: %v", prov.descr.Digest, err) - } - - var pred provenancetypes.ProvenancePredicate - if err := json.Unmarshal(dt, &pred); err != nil { - return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err) - } - + if len(out.Materials) > 0 { fmt.Fprintln(dockerCli.Out(), "Materials:") tw = tabwriter.NewWriter(dockerCli.Out(), 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), ", ")) + for _, m := range out.Materials { + fmt.Fprintf(tw, "%s\t%s\n", m.URI, strings.Join(m.Digests, ", ")) } tw.Flush() fmt.Fprintln(dockerCli.Out()) } - if len(attachments) > 0 { + if len(out.Attachments) > 0 { fmt.Fprintf(tw, "Attachments:\n") tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) fmt.Fprintf(tw, "DIGEST\tPLATFORM\tTYPE\n") - for _, a := range attachments { - p := "" - if a.platform != nil { - p = platforms.FormatAll(*a.platform) - } - fmt.Fprintf(tw, "%s\t%s\t%s\n", a.descr.Digest, p, descrType(a.descr)) + for _, a := range out.Attachments { + fmt.Fprintf(tw, "%s\t%s\t%s\n", a.Digest, a.Platform, a.Type) } tw.Flush() fmt.Fprintln(dockerCli.Out()) From 417af36abc57fd41d4f6232bb1c3b9ea074facdd Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:09:05 +0100 Subject: [PATCH 6/7] history: support go template format for inspect Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/history/inspect.go | 124 +++++++++++++---------- docs/reference/buildx_history_inspect.md | 83 ++++++++++----- 2 files changed, 132 insertions(+), 75 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index 432f8d26..e83298cd 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "text/tabwriter" + "text/template" "time" "github.com/containerd/containerd/v2/core/content" @@ -62,81 +63,83 @@ type inspectOptions struct { } type inspectOutput struct { - Name string `json:"name,omitempty"` - Context string `json:"context,omitempty"` - Dockerfile string `json:"dockerfile,omitempty"` - VCSRepository string `json:"vcs_repository,omitempty"` - VCSRevision string `json:"vcs_revision,omitempty"` - Target string `json:"target,omitempty"` - Platform []string `json:"platform,omitempty"` - KeepGitDir bool `json:"keep_git_dir,omitempty"` + Name string `json:",omitempty"` + Ref string - NamedContexts []keyValueOutput `json:"named_contexts,omitempty"` + Context string `json:",omitempty"` + Dockerfile string `json:",omitempty"` + VCSRepository string `json:",omitempty"` + VCSRevision string `json:",omitempty"` + Target string `json:",omitempty"` + Platform []string `json:",omitempty"` + KeepGitDir bool `json:",omitempty"` - StartedAt *time.Time `json:"started_at,omitempty"` - CompletedAt *time.Time `json:"complete_at,omitempty"` - Duration time.Duration `json:"duration,omitempty"` - Status statusT `json:"status,omitempty"` - Error *errorOutput `json:"error,omitempty"` + NamedContexts []keyValueOutput `json:",omitempty"` - NumCompletedSteps int32 `json:"num_completed_steps"` - NumTotalSteps int32 `json:"num_total_steps"` - NumCachedSteps int32 `json:"num_cached_steps"` + StartedAt *time.Time `json:",omitempty"` + CompletedAt *time.Time `json:",omitempty"` + Duration time.Duration `json:",omitempty"` + Status statusT `json:",omitempty"` + Error *errorOutput `json:",omitempty"` - BuildArgs []keyValueOutput `json:"build_args,omitempty"` - Labels []keyValueOutput `json:"labels,omitempty"` + NumCompletedSteps int32 + NumTotalSteps int32 + NumCachedSteps int32 - Config configOutput `json:"config,omitempty"` + BuildArgs []keyValueOutput `json:",omitempty"` + Labels []keyValueOutput `json:",omitempty"` - Materials []materialOutput `json:"materials,omitempty"` - Attachments []attachmentOutput `json:"attachments,omitempty"` + Config configOutput `json:",omitempty"` - Errors []string `json:"errors,omitempty"` + Materials []materialOutput `json:",omitempty"` + Attachments []attachmentOutput `json:",omitempty"` + + Errors []string `json:",omitempty"` } type configOutput struct { - Network string `json:"network,omitempty"` - ExtraHosts []string `json:"extra_hosts,omitempty"` - Hostname string `json:"hostname,omitempty"` - CgroupParent string `json:"cgroup_parent,omitempty"` - ImageResolveMode string `json:"image_resolve_mode,omitempty"` - MultiPlatform bool `json:"multi_platform,omitempty"` - NoCache bool `json:"no_cache,omitempty"` - NoCacheFilter []string `json:"no_cache_filter,omitempty"` + Network string `json:",omitempty"` + ExtraHosts []string `json:",omitempty"` + Hostname string `json:",omitempty"` + CgroupParent string `json:",omitempty"` + ImageResolveMode string `json:",omitempty"` + MultiPlatform bool `json:",omitempty"` + NoCache bool `json:",omitempty"` + NoCacheFilter []string `json:",omitempty"` - ShmSize string `json:"shm_size,omitempty"` - Ulimit string `json:"ulimit,omitempty"` - CacheMountNS string `json:"cache_mount_ns,omitempty"` - DockerfileCheckConfig string `json:"dockerfile_check_config,omitempty"` - SourceDateEpoch string `json:"source_date_epoch,omitempty"` - SandboxHostname string `json:"sandbox_hostname,omitempty"` + ShmSize string `json:",omitempty"` + Ulimit string `json:",omitempty"` + CacheMountNS string `json:",omitempty"` + DockerfileCheckConfig string `json:",omitempty"` + SourceDateEpoch string `json:",omitempty"` + SandboxHostname string `json:",omitempty"` - RestRaw []keyValueOutput `json:"rest_raw,omitempty"` + RestRaw []keyValueOutput `json:",omitempty"` } type materialOutput struct { - URI string `json:"uri,omitempty"` - Digests []string `json:"digests,omitempty"` + URI string `json:",omitempty"` + Digests []string `json:",omitempty"` } type attachmentOutput struct { - Digest string `json:"digest,omitempty"` - Platform string `json:"platform,omitempty"` - Type string `json:"type,omitempty"` + Digest string `json:",omitempty"` + Platform string `json:",omitempty"` + Type string `json:",omitempty"` } type errorOutput struct { - Code int `json:"code,omitempty"` - Message string `json:"message,omitempty"` - Name string `json:"name,omitempty"` - Logs []string `json:"logs,omitempty"` - Sources []byte `json:"sources,omitempty"` - Stack []byte `json:"stack,omitempty"` + Code int `json:",omitempty"` + Message string `json:",omitempty"` + Name string `json:",omitempty"` + Logs []string `json:",omitempty"` + Sources []byte `json:",omitempty"` + Stack []byte `json:",omitempty"` } type keyValueOutput struct { - Name string `json:"name,omitempty"` - Value string `json:"value,omitempty"` + Name string `json:",omitempty"` + Value string `json:",omitempty"` } func readAttr[T any](attrs map[string]string, k string, dest *T, f func(v string) (T, bool)) { @@ -259,6 +262,8 @@ workers0: delete(attrs, "filename") out.Name = buildName(rec.FrontendAttrs, st) + out.Ref = rec.Ref + out.Context = context out.Dockerfile = dockerfile @@ -467,11 +472,26 @@ workers0: enc.SetIndent("", " ") return enc.Encode(out) } else if opts.format != formatter.RawFormatKey { - return errors.Errorf("unsupported format %q", opts.format) + tmpl, err := template.New("inspect").Parse(opts.format) + if err != nil { + return errors.Wrapf(err, "failed to parse format template") + } + var buf bytes.Buffer + if err := tmpl.Execute(&buf, out); err != nil { + return errors.Wrapf(err, "failed to execute format template") + } + fmt.Fprintln(dockerCli.Out(), buf.String()) + return nil } tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0) + if out.Name != "" { + fmt.Fprintf(tw, "Name:\t%s\n", out.Name) + } + if opts.ref == "" && out.Ref != "" { + fmt.Fprintf(tw, "Ref:\t%s\n", out.Ref) + } if out.Context != "" { fmt.Fprintf(tw, "Context:\t%s\n", out.Context) } diff --git a/docs/reference/buildx_history_inspect.md b/docs/reference/buildx_history_inspect.md index e6f54cdb..4af49f09 100644 --- a/docs/reference/buildx_history_inspect.md +++ b/docs/reference/buildx_history_inspect.md @@ -25,22 +25,25 @@ Inspect a build ### Format the output (--format) -Output format can be one of `raw`, `json`. +The formatting options (`--format`) pretty-prints the output to `raw` (default), +`json` or using a Go template. ```console -$ docker buildx history inspect --format raw +$ docker buildx history inspect +Name: buildx (binaries) Context: . Dockerfile: Dockerfile VCS Repository: https://github.com/crazy-max/buildx.git -VCS Revision: 04aab6958cb5feb012a3c607569573b5cab141e1 +VCS Revision: f15eaa1ee324ffbbab29605600d27a84cab86361 Target: binaries Platforms: linux/amd64 Keep Git Dir: true -Started: 2025-02-06 16:15:13 -Duration: 1m 3s +Started: 2025-02-07 11:56:24 +Duration: 1m 1s Build Steps: 16/16 (25% cached) +Image Resolve Mode: local Materials: URI DIGEST @@ -50,31 +53,65 @@ pkg:docker/tonistiigi/xx@1.6.1?platform=linux%2Famd64 sha256:923441d7c Attachments: DIGEST PLATFORM TYPE -sha256:1b44912514074d3e309d80f8a5886a4d89eeeb52bef4d3e57ced17d1781bfce1 https://slsa.dev/provenance/v0.2 +sha256:217329d2af959d4f02e3a96dcbe62bf100cab1feb8006a047ddfe51a5397f7e3 https://slsa.dev/provenance/v0.2 -Print build logs: docker buildx history logs qrdbfvaoarfz42ye54lzx9aoy +Print build logs: docker buildx history logs g9808bwrjrlkbhdamxklx660b ``` ```console $ docker buildx history inspect --format json { - "name": "buildx (binaries)", - "context": ".", - "dockerfile": "Dockerfile", - "vcs_repository": "https://github.com/crazy-max/buildx.git", - "vcs_revision": "04aab6958cb5feb012a3c607569573b5cab141e1", - "target": "binaries", - "platform": [ + "Name": "buildx (binaries)", + "Ref": "5w7vkqfi0rf59hw4hnmn627r9", + "Context": ".", + "Dockerfile": "Dockerfile", + "VCSRepository": "https://github.com/crazy-max/buildx.git", + "VCSRevision": "f15eaa1ee324ffbbab29605600d27a84cab86361", + "Target": "binaries", + "Platform": [ "linux/amd64" ], - "keep_git_dir": true, - "started_at": "2025-02-06T16:15:13.077644732+01:00", - "complete_at": "2025-02-06T16:16:17.046656296+01:00", - "duration": 63969011564, - "status": "completed", - "num_completed_steps": 16, - "num_total_steps": 16, - "num_cached_steps": 4, - "config": {} + "KeepGitDir": true, + "StartedAt": "2025-02-07T12:01:05.75807272+01:00", + "CompletedAt": "2025-02-07T12:02:07.991778875+01:00", + "Duration": 62233706155, + "Status": "completed", + "NumCompletedSteps": 16, + "NumTotalSteps": 16, + "NumCachedSteps": 4, + "Config": { + "ImageResolveMode": "local" + }, + "Materials": [ + { + "URI": "pkg:docker/docker/dockerfile@1", + "Digests": [ + "sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d801e66d25" + ] + }, + { + "URI": "pkg:docker/golang@1.23-alpine3.21?platform=linux%2Famd64", + "Digests": [ + "sha256:2c49857f2295e89b23b28386e57e018a86620a8fede5003900f2d138ba9c4037" + ] + }, + { + "URI": "pkg:docker/tonistiigi/xx@1.6.1?platform=linux%2Famd64", + "Digests": [ + "sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3" + ] + } + ], + "Attachments": [ + { + "Digest": "sha256:450fdd2e6b868fecd69e9891c2c404ba461aa38a47663b4805edeb8d2baf80b1", + "Type": "https://slsa.dev/provenance/v0.2" + } + ] } ``` + +```console +$ docker buildx history inspect --format "{{.Name}}: {{.VCSRepository}} ({{.VCSRevision}})" +buildx (binaries): https://github.com/crazy-max/buildx.git (f15eaa1ee324ffbbab29605600d27a84cab86361) +``` From 464f9278d1501ebd1da1c4504f826603affb9829 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:23:23 +0100 Subject: [PATCH 7/7] history: fix default format for inspect command Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/history/inspect.go | 4 ++-- docs/reference/buildx_history_inspect.md | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/commands/history/inspect.go b/commands/history/inspect.go index e83298cd..0dbfe21e 100644 --- a/commands/history/inspect.go +++ b/commands/history/inspect.go @@ -471,7 +471,7 @@ workers0: enc := json.NewEncoder(dockerCli.Out()) enc.SetIndent("", " ") return enc.Encode(out) - } else if opts.format != formatter.RawFormatKey { + } else if opts.format != formatter.PrettyFormatKey { tmpl, err := template.New("inspect").Parse(opts.format) if err != nil { return errors.Wrapf(err, "failed to parse format template") @@ -672,7 +672,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command { ) flags := cmd.Flags() - flags.StringVar(&options.format, "format", formatter.RawFormatKey, "Format the output") + flags.StringVar(&options.format, "format", formatter.PrettyFormatKey, "Format the output") return cmd } diff --git a/docs/reference/buildx_history_inspect.md b/docs/reference/buildx_history_inspect.md index 4af49f09..bfad9661 100644 --- a/docs/reference/buildx_history_inspect.md +++ b/docs/reference/buildx_history_inspect.md @@ -12,11 +12,11 @@ Inspect a build ### Options -| Name | Type | Default | Description | -|:----------------------|:---------|:--------|:-----------------------------------------| -| `--builder` | `string` | | Override the configured builder instance | -| `-D`, `--debug` | `bool` | | Enable debug logging | -| [`--format`](#format) | `string` | `raw` | Format the output | +| Name | Type | Default | Description | +|:----------------------|:---------|:---------|:-----------------------------------------| +| `--builder` | `string` | | Override the configured builder instance | +| `-D`, `--debug` | `bool` | | Enable debug logging | +| [`--format`](#format) | `string` | `pretty` | Format the output | @@ -25,7 +25,7 @@ Inspect a build ### Format the output (--format) -The formatting options (`--format`) pretty-prints the output to `raw` (default), +The formatting options (`--format`) pretty-prints the output to `pretty` (default), `json` or using a Go template. ```console