Skip to content

Commit f118749

Browse files
committed
history: add error details to history inspect command
For failed builds, show the source with error location and last logs for vertex that caused the error. When debug mode is on, stacktrace is printed. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1 parent a18ff4d commit f118749

2 files changed

Lines changed: 120 additions & 4 deletions

File tree

commands/history/inspect.go

Lines changed: 119 additions & 3 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 {
@@ -186,14 +195,14 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
186195

187196
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().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,6 +318,46 @@ 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+
335+
var ve *errdefs.VertexError
336+
if errors.As(retErr, &ve) {
337+
dgst, err := digest.Parse(ve.Vertex.Digest)
338+
if err != nil {
339+
return errors.Wrapf(err, "failed to parse vertex digest %s", ve.Vertex.Digest)
340+
}
341+
name, logs, err := loadVertexLogs(ctx, c, rec.Ref, dgst, 16)
342+
if err != nil {
343+
return errors.Wrapf(err, "failed to load vertex logs %s", dgst)
344+
}
345+
if len(logs) > 0 {
346+
fmt.Fprintf(dockerCli.Out(), "\n => %s:\n", name)
347+
for _, l := range logs {
348+
fmt.Fprintln(dockerCli.Out(), l)
349+
}
350+
fmt.Fprintln(dockerCli.Out())
351+
}
352+
}
353+
354+
if debug.IsEnabled() {
355+
fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(retErr))
356+
} else if len(stack.Traces(retErr)) > 0 {
357+
fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n")
358+
}
359+
}
360+
312361
fmt.Fprintf(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
313362

314363
fmt.Fprintf(dockerCli.Out(), "View build in Docker Desktop: %s\n", desktop.BuildURL(rec.Ref))
@@ -342,6 +391,73 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
342391
return cmd
343392
}
344393

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