Skip to content

Commit 2ee1562

Browse files
authored
Merge pull request #2925 from tonistiigi/history-inspect-error
history: add error details to history inspect command
2 parents e74185a + 1335264 commit 2ee1562

File tree

2 files changed

+124
-6
lines changed

2 files changed

+124
-6
lines changed

commands/history/inspect.go

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package history
22

33
import (
4+
"bytes"
45
"cmp"
56
"context"
67
"encoding/json"
@@ -24,16 +25,24 @@ import (
2425
"github.com/docker/buildx/util/confutil"
2526
"github.com/docker/buildx/util/desktop"
2627
"github.com/docker/cli/cli/command"
28+
"github.com/docker/cli/cli/debug"
2729
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
2830
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
2931
controlapi "github.com/moby/buildkit/api/services/control"
32+
"github.com/moby/buildkit/client"
33+
"github.com/moby/buildkit/solver/errdefs"
3034
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
35+
"github.com/moby/buildkit/util/grpcerrors"
36+
"github.com/moby/buildkit/util/stack"
3137
"github.com/opencontainers/go-digest"
3238
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
3339
"github.com/pkg/errors"
3440
"github.com/spf13/cobra"
3541
"github.com/tonistiigi/go-csvvalue"
42+
spb "google.golang.org/genproto/googleapis/rpc/status"
3643
"google.golang.org/grpc/codes"
44+
"google.golang.org/grpc/status"
45+
proto "google.golang.org/protobuf/proto"
3746
)
3847

3948
type inspectOptions struct {
@@ -184,16 +193,16 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
184193

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

187-
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Format("2006-01-02 15:04:05"))
196+
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Local().Format("2006-01-02 15:04:05"))
188197
var duration time.Duration
189-
var status string
198+
var statusStr string
190199
if rec.CompletedAt != nil {
191200
duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
192201
} else {
193202
duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
194-
status = " (running)"
203+
statusStr = " (running)"
195204
}
196-
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), status)
205+
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), statusStr)
197206
if rec.Error != nil {
198207
if codes.Code(rec.Error.Code) == codes.Canceled {
199208
fmt.Fprintf(tw, "Status:\tCanceled\n")
@@ -309,9 +318,51 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
309318
fmt.Fprintln(dockerCli.Out())
310319
}
311320

321+
if rec.ExternalError != nil {
322+
dt, err := content.ReadBlob(ctx, store, ociDesc(rec.ExternalError))
323+
if err != nil {
324+
return errors.Wrapf(err, "failed to read external error %s", rec.ExternalError.Digest)
325+
}
326+
var st spb.Status
327+
if err := proto.Unmarshal(dt, &st); err != nil {
328+
return errors.Wrapf(err, "failed to unmarshal external error %s", rec.ExternalError.Digest)
329+
}
330+
retErr := grpcerrors.FromGRPC(status.ErrorProto(&st))
331+
for _, s := range errdefs.Sources(retErr) {
332+
s.Print(dockerCli.Out())
333+
}
334+
fmt.Fprintln(dockerCli.Out())
335+
336+
var ve *errdefs.VertexError
337+
if errors.As(retErr, &ve) {
338+
dgst, err := digest.Parse(ve.Vertex.Digest)
339+
if err != nil {
340+
return errors.Wrapf(err, "failed to parse vertex digest %s", ve.Vertex.Digest)
341+
}
342+
name, logs, err := loadVertexLogs(ctx, c, rec.Ref, dgst, 16)
343+
if err != nil {
344+
return errors.Wrapf(err, "failed to load vertex logs %s", dgst)
345+
}
346+
if len(logs) > 0 {
347+
fmt.Fprintln(dockerCli.Out(), "Logs:")
348+
fmt.Fprintf(dockerCli.Out(), "> => %s:\n", name)
349+
for _, l := range logs {
350+
fmt.Fprintln(dockerCli.Out(), "> "+l)
351+
}
352+
fmt.Fprintln(dockerCli.Out())
353+
}
354+
}
355+
356+
if debug.IsEnabled() {
357+
fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(retErr))
358+
} else if len(stack.Traces(retErr)) > 0 {
359+
fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n")
360+
}
361+
}
362+
312363
fmt.Fprintf(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
313364

314-
fmt.Fprintf(dockerCli.Out(), "View build in Docker Desktop: %s\n", desktop.BuildURL(rec.Ref))
365+
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)))
315366

316367
return nil
317368
}
@@ -342,6 +393,73 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
342393
return cmd
343394
}
344395

396+
func loadVertexLogs(ctx context.Context, c *client.Client, ref string, dgst digest.Digest, limit int) (string, []string, error) {
397+
st, err := c.ControlClient().Status(ctx, &controlapi.StatusRequest{
398+
Ref: ref,
399+
})
400+
if err != nil {
401+
return "", nil, err
402+
}
403+
404+
var name string
405+
var logs []string
406+
lastState := map[int]int{}
407+
408+
loop0:
409+
for {
410+
select {
411+
case <-ctx.Done():
412+
st.CloseSend()
413+
return "", nil, context.Cause(ctx)
414+
default:
415+
ev, err := st.Recv()
416+
if err != nil {
417+
if errors.Is(err, io.EOF) {
418+
break loop0
419+
}
420+
return "", nil, err
421+
}
422+
ss := client.NewSolveStatus(ev)
423+
for _, v := range ss.Vertexes {
424+
if v.Digest == dgst {
425+
name = v.Name
426+
break
427+
}
428+
}
429+
for _, l := range ss.Logs {
430+
if l.Vertex == dgst {
431+
parts := bytes.Split(l.Data, []byte("\n"))
432+
for i, p := range parts {
433+
var wrote bool
434+
if i == 0 {
435+
idx, ok := lastState[l.Stream]
436+
if ok && idx != -1 {
437+
logs[idx] = logs[idx] + string(p)
438+
wrote = true
439+
}
440+
}
441+
if !wrote {
442+
if len(p) > 0 {
443+
logs = append(logs, string(p))
444+
}
445+
lastState[l.Stream] = len(logs) - 1
446+
}
447+
if i == len(parts)-1 && len(p) == 0 {
448+
lastState[l.Stream] = -1
449+
}
450+
}
451+
}
452+
}
453+
}
454+
}
455+
456+
if limit > 0 && len(logs) > limit {
457+
logs = logs[len(logs)-limit:]
458+
}
459+
460+
return name, logs, nil
461+
}
462+
345463
type attachment struct {
346464
platform *ocispecs.Platform
347465
descr ocispecs.Descriptor

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ require (
5757
golang.org/x/sys v0.28.0
5858
golang.org/x/term v0.27.0
5959
golang.org/x/text v0.21.0
60+
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38
6061
google.golang.org/grpc v1.68.1
6162
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
6263
google.golang.org/protobuf v1.35.2
@@ -173,7 +174,6 @@ require (
173174
golang.org/x/time v0.6.0 // indirect
174175
golang.org/x/tools v0.25.0 // indirect
175176
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
176-
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
177177
gopkg.in/inf.v0 v0.9.1 // indirect
178178
gopkg.in/yaml.v2 v2.4.0 // indirect
179179
k8s.io/klog/v2 v2.130.1 // indirect

0 commit comments

Comments
 (0)