mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 09:17:49 +08:00
Merge pull request #2925 from tonistiigi/history-inspect-error
history: add error details to history inspect command
This commit is contained in:
commit
2ee156236b
@ -1,6 +1,7 @@
|
|||||||
package history
|
package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -24,16 +25,24 @@ import (
|
|||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/debug"
|
||||||
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
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"
|
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
||||||
controlapi "github.com/moby/buildkit/api/services/control"
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
|
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/moby/buildkit/util/stack"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tonistiigi/go-csvvalue"
|
"github.com/tonistiigi/go-csvvalue"
|
||||||
|
spb "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
proto "google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
@ -184,16 +193,16 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
|||||||
|
|
||||||
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Format("2006-01-02 15:04:05"))
|
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Local().Format("2006-01-02 15:04:05"))
|
||||||
var duration time.Duration
|
var duration time.Duration
|
||||||
var status string
|
var statusStr string
|
||||||
if rec.CompletedAt != nil {
|
if rec.CompletedAt != nil {
|
||||||
duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
|
duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
|
||||||
} else {
|
} else {
|
||||||
duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
|
duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
|
||||||
status = " (running)"
|
statusStr = " (running)"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), status)
|
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), statusStr)
|
||||||
if rec.Error != nil {
|
if rec.Error != nil {
|
||||||
if codes.Code(rec.Error.Code) == codes.Canceled {
|
if codes.Code(rec.Error.Code) == codes.Canceled {
|
||||||
fmt.Fprintf(tw, "Status:\tCanceled\n")
|
fmt.Fprintf(tw, "Status:\tCanceled\n")
|
||||||
@ -309,9 +318,51 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
|||||||
fmt.Fprintln(dockerCli.Out())
|
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) {
|
||||||
|
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 debug.IsEnabled() {
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "\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(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
|
fmt.Fprintf(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "View build in Docker Desktop: %s\n", desktop.BuildURL(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)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -342,6 +393,73 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadVertexLogs(ctx context.Context, c *client.Client, ref string, dgst digest.Digest, limit int) (string, []string, error) {
|
||||||
|
st, err := c.ControlClient().Status(ctx, &controlapi.StatusRequest{
|
||||||
|
Ref: ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var logs []string
|
||||||
|
lastState := map[int]int{}
|
||||||
|
|
||||||
|
loop0:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
st.CloseSend()
|
||||||
|
return "", nil, context.Cause(ctx)
|
||||||
|
default:
|
||||||
|
ev, err := st.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break loop0
|
||||||
|
}
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
ss := client.NewSolveStatus(ev)
|
||||||
|
for _, v := range ss.Vertexes {
|
||||||
|
if v.Digest == dgst {
|
||||||
|
name = v.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range ss.Logs {
|
||||||
|
if l.Vertex == dgst {
|
||||||
|
parts := bytes.Split(l.Data, []byte("\n"))
|
||||||
|
for i, p := range parts {
|
||||||
|
var wrote bool
|
||||||
|
if i == 0 {
|
||||||
|
idx, ok := lastState[l.Stream]
|
||||||
|
if ok && idx != -1 {
|
||||||
|
logs[idx] = logs[idx] + string(p)
|
||||||
|
wrote = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !wrote {
|
||||||
|
if len(p) > 0 {
|
||||||
|
logs = append(logs, string(p))
|
||||||
|
}
|
||||||
|
lastState[l.Stream] = len(logs) - 1
|
||||||
|
}
|
||||||
|
if i == len(parts)-1 && len(p) == 0 {
|
||||||
|
lastState[l.Stream] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 && len(logs) > limit {
|
||||||
|
logs = logs[len(logs)-limit:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
type attachment struct {
|
type attachment struct {
|
||||||
platform *ocispecs.Platform
|
platform *ocispecs.Platform
|
||||||
descr ocispecs.Descriptor
|
descr ocispecs.Descriptor
|
||||||
|
2
go.mod
2
go.mod
@ -57,6 +57,7 @@ require (
|
|||||||
golang.org/x/sys v0.28.0
|
golang.org/x/sys v0.28.0
|
||||||
golang.org/x/term v0.27.0
|
golang.org/x/term v0.27.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.21.0
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38
|
||||||
google.golang.org/grpc v1.68.1
|
google.golang.org/grpc v1.68.1
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
||||||
google.golang.org/protobuf v1.35.2
|
google.golang.org/protobuf v1.35.2
|
||||||
@ -173,7 +174,6 @@ require (
|
|||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
golang.org/x/tools v0.25.0 // indirect
|
golang.org/x/tools v0.25.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
Loading…
x
Reference in New Issue
Block a user