11package history
22
33import (
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
3948type 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:\t Canceled\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+
345461type attachment struct {
346462 platform * ocispecs.Platform
347463 descr ocispecs.Descriptor
0 commit comments